2009-08-12 Xan Lopez <xlopez@igalia.com>
[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
26 #include "webkitmarshal.h"
27 #include "webkitsoupauthdialog.h"
28
29 /**
30  * SECTION:webkitsoupauthdialog
31  * @short_description: A #SoupSessionFeature to provide a simple
32  * authentication dialog for HTTP basic auth support.
33  *
34  * #WebKitSoupAuthDialog is a #SoupSessionFeature that you can attach to your
35  * #SoupSession to provide a simple authentication dialog while
36  * handling HTTP basic auth. It is built as a simple C-only module
37  * to ease reuse.
38  */
39
40 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface* feature_interface, gpointer interface_data);
41 static void attach(SoupSessionFeature* manager, SoupSession* session);
42 static void detach(SoupSessionFeature* manager, SoupSession* session);
43
44 enum {
45     CURRENT_TOPLEVEL,
46     LAST_SIGNAL
47 };
48
49 static guint signals[LAST_SIGNAL] = { 0 };
50
51 G_DEFINE_TYPE_WITH_CODE(WebKitSoupAuthDialog, webkit_soup_auth_dialog, G_TYPE_OBJECT,
52                         G_IMPLEMENT_INTERFACE(SOUP_TYPE_SESSION_FEATURE,
53                                               webkit_soup_auth_dialog_session_feature_init))
54
55 static void webkit_soup_auth_dialog_class_init(WebKitSoupAuthDialogClass* klass)
56 {
57     GObjectClass* object_class = G_OBJECT_CLASS(klass);
58
59     signals[CURRENT_TOPLEVEL] =
60       g_signal_new("current-toplevel",
61                    G_OBJECT_CLASS_TYPE(object_class),
62                    G_SIGNAL_RUN_LAST,
63                    G_STRUCT_OFFSET(WebKitSoupAuthDialogClass, current_toplevel),
64                    NULL, NULL,
65                    webkit_marshal_OBJECT__OBJECT,
66                    GTK_TYPE_WIDGET, 1,
67                    SOUP_TYPE_MESSAGE);
68 }
69
70 static void webkit_soup_auth_dialog_init(WebKitSoupAuthDialog* instance)
71 {
72 }
73
74 static void webkit_soup_auth_dialog_session_feature_init(SoupSessionFeatureInterface *feature_interface,
75                                                          gpointer interface_data)
76 {
77     feature_interface->attach = attach;
78     feature_interface->detach = detach;
79 }
80
81 typedef struct _WebKitAuthData {
82     SoupMessage* msg;
83     SoupAuth* auth;
84     SoupSession* session;
85     SoupSessionFeature* manager;
86     GtkWidget* loginEntry;
87     GtkWidget* passwordEntry;
88     GtkWidget* checkButton;
89     char *username;
90     char *password;
91 } WebKitAuthData;
92
93 static void free_authData(WebKitAuthData* authData)
94 {
95     g_object_unref(authData->msg);
96     g_free(authData->username);
97     g_free(authData->password);
98     g_slice_free(WebKitAuthData, authData);
99 }
100
101 static void save_password_callback(SoupMessage* msg, WebKitAuthData* authData)
102 {
103     /* Anything but 401 and 5xx means the password was accepted */
104     if (msg->status_code != 401 && msg->status_code < 500)
105         soup_auth_save_password(authData->auth, authData->username, authData->password);
106
107     free_authData(authData);
108 }
109
110 static void response_callback(GtkDialog* dialog, gint response_id, WebKitAuthData* authData)
111 {
112     gboolean freeAuthData = TRUE;
113
114     if (response_id == GTK_RESPONSE_OK) {
115         authData->username = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->loginEntry)));
116         authData->password = g_strdup(gtk_entry_get_text(GTK_ENTRY(authData->passwordEntry)));
117
118         soup_auth_authenticate(authData->auth, authData->username, authData->password);
119
120         if (authData->checkButton &&
121             gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(authData->checkButton))) {
122             g_signal_connect(authData->msg, "got-headers", G_CALLBACK(save_password_callback), authData);
123             freeAuthData = FALSE;
124         }
125     }
126
127     soup_session_unpause_message(authData->session, authData->msg);
128     if (freeAuthData)
129         free_authData(authData);
130     gtk_widget_destroy(GTK_WIDGET(dialog));
131 }
132
133 static GtkWidget *
134 table_add_entry(GtkWidget*  table,
135                 int         row,
136                 const char* label_text,
137                 const char* value,
138                 gpointer    user_data)
139 {
140     GtkWidget* entry;
141     GtkWidget* label;
142
143     label = gtk_label_new(label_text);
144     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
145
146     entry = gtk_entry_new();
147     gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
148
149     if (value)
150         gtk_entry_set_text(GTK_ENTRY(entry), value);
151
152     gtk_table_attach(GTK_TABLE(table), label,
153                      0, 1, row, row + 1,
154                      GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
155     gtk_table_attach_defaults(GTK_TABLE(table), entry,
156                               1, 2, row, row + 1);
157
158     return entry;
159 }
160
161 static gboolean session_can_save_passwords(SoupSession* session)
162 {
163     return soup_session_get_feature(session, SOUP_TYPE_PASSWORD_MANAGER) != NULL;
164 }
165
166 static void show_auth_dialog(WebKitAuthData* authData, const char* login, const char* password)
167 {
168     GtkWidget* toplevel;
169     GtkWidget* widget;
170     GtkDialog* dialog;
171     GtkWindow* window;
172     GtkWidget* entryContainer;
173     GtkWidget* hbox;
174     GtkWidget* mainVBox;
175     GtkWidget* vbox;
176     GtkWidget* icon;
177     GtkWidget* table;
178     GtkWidget* messageLabel;
179     char* message;
180     SoupURI* uri;
181     GtkWidget* rememberBox;
182     GtkWidget* checkButton;
183
184     /* From GTK+ gtkmountoperation.c, modified and simplified. LGPL 2 license */
185
186     widget = gtk_dialog_new();
187     window = GTK_WINDOW(widget);
188     dialog = GTK_DIALOG(widget);
189
190     gtk_dialog_add_buttons(dialog,
191                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
192                            GTK_STOCK_OK, GTK_RESPONSE_OK,
193                            NULL);
194
195     /* Set the dialog up with HIG properties */
196     gtk_dialog_set_has_separator(dialog, FALSE);
197     gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
198     gtk_box_set_spacing(GTK_BOX(dialog->vbox), 2); /* 2 * 5 + 2 = 12 */
199     gtk_container_set_border_width(GTK_CONTAINER(dialog->action_area), 5);
200     gtk_box_set_spacing(GTK_BOX(dialog->action_area), 6);
201
202     gtk_window_set_resizable(window, FALSE);
203     gtk_window_set_title(window, "");
204     gtk_window_set_icon_name(window, GTK_STOCK_DIALOG_AUTHENTICATION);
205
206     gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
207
208     /* Get the current toplevel */
209     g_signal_emit(authData->manager, signals[CURRENT_TOPLEVEL], 0, authData->msg, &toplevel);
210
211     if (toplevel)
212         gtk_window_set_transient_for(window, GTK_WINDOW(toplevel));
213
214     /* Build contents */
215     hbox = gtk_hbox_new(FALSE, 12);
216     gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
217     gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, TRUE, TRUE, 0);
218
219     icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION,
220                                     GTK_ICON_SIZE_DIALOG);
221
222     gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
223     gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
224
225     mainVBox = gtk_vbox_new(FALSE, 18);
226     gtk_box_pack_start(GTK_BOX(hbox), mainVBox, TRUE, TRUE, 0);
227
228     uri = soup_message_get_uri(authData->msg);
229     message = g_strdup_printf(_("A username and password are being requested by the site %s"), uri->host);
230     messageLabel = gtk_label_new(message);
231     g_free(message);
232     gtk_misc_set_alignment(GTK_MISC(messageLabel), 0.0, 0.5);
233     gtk_label_set_line_wrap(GTK_LABEL(messageLabel), TRUE);
234     gtk_box_pack_start(GTK_BOX(mainVBox), GTK_WIDGET(messageLabel),
235                        FALSE, FALSE, 0);
236
237     vbox = gtk_vbox_new(FALSE, 6);
238     gtk_box_pack_start(GTK_BOX(mainVBox), vbox, FALSE, FALSE, 0);
239
240     /* The table that holds the entries */
241     entryContainer = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
242
243     gtk_alignment_set_padding(GTK_ALIGNMENT(entryContainer),
244                               0, 0, 0, 0);
245
246     gtk_box_pack_start(GTK_BOX(vbox), entryContainer,
247                        FALSE, FALSE, 0);
248
249     table = gtk_table_new(2, 2, FALSE);
250     gtk_table_set_col_spacings(GTK_TABLE(table), 12);
251     gtk_table_set_row_spacings(GTK_TABLE(table), 6);
252     gtk_container_add(GTK_CONTAINER(entryContainer), table);
253
254     authData->loginEntry = table_add_entry(table, 0, _("Username:"),
255                                            login, NULL);
256     authData->passwordEntry = table_add_entry(table, 1, _("Password:"),
257                                               password, NULL);
258
259     gtk_entry_set_visibility(GTK_ENTRY(authData->passwordEntry), FALSE);
260
261     if (session_can_save_passwords(authData->session)) {
262         rememberBox = gtk_vbox_new(FALSE, 6);
263         gtk_box_pack_start(GTK_BOX(vbox), rememberBox,
264                            FALSE, FALSE, 0);
265         checkButton = gtk_check_button_new_with_mnemonic(_("_Remember password"));
266         if (login && password)
267             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkButton), TRUE);
268         gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(checkButton))), TRUE);
269         gtk_box_pack_start(GTK_BOX(rememberBox), checkButton, FALSE, FALSE, 0);
270         authData->checkButton = checkButton;
271     }
272
273     g_signal_connect(dialog, "response", G_CALLBACK(response_callback), authData);
274     gtk_widget_show_all(widget);
275 }
276
277 static void session_authenticate(SoupSession* session, SoupMessage* msg, SoupAuth* auth, gboolean retrying, gpointer user_data)
278 {
279     SoupURI* uri;
280     WebKitAuthData* authData;
281     SoupSessionFeature* manager = (SoupSessionFeature*)user_data;
282     GSList* users;
283     const char *login, *password;
284
285     soup_session_pause_message(session, msg);
286     /* We need to make sure the message sticks around when pausing it */
287     g_object_ref(msg);
288
289     uri = soup_message_get_uri(msg);
290     authData = g_slice_new0(WebKitAuthData);
291     authData->msg = msg;
292     authData->auth = auth;
293     authData->session = session;
294     authData->manager = manager;
295
296     login = password = NULL;
297
298     users = soup_auth_get_saved_users(auth);
299     if (users) {
300         login = users->data;
301         password = soup_auth_get_saved_password(auth, login);
302         soup_auth_free_saved_users(auth, users);
303     }
304
305     show_auth_dialog(authData, login, password);
306 }
307
308 static void attach(SoupSessionFeature* manager, SoupSession* session)
309 {
310     g_signal_connect(session, "authenticate", G_CALLBACK(session_authenticate), manager);
311 }
312
313 static void detach(SoupSessionFeature* manager, SoupSession* session)
314 {
315     g_signal_handlers_disconnect_by_func(session, session_authenticate, manager);
316 }
317
318