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