1/*
2 * Copyright (C) 2012 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#include "WebKitAuthenticationWidget.h"
22
23#include "CredentialBackingStore.h"
24#include "GtkVersioning.h"
25#include <glib/gi18n-lib.h>
26#include <wtf/gobject/GUniquePtr.h>
27#include <wtf/text/CString.h>
28
29using namespace WebCore;
30
31struct _WebKitAuthenticationWidgetPrivate {
32    AuthenticationChallenge challenge;
33    CredentialStorageMode credentialStorageMode;
34
35    GtkWidget* loginEntry;
36    GtkWidget* passwordEntry;
37    GtkWidget* rememberCheckButton;
38};
39
40G_DEFINE_TYPE(WebKitAuthenticationWidget, webkit_authentication_widget, GTK_TYPE_BOX)
41
42static const int gLayoutColumnSpacing = 12;
43static const int gLayoutRowSpacing = 6;
44static const int gButtonSpacing = 5;
45
46#ifdef GTK_API_VERSION_2
47static void packTwoColumnLayoutInBox(GtkWidget* box, ...)
48{
49    va_list argumentList;
50    va_start(argumentList, box);
51
52    GtkWidget* table = gtk_table_new(1, 2, FALSE);
53    gtk_table_set_col_spacings(GTK_TABLE(table), gLayoutColumnSpacing);
54    gtk_table_set_row_spacings(GTK_TABLE(table), gLayoutRowSpacing);
55
56    GtkWidget* firstColumnWidget = va_arg(argumentList, GtkWidget*);
57    int rowNumber = 0;
58    while (firstColumnWidget) {
59        if (rowNumber)
60            gtk_table_resize(GTK_TABLE(table), rowNumber + 1, 2);
61
62        GtkWidget* secondColumnWidget = va_arg(argumentList, GtkWidget*);
63        GtkAttachOptions attachOptions = static_cast<GtkAttachOptions>(GTK_EXPAND | GTK_FILL);
64        gtk_table_attach(
65            GTK_TABLE(table), firstColumnWidget,
66            0, secondColumnWidget ? 1 : 2,
67            rowNumber, rowNumber + 1,
68            attachOptions, attachOptions,
69            0, 0);
70        gtk_widget_show(firstColumnWidget);
71
72        if (secondColumnWidget) {
73            gtk_table_attach_defaults(GTK_TABLE(table), secondColumnWidget, 1, 2, rowNumber, rowNumber + 1);
74            gtk_widget_show(secondColumnWidget);
75        }
76
77        firstColumnWidget = va_arg(argumentList, GtkWidget*);
78        rowNumber++;
79    }
80
81    va_end(argumentList);
82
83    gtk_box_pack_start(GTK_BOX(box), table, FALSE, FALSE, 0);
84    gtk_widget_show(table);
85}
86#else
87static void packTwoColumnLayoutInBox(GtkWidget* box, ...)
88{
89    va_list argumentList;
90    va_start(argumentList, box);
91
92    GtkWidget* grid = gtk_grid_new();
93    gtk_grid_set_column_spacing(GTK_GRID(grid), gLayoutRowSpacing);
94    gtk_grid_set_row_spacing(GTK_GRID(grid), gLayoutRowSpacing);
95    gtk_grid_set_column_homogeneous(GTK_GRID(grid), TRUE);
96
97    GtkWidget* firstColumnWidget = va_arg(argumentList, GtkWidget*);
98    int rowNumber = 0;
99    while (firstColumnWidget) {
100        GtkWidget* secondColumnWidget = va_arg(argumentList, GtkWidget*);
101        int firstWidgetWidth = secondColumnWidget ? 1 : 2;
102
103        gtk_grid_attach(GTK_GRID(grid), firstColumnWidget, 0, rowNumber, firstWidgetWidth, 1);
104        gtk_widget_set_hexpand(firstColumnWidget, TRUE);
105        gtk_widget_set_vexpand(firstColumnWidget, TRUE);
106        gtk_widget_show(firstColumnWidget);
107
108        if (secondColumnWidget) {
109            gtk_grid_attach(GTK_GRID(grid), secondColumnWidget, 1, rowNumber, 1, 1);
110            gtk_widget_set_hexpand(secondColumnWidget, TRUE);
111            gtk_widget_set_vexpand(secondColumnWidget, TRUE);
112            gtk_widget_show(secondColumnWidget);
113        }
114
115        firstColumnWidget = va_arg(argumentList, GtkWidget*);
116        rowNumber++;
117    }
118
119    va_end(argumentList);
120
121    gtk_box_pack_start(GTK_BOX(box), grid, FALSE, FALSE, 0);
122    gtk_widget_show(grid);
123}
124#endif
125
126static GtkWidget* createLabel(const char* labelString, int horizontalPadding = 0)
127{
128    GtkWidget* label = gtk_label_new(labelString);
129    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
130    if (horizontalPadding)
131        gtk_misc_set_padding(GTK_MISC(label), 0, horizontalPadding);
132    return label;
133}
134
135static GtkWidget* createEntry(GtkWidget** member)
136{
137    *member = gtk_entry_new();
138    gtk_entry_set_activates_default(GTK_ENTRY(*member), TRUE);
139    return *member;
140}
141
142static void webkitAuthenticationWidgetInitialize(WebKitAuthenticationWidget* authWidget)
143{
144    gtk_orientable_set_orientation(GTK_ORIENTABLE(authWidget), GTK_ORIENTATION_HORIZONTAL);
145    gtk_box_set_spacing(GTK_BOX(authWidget), gLayoutColumnSpacing);
146    gtk_container_set_border_width(GTK_CONTAINER(authWidget), gButtonSpacing);
147
148    GtkWidget* icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_AUTHENTICATION, GTK_ICON_SIZE_DIALOG);
149    gtk_misc_set_alignment(GTK_MISC(icon), 0.5, 0.0);
150    gtk_box_pack_start(GTK_BOX(authWidget), icon, FALSE, FALSE, 0);
151    gtk_widget_show(icon);
152
153    WebKitAuthenticationWidgetPrivate* priv = authWidget->priv;
154    GUniquePtr<char> prompt(g_strdup_printf(
155        _("The site %s:%i requests a username and password"),
156        priv->challenge.protectionSpace().host().utf8().data(),
157        priv->challenge.protectionSpace().port()));
158
159    priv->rememberCheckButton = gtk_check_button_new_with_mnemonic(_("_Remember password"));
160    gtk_label_set_line_wrap(GTK_LABEL(gtk_bin_get_child(GTK_BIN(priv->rememberCheckButton))), TRUE);
161
162    String realm = authWidget->priv->challenge.protectionSpace().realm();
163    if (!realm.isEmpty()) {
164        packTwoColumnLayoutInBox(
165            GTK_WIDGET(authWidget),
166            createLabel(prompt.get(), gLayoutRowSpacing), NULL,
167            createLabel(_("Server message:")), createLabel(realm.utf8().data()),
168            createLabel(_("Username:")), createEntry(&priv->loginEntry),
169            createLabel(_("Password:")), createEntry(&priv->passwordEntry),
170            priv->rememberCheckButton, NULL,
171            NULL);
172
173    } else {
174        packTwoColumnLayoutInBox(
175            GTK_WIDGET(authWidget),
176            createLabel(prompt.get(), gLayoutRowSpacing), NULL,
177            createLabel(_("Username:")), createEntry(&priv->loginEntry),
178            createLabel(_("Password:")), createEntry(&priv->passwordEntry),
179            priv->rememberCheckButton, NULL, NULL,
180            NULL);
181    }
182    gtk_entry_set_visibility(GTK_ENTRY(priv->passwordEntry), FALSE);
183    gtk_widget_set_visible(priv->rememberCheckButton, priv->credentialStorageMode != DisallowPersistentStorage);
184
185    const Credential& credentialFromPersistentStorage = priv->challenge.proposedCredential();
186    if (!credentialFromPersistentStorage.isEmpty()) {
187        gtk_entry_set_text(GTK_ENTRY(priv->loginEntry), credentialFromPersistentStorage.user().utf8().data());
188        gtk_entry_set_text(GTK_ENTRY(priv->passwordEntry), credentialFromPersistentStorage.password().utf8().data());
189        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->rememberCheckButton), TRUE);
190    }
191
192    gtk_widget_grab_focus(priv->loginEntry);
193}
194
195static void webkitAuthenticationWidgetFinalize(GObject* object)
196{
197    WEBKIT_AUTHENTICATION_WIDGET(object)->priv->~WebKitAuthenticationWidgetPrivate();
198    G_OBJECT_CLASS(webkit_authentication_widget_parent_class)->finalize(object);
199}
200
201static void webkit_authentication_widget_init(WebKitAuthenticationWidget* authWidget)
202{
203    WebKitAuthenticationWidgetPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(authWidget, WEBKIT_TYPE_AUTHENTICATION_WIDGET, WebKitAuthenticationWidgetPrivate);
204    new (priv) WebKitAuthenticationWidgetPrivate();
205    authWidget->priv = priv;
206}
207
208static void webkit_authentication_widget_class_init(WebKitAuthenticationWidgetClass* klass)
209{
210    GObjectClass* objectClass = G_OBJECT_CLASS(klass);
211    objectClass->finalize = webkitAuthenticationWidgetFinalize;
212    g_type_class_add_private(objectClass, sizeof(WebKitAuthenticationWidgetPrivate));
213}
214
215GtkWidget* webkitAuthenticationWidgetNew(const AuthenticationChallenge& challenge, CredentialStorageMode mode)
216{
217    WebKitAuthenticationWidget* authWidget = WEBKIT_AUTHENTICATION_WIDGET(g_object_new(WEBKIT_TYPE_AUTHENTICATION_WIDGET, NULL));
218    authWidget->priv->challenge = challenge;
219    authWidget->priv->credentialStorageMode = mode;
220    webkitAuthenticationWidgetInitialize(authWidget);
221    return GTK_WIDGET(authWidget);
222}
223
224Credential webkitAuthenticationWidgetCreateCredential(WebKitAuthenticationWidget* authWidget)
225{
226    const char* username = gtk_entry_get_text(GTK_ENTRY(authWidget->priv->loginEntry));
227    const char* password = gtk_entry_get_text(GTK_ENTRY(authWidget->priv->passwordEntry));
228    bool rememberPassword = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(authWidget->priv->rememberCheckButton));
229
230    CredentialPersistence persistence;
231    if (rememberPassword && authWidget->priv->credentialStorageMode == AllowPersistentStorage)
232        persistence = CredentialPersistencePermanent;
233    else
234        persistence = CredentialPersistenceForSession;
235    return Credential(String::fromUTF8(username), String::fromUTF8(password), persistence);
236}
237
238AuthenticationChallenge& webkitAuthenticationWidgetGetChallenge(WebKitAuthenticationWidget* authWidget)
239{
240    return authWidget->priv->challenge;
241}
242