9bc188e3c11bcfd41199d3fa31770aeaec452eb5
[WebKit-https.git] / WebKit / gtk / webkit / webkitsoupauthdialog.c
1 /*
2  * Copyright (C) 2009 Igalia S.L.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #include <glib/gi18n-lib.h>
23 #include <gtk/gtk.h>
24 #include <libsoup/soup.h>
25 #if USE(GNOMEKEYRING)
26 #include <gnome-keyring.h>
27 #endif
28
29 #include "webkitmarshal.h"
30 #include "webkitsoupauthdialog.h"
31
32 /**
33  * SECTION:webkitsoupauthdialog
34  * @short_description: A #SoupFeature to provide a simple
35  * authentication dialog for HTTP basic auth support.
36  *
37  * #WebKitSoupAuthDialog is a #SoupFeature that you can attach to your
38  * #SoupSession to provide a simple authentication dialog, with
39  * optional GNOME Keyring support, while handling HTTP basic auth. It
40  * is built as a simple C-only module to ease reuse.
41  */
42
43 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface* feature_interface, gpointer interface_data);
44 static void attach(SoupSessionFeature* manager, SoupSession* session);
45 static void detach(SoupSessionFeature* manager, SoupSession* session);
46
47 enum {
48     CURRENT_TOPLEVEL,
49     LAST_SIGNAL
50 };
51
52 static guint signals[LAST_SIGNAL] = { 0 };
53
54 G_DEFINE_TYPE_WITH_CODE(WebKitSoupAuthDialog, webkit_soup_auth_dialog, G_TYPE_OBJECT,
55                         G_IMPLEMENT_INTERFACE(SOUP_TYPE_SESSION_FEATURE,
56                                               webkit_soup_auth_dialog_session_feature_init))
57
58 static void webkit_soup_auth_dialog_class_init(WebKitSoupAuthDialogClass* klass)
59 {
60     GObjectClass* object_class = G_OBJECT_CLASS(klass);
61
62     signals[CURRENT_TOPLEVEL] =
63       g_signal_new("current-toplevel",
64                    G_OBJECT_CLASS_TYPE(object_class),
65                    G_SIGNAL_RUN_LAST,
66                    G_STRUCT_OFFSET(WebKitSoupAuthDialogClass, current_toplevel),
67                    NULL, NULL,
68                    webkit_marshal_OBJECT__OBJECT,
69                    GTK_TYPE_WIDGET, 1,
70                    SOUP_TYPE_MESSAGE);
71 }
72
73 static void webkit_soup_auth_dialog_init(WebKitSoupAuthDialog* instance)
74 {
75 }
76
77 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface *feature_interface,
78                                                          gpointer interface_data)
79 {
80     feature_interface->attach = attach;
81     feature_interface->detach = detach;
82 }
83
84 typedef struct _WebKitAuthData {
85     SoupMessage* msg;
86     SoupAuth* auth;
87     SoupSession* session;
88     SoupSessionFeature* manager;
89     GtkWidget* loginEntry;
90     GtkWidget* passwordEntry;
91 #if USE(GNOMEKEYRING)
92     GtkWidget* checkButton;
93 #endif
94     char *username;
95     char *password;
96 } WebKitAuthData;
97
98 static void free_authData(WebKitAuthData* authData)
99 {
100     g_object_unref(authData->msg);
101     g_free(authData->username);
102     g_free(authData->password);
103     g_slice_free(WebKitAuthData, authData);
104 }
105
106 #if USE(GNOMEKEYRING)
107 static void set_password_callback(GnomeKeyringResult result, guint32 val, gpointer user_data)
108 {
109     /* Dummy callback, gnome_keyring_set_network_password does not accept a NULL one */
110 }
111
112 static void save_password_callback(SoupMessage* msg, WebKitAuthData* authData)
113 {
114     /* Check only for Success status codes (2xx) */
115     if (msg->status_code >= 200 && msg->status_code < 300) {
116         SoupURI* uri = soup_message_get_uri(authData->msg);
117         gnome_keyring_set_network_password(NULL,
118                                            authData->username,
119                                            soup_auth_get_realm(authData->auth),
120                                            uri->host,
121                                            NULL,
122                                            uri->scheme,
123                                            soup_auth_get_scheme_name(authData->auth),
124                                            uri->port,
125                                            authData->password,
126                                            (GnomeKeyringOperationGetIntCallback)set_password_callback,
127                                            NULL,
128                                            NULL);
129     }
130     free_authData(authData);
131 }
132 #endif
133
134 static void response_callback(GtkDialog* dialog, gint response_id, WebKitAuthData* authData)
135 {
136     switch(response_id) {
137     case GTK_RESPONSE_OK:
138         authData->username = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->loginEntry)));
139         authData->password = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->passwordEntry)));
140         soup_auth_authenticate(authData->auth, authData->username, authData->password);
141
142 #if USE(GNOMEKEYRING)
143         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(authData->checkButton)))
144             g_signal_connect(authData->msg, "got-headers", G_CALLBACK(save_password_callback), authData);
145 #endif
146     default:
147         break;
148     }
149
150     soup_session_unpause_message(authData->session, authData->msg);
151 #if !USE(GNOMEKEYRING)
152     free_authData(authData);
153 #endif
154     gtk_widget_destroy(GTK_WIDGET(dialog));
155 }
156
157 static GtkWidget *
158 table_add_entry (GtkWidget*  table,
159                  int         row,
160                  const char* label_text,
161                  const char* value,
162                  gpointer    user_data)
163 {
164     GtkWidget* entry;
165     GtkWidget* label;
166
167     label = gtk_label_new(label_text);
168     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
169
170     entry = gtk_entry_new();
171     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
172
173     if (value)
174         gtk_entry_set_text(GTK_ENTRY(entry), value);
175
176     gtk_table_attach(GTK_TABLE(table), label,
177                      0, 1, row, row + 1,
178                      GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
179     gtk_table_attach_defaults(GTK_TABLE(table), entry,
180                               1, 2, row, row + 1);
181
182     return entry;
183 }
184
185 static void show_auth_dialog(WebKitAuthData* authData, const char* login, const char* password)
186 {
187     GtkWidget* toplevel;
188     GtkWidget* widget;
189     GtkDialog* dialog;
190     GtkWindow* window;
191     GtkWidget* entryContainer;
192     GtkWidget* hbox;
193     GtkWidget* mainVBox;
194     GtkWidget* vbox;
195     GtkWidget* icon;
196     GtkWidget* table;
197     GtkWidget* messageLabel;
198     char* message;
199     SoupURI* uri;
200 #if USE(GNOMEKEYRING)
201     GtkWidget* rememberBox;
202     GtkWidget* checkButton;
203 #endif
204
205     /* From GTK+ gtkmountoperation.c, modified and simplified. LGPL 2 license */
206
207     widget = gtk_dialog_new();
208     window = GTK_WINDOW(widget);
209     dialog = GTK_DIALOG(widget);
210
211     gtk_dialog_add_buttons(dialog,
212                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
213                            GTK_STOCK_OK, GTK_RESPONSE_OK,
214                            NULL);
215
216     /* Set the dialog up with HIG properties */
217     gtk_dialog_set_has_separator(dialog, FALSE);
218     gtk_container_set_border_width(GTK_CONTAINER (dialog), 5);
219     gtk_box_set_spacing(GTK_BOX(dialog->vbox), 2); /* 2 * 5 + 2 = 12 */
220     gtk_container_set_border_width(GTK_CONTAINER (dialog->action_area), 5);
221     gtk_box_set_spacing(GTK_BOX(dialog->action_area), 6);
222
223     gtk_window_set_resizable(window, FALSE);
224     gtk_window_set_title(window, "");
225     gtk_window_set_icon_name(window, GTK_STOCK_DIALOG_AUTHENTICATION);
226
227     gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
228
229     /* Get the current toplevel */
230     g_signal_emit(authData->manager, signals[CURRENT_TOPLEVEL], 0, authData->msg, &toplevel);
231
232     if (toplevel)
233         gtk_window_set_transient_for(window, GTK_WINDOW(toplevel));
234
235     /* Build contents */
236     hbox = gtk_hbox_new(FALSE, 12);
237     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
238     gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, TRUE, TRUE, 0);
239
240     icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION,
241                                     GTK_ICON_SIZE_DIALOG);
242
243     gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
244     gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
245
246     mainVBox = gtk_vbox_new(FALSE, 18);
247     gtk_box_pack_start(GTK_BOX(hbox), mainVBox, TRUE, TRUE, 0);
248
249     uri = soup_message_get_uri(authData->msg);
250     message = g_strdup_printf(_("A username and password are being requested by the site %s"), uri->host);
251     messageLabel = gtk_label_new(message);
252     g_free(message);
253     gtk_misc_set_alignment(GTK_MISC(messageLabel), 0.0, 0.5);
254     gtk_label_set_line_wrap(GTK_LABEL(messageLabel), TRUE);
255     gtk_box_pack_start(GTK_BOX(mainVBox), GTK_WIDGET(messageLabel),
256                        FALSE, FALSE, 0);
257
258     vbox = gtk_vbox_new(FALSE, 6);
259     gtk_box_pack_start(GTK_BOX (mainVBox), vbox, FALSE, FALSE, 0);
260
261     /* The table that holds the entries */
262     entryContainer = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
263
264     gtk_alignment_set_padding(GTK_ALIGNMENT(entryContainer),
265                               0, 0, 0, 0);
266
267     gtk_box_pack_start(GTK_BOX(vbox), entryContainer,
268                        FALSE, FALSE, 0);
269
270     table = gtk_table_new(2, 2, FALSE);
271     gtk_table_set_col_spacings(GTK_TABLE (table), 12);
272     gtk_table_set_row_spacings(GTK_TABLE (table), 6);
273     gtk_container_add(GTK_CONTAINER(entryContainer), table);
274
275     authData->loginEntry = table_add_entry(table, 0, _("Username:"),
276                                            login, NULL);
277     authData->passwordEntry = table_add_entry(table, 1, _("Password:"),
278                                               password, NULL);
279
280     gtk_entry_set_visibility(GTK_ENTRY(authData->passwordEntry), FALSE);
281
282 #if USE(GNOMEKEYRING)
283     rememberBox = gtk_vbox_new (FALSE, 6);
284     gtk_box_pack_start (GTK_BOX (vbox), rememberBox,
285                         FALSE, FALSE, 0);
286
287     checkButton = gtk_check_button_new_with_mnemonic(_("_Remember password"));
288     if (login && password)
289         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkButton), TRUE);
290     gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(checkButton))), TRUE);
291     gtk_box_pack_start (GTK_BOX (rememberBox), checkButton, FALSE, FALSE, 0);
292     authData->checkButton = checkButton;
293 #endif
294
295     g_signal_connect(dialog, "response", G_CALLBACK(response_callback), authData);
296     gtk_widget_show_all(widget);
297 }
298
299 #if USE(GNOMEKEYRING)
300 static void find_password_callback(GnomeKeyringResult result, GList* list, WebKitAuthData* authData)
301 {
302     GList* p;
303     const char* login = NULL;
304     const char* password = NULL;
305
306     for (p = list; p; p = p->next) {
307         /* FIXME: support multiple logins/passwords ? */
308         GnomeKeyringNetworkPasswordData* data = (GnomeKeyringNetworkPasswordData*)p->data;
309         login = data->user;
310         password = data->password;
311         break;
312     }
313
314     show_auth_dialog(authData, login, password);
315 }
316 #endif
317
318 static void session_authenticate(SoupSession* session, SoupMessage* msg, SoupAuth* auth, gboolean retrying, gpointer user_data)
319 {
320     SoupURI* uri;
321     WebKitAuthData* authData;
322     SoupSessionFeature* manager = (SoupSessionFeature*)user_data;
323
324     soup_session_pause_message(session, msg);
325     /* We need to make sure the message sticks around when pausing it */
326     g_object_ref(msg);
327
328     uri = soup_message_get_uri(msg);
329     authData = g_slice_new(WebKitAuthData);
330     authData->msg = msg;
331     authData->auth = auth;
332     authData->session = session;
333     authData->manager = manager;
334
335     /*
336      * If we have gnome-keyring let's try to find the password first in the ring.
337      * Otherwise just show the dialog straight away
338      */
339 #if USE(GNOMEKEYRING)
340     gnome_keyring_find_network_password(NULL,
341                                         soup_auth_get_realm(auth),
342                                         uri->host,
343                                         NULL,
344                                         uri->scheme,
345                                         soup_auth_get_scheme_name(auth),
346                                         uri->port,
347                                         (GnomeKeyringOperationGetListCallback)find_password_callback,
348                                         authData,
349                                         NULL);
350 #else
351     show_auth_dialog(authData, NULL, NULL);
352 #endif
353 }
354
355 static void attach(SoupSessionFeature* manager, SoupSession* session)
356 {
357     g_signal_connect(session, "authenticate", G_CALLBACK(session_authenticate), manager);
358 }
359
360 static void detach(SoupSessionFeature* manager, SoupSession* session)
361 {
362     g_signal_handlers_disconnect_by_func(session, session_authenticate, manager);
363 }
364
365