| Paste number 92134: | more patch! |
| Pasted by: | kov |
| When: | 2 years, 1 month ago |
| Share: | Tweet this! | http://paste.lisp.org/+1Z3A |
| Channel: | #webkit-gtk |
| Paste contents: |
commit 7b79f582b439cb4be8fd3a556b1636e776072723
Author: Gustavo Noronha Silva <gns@gnome.org>
Date: Mon Dec 14 16:58:32 2009 +0100
Implement form detection, and saving of passwords into the keyring
diff --git a/embed/ephy-embed-single.c b/embed/ephy-embed-single.c
index 5e7f6a2..f8e0319 100644
--- a/embed/ephy-embed-single.c
+++ b/embed/ephy-embed-single.c
@@ -26,6 +26,7 @@
#include "ephy-embed-single.h"
#include "ephy-embed-prefs.h"
#include "ephy-embed-type-builtins.h"
+#include "ephy-debug.h"
#include "ephy-file-helpers.h"
#include "ephy-marshal.h"
#include "ephy-signal-accumulator.h"
@@ -648,6 +649,8 @@ ephy_embed_single_add_form_auth (EphyEmbedSingle *single,
priv = single->priv;
+ LOG ("Appending: name field: %s / pass field: %s / username: %s / uri: %s", form_username, form_password, username, uri);
+
form_data = form_auth_data_new (form_username, form_password, username);
l = g_hash_table_lookup (priv->form_auth_data,
uri);
diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c
index e045a1d..2f65c53 100644
--- a/embed/ephy-web-view.c
+++ b/embed/ephy-web-view.c
@@ -26,6 +26,8 @@
#include <gtk/gtk.h>
#include <string.h>
#include <webkit/webkit.h>
+#include <JavaScriptCore/JavaScript.h>
+#include <gnome-keyring.h>
#include "eel-gconf-extensions.h"
#include "ephy-debug.h"
@@ -39,6 +41,7 @@
#include "ephy-prefs.h"
#include "ephy-marshal.h"
#include "ephy-permission-manager.h"
+#include "ephy-profile-migration.h"
#include "ephy-favicon-cache.h"
#include "ephy-history.h"
#include "ephy-string.h"
@@ -54,6 +57,11 @@ static void ephy_web_view_init (EphyWebView *gs);
#define RELOAD_DELAY_MAX_TICKS 40 /* RELOAD_DELAY * RELOAD_DELAY_MAX_TICKS = 10 s */
#define EMPTY_PAGE _("Blank page") /* Title for the empty page */
+typedef struct {
+ char *form_username;
+ char *form_password;
+} FormAuthData;
+
struct _EphyWebViewPrivate {
EphyWebViewSecurityLevel security_level;
EphyWebViewDocumentType document_type;
@@ -503,6 +511,447 @@ ephy_web_view_dispose (GObject *object)
G_OBJECT_CLASS (ephy_web_view_parent_class)->dispose (object);
}
+
+static char*
+js_value_to_string (JSContextRef js_context,
+ JSValueRef js_value)
+{
+ gssize length;
+ char* buffer;
+ JSStringRef str = JSValueToStringCopy (js_context, js_value, NULL);
+ g_return_val_if_fail (str != NULL, NULL);
+
+ length = JSStringGetLength (str) + 1;
+
+ buffer = g_malloc0 (length);
+ JSStringGetUTF8CString (str, buffer, length);
+ JSStringRelease (str);
+
+ return buffer;
+}
+
+static JSValueRef
+js_object_get_property (JSContextRef js_context,
+ JSObjectRef js_object,
+ const char *name)
+{
+ JSStringRef js_string = JSStringCreateWithUTF8CString (name);
+ JSValueRef js_value = JSObjectGetProperty (js_context, js_object, js_string, NULL);
+
+ JSStringRelease (js_string);
+
+ return js_value;
+}
+
+static JSObjectRef
+js_object_get_property_as_object (JSContextRef js_context,
+ JSObjectRef object,
+ const char *attr)
+{
+ JSStringRef attrstr = JSStringCreateWithUTF8CString (attr);
+ JSValueRef val = JSObjectGetProperty (js_context, object, attrstr, NULL);
+
+ JSStringRelease (attrstr);
+ return JSValueToObject (js_context, val, NULL);
+}
+
+static char *
+js_get_element_attribute (JSContextRef js_context,
+ JSObjectRef object,
+ const char *attr)
+{
+ JSStringRef attrstr = JSStringCreateWithUTF8CString (attr);
+ JSObjectRef ga = js_object_get_property_as_object (js_context, object, "getAttribute");
+ JSValueRef args[1], val;
+ char *buffer = NULL;
+
+ args[0] = JSValueMakeString (js_context, attrstr);
+ val = JSObjectCallAsFunction (js_context, ga, object, 1, args, NULL);
+ JSStringRelease (attrstr);
+
+ if (JSValueIsString (js_context, val)) {
+ buffer = js_value_to_string (js_context, val);
+ }
+
+ return buffer;
+}
+
+static GSList*
+js_get_all_forms (JSContextRef js_context)
+{
+ JSObjectRef js_global;
+ JSObjectRef js_object;
+ JSValueRef js_form;
+ guint index = 0;
+ GSList *retval = NULL;
+
+ js_global = JSContextGetGlobalObject (js_context);
+
+ js_object = js_object_get_property_as_object (js_context, js_global, "document");
+ if (!js_object)
+ return NULL;
+
+ js_object = js_object_get_property_as_object (js_context, js_object, "forms");
+ if (!js_object)
+ return NULL;
+
+ js_form = JSObjectGetPropertyAtIndex (js_context, js_object, index++, NULL);
+ while (!JSValueIsUndefined (js_context, js_form)) {
+ retval = g_slist_prepend (retval, (gpointer)js_form);
+ js_form = JSObjectGetPropertyAtIndex (js_context, js_object, index++, NULL);
+ }
+
+ return retval;
+}
+
+static GSList*
+js_get_form_elements (JSContextRef js_context, JSValueRef js_form)
+{
+ JSObjectRef js_object = JSValueToObject (js_context, js_form, NULL);
+ JSStringRef js_name;
+ JSValueRef value;
+ guint num;
+ guint count;
+ GSList *retval = NULL;
+
+ js_object = js_object_get_property_as_object (js_context, js_object, "elements");
+ if (!js_object)
+ return NULL;
+
+ js_name = JSStringCreateWithUTF8CString ("length");
+ value = JSObjectGetProperty (js_context, js_object, js_name, NULL);
+ JSStringRelease (js_name);
+
+ num = (guint)JSValueToNumber (js_context, value, NULL);
+ for (count = 0; count < num; count++) {
+ value = JSObjectGetPropertyAtIndex (js_context, js_object, count, NULL);
+
+ if (!JSValueIsObject (js_context, value))
+ continue;
+
+ retval = g_slist_prepend (retval, (gpointer)value);
+ }
+
+ return retval;
+}
+
+typedef struct {
+ JSContextRef contxext;
+ JSObjectRef object;
+} FillData;
+
+static void
+find_username_and_password_elements (JSContextRef js_context,
+ GSList *elements,
+ JSObjectRef *name_element,
+ JSObjectRef *password_element,
+ gboolean auto_fill,
+ EphyWebView *view)
+{
+ GSList *iter = elements;
+ GSList *l = NULL;
+ SoupURI *uri = NULL;
+
+ if (auto_fill) {
+ uri = soup_uri_new (webkit_web_view_get_uri (WEBKIT_WEB_VIEW (view)));
+ if (uri->host)
+ l = ephy_embed_single_get_form_auth (EPHY_EMBED_SINGLE (ephy_embed_shell_get_embed_single (embed_shell)), uri->host);
+ }
+
+ for (; iter; iter = iter->next) {
+ JSObjectRef object;
+ char *type;
+
+ object = JSValueToObject (js_context, (JSValueRef)iter->data, NULL);
+
+ type = js_get_element_attribute (js_context, object, "type");
+ if (!type)
+ continue;
+
+ if (g_str_equal (type, "text")) {
+ /* We found more than one inputs of type text; we won't be
+ * saving here */
+ if (*name_element) {
+ *name_element = NULL;
+ break;
+ }
+
+ *name_element = object;
+ } else if (g_str_equal (type, "password")) {
+ if (*password_element) {
+ *password_element = NULL;
+ break;
+ }
+
+ *password_element = object;
+ }
+
+ if (auto_fill) {
+ GSList *p;
+
+ for (p = l; p; p = p->next) {
+ FormAuthData *data = (FormAuthData*)p->data;
+ if (!g_strcmp0 (js_get_element_attribute (js_context, object, "name"), data->form_username) ||
+ !g_strcmp0 (js_get_element_attribute (js_context, object, "name"), data->form_password)) {
+ GList *results = NULL;
+ JSValueRef prop_value;
+ JSStringRef prop_value_str, prop_name;
+ char *uri_str = soup_uri_to_string (uri, FALSE);
+
+ results = _ephy_profile_query_form_auth_data (uri_str,
+ data->form_username,
+ data->form_password);
+
+ g_free (uri_str);
+
+ if (!results) {
+ LOG ("NO RESULTS");
+ return;
+ }
+
+ prop_name = JSStringCreateWithUTF8CString ("value");
+ LOG ("USER %s PASS (hidden)", ((GnomeKeyringNetworkPasswordData*)results->data)->user);
+
+ if (!g_strcmp0 (js_get_element_attribute (js_context, object, "name"), data->form_username)) {
+ prop_value_str = JSStringCreateWithUTF8CString (((GnomeKeyringNetworkPasswordData*)results->data)->user);
+ } else {
+ prop_value_str = JSStringCreateWithUTF8CString (((GnomeKeyringNetworkPasswordData*)results->data)->password);
+ }
+
+ prop_value = JSValueMakeString (js_context, prop_value_str);
+ JSObjectSetProperty (js_context, object, prop_name, prop_value, 0, NULL);
+ }
+ }
+ }
+
+ g_free (type);
+ }
+
+ if (auto_fill)
+ soup_uri_free (uri);
+}
+
+static char*
+js_get_domain_and_path (JSContextRef js_context)
+{
+ JSObjectRef js_object;
+ JSValueRef js_value;
+ char *tmp;
+ GString *result = NULL;
+
+ js_object = JSContextGetGlobalObject (js_context);
+
+ js_object = js_object_get_property_as_object (js_context, js_object, "document");
+ if (!js_object)
+ return NULL;
+
+ js_object = js_object_get_property_as_object (js_context, js_object, "location");
+ if (!js_object)
+ return NULL;
+
+ /* We got document.location; now we are going to build the string:
+ * protocol + // + host + port? + path
+ */
+
+ /* protocol */
+ js_value = js_object_get_property (js_context, js_object, "protocol");
+ if (!JSValueIsString (js_context, js_value))
+ goto js_get_domain_and_path_fail;
+
+ tmp = js_value_to_string (js_context, js_value);
+ result = g_string_new (tmp);
+ g_free (tmp);
+
+ /* // */
+ g_string_append (result, "//");
+
+ /* host */
+ js_value = js_object_get_property (js_context, js_object, "host");
+ if (!JSValueIsString (js_context, js_value))
+ goto js_get_domain_and_path_fail;
+
+ tmp = js_value_to_string (js_context, js_value);
+ g_string_append (result, tmp);
+ g_free (tmp);
+
+ /* port? */
+ js_value = js_object_get_property (js_context, js_object, "port");
+ if (!JSValueIsString (js_context, js_value))
+ goto js_get_domain_and_path_fail;
+
+ tmp = js_value_to_string (js_context, js_value);
+ if (!g_str_equal (tmp, "")) {
+ g_string_append (result, ":");
+ g_string_append (result, tmp);
+ }
+ g_free (tmp);
+
+ /* pathname */
+ js_value = js_object_get_property (js_context, js_object, "pathname");
+ if (!JSValueIsString (js_context, js_value))
+ goto js_get_domain_and_path_fail;
+
+ tmp = js_value_to_string (js_context, js_value);
+ g_string_append (result, tmp);
+ g_free (tmp);
+
+ tmp = result->str;
+ LOG ("Obtained the following from document.location: %s", tmp);
+ g_string_free (result, FALSE);
+ return tmp;
+
+ js_get_domain_and_path_fail:
+ if (result)
+ g_string_free (result, TRUE);
+
+ return NULL;
+}
+
+static JSValueRef
+do_form_submitted_cb (JSContextRef js_context,
+ JSObjectRef js_function,
+ JSObjectRef js_this,
+ size_t argument_count,
+ const JSValueRef js_arguments[],
+ JSValueRef* js_exception)
+{
+ GSList *elements = js_get_form_elements (js_context, js_this);
+ JSObjectRef name_element = NULL;
+ JSObjectRef password_element = NULL;
+ JSStringRef js_string;
+ JSValueRef js_value;
+ char *name_field_name;
+ char *name_field_value;
+ char *password_field_name;
+ char *password_field_value;
+ char *uri;
+ SoupURI *soup_uri;
+
+ LOG ("Form submitted!");
+
+ find_username_and_password_elements (js_context, elements, &name_element, &password_element, FALSE, NULL);
+ g_slist_free (elements);
+
+ if (!name_element || !password_element)
+ return JSValueMakeUndefined (js_context);
+
+ name_field_name = js_get_element_attribute (js_context, name_element, "name");
+ password_field_name = js_get_element_attribute (js_context, password_element, "name");
+
+ js_string = JSStringCreateWithUTF8CString ("value");
+ js_value = JSObjectGetProperty (js_context, name_element, js_string, NULL);
+
+ name_field_value = js_value_to_string (js_context, js_value);
+
+ js_value = JSObjectGetProperty (js_context, password_element, js_string, NULL);
+ JSStringRelease (js_string);
+
+ password_field_value = js_value_to_string (js_context, js_value);
+
+ uri = js_get_domain_and_path (js_context);
+ _ephy_profile_store_form_auth_data (uri,
+ name_field_name,
+ password_field_name,
+ name_field_value,
+ password_field_value);
+
+ /* Update internal caching */
+ soup_uri = soup_uri_new (uri);
+ g_free (uri);
+
+ ephy_embed_single_add_form_auth (EPHY_EMBED_SINGLE (ephy_embed_shell_get_embed_single (embed_shell)),
+ soup_uri->host,
+ name_field_name,
+ password_field_name,
+ name_field_value);
+ soup_uri_free (soup_uri);
+
+ g_free (name_field_name);
+ g_free (name_field_value);
+ g_free (password_field_name);
+ g_free (password_field_value);
+
+ return JSValueMakeUndefined (js_context);
+}
+
+static void
+hook_form (JSContextRef js_context, JSValueRef js_form, JSObjectRef form_submitted_cb)
+{
+ JSObjectRef object = JSValueToObject (js_context, js_form, NULL);
+ JSObjectRef add_event_listener = js_object_get_property_as_object (js_context, object, "addEventListener");
+ JSStringRef event_name;
+ JSValueRef args[3], val;
+ JSValueRef js_exception;
+
+ event_name = JSStringCreateWithUTF8CString ("submit");
+ args[0] = JSValueMakeString (js_context, event_name);
+ JSStringRelease (event_name);
+
+ args[1] = form_submitted_cb;
+ args[2] = JSValueMakeBoolean (js_context, TRUE);
+ val = JSObjectCallAsFunction (js_context, add_event_listener, object, 3, args, &js_exception);
+}
+
+static void
+do_hook_into_forms (JSContextRef js_context, JSObjectRef form_submitted_cb, EphyWebView *web_view)
+{
+ GSList *forms = js_get_all_forms (js_context);
+
+ if (!forms) {
+ LOG ("No forms found.");
+ return;
+ }
+
+ for (; forms; forms = forms->next) {
+ JSValueRef form = (JSValueRef)forms->data;
+ GSList *elements = js_get_form_elements (js_context, form);
+ JSObjectRef name_element = NULL;
+ JSObjectRef password_element = NULL;
+
+ if (!elements) {
+ LOG ("No elements found for this form.");
+ continue;
+ }
+
+ find_username_and_password_elements (js_context, elements, &name_element, &password_element, TRUE, web_view);
+ g_slist_free (elements);
+
+ /* We have a field that may be the user, and one for a password. */
+ if (name_element && password_element) {
+ LOG ("Hooking into form: %s / %s",
+ js_get_element_attribute (js_context, name_element, "name"),
+ js_get_element_attribute (js_context, password_element, "name");
+
+ hook_form (js_context, form, form_submitted_cb);
+ } else
+ LOG ("NOT hooking into form: username element: %p / password element: %p", name_element, password_element);
+ }
+
+ g_slist_free (forms);
+}
+
+static void
+_ephy_web_view_hook_into_forms (EphyWebView *web_view)
+{
+ WebKitWebFrame *web_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (web_view));
+ JSGlobalContextRef js_context;
+ JSObjectRef js_global;
+ JSStringRef js_function_name;
+ JSObjectRef js_function_object;
+
+ js_context = webkit_web_frame_get_global_context (web_frame);
+ js_global = JSContextGetGlobalObject (js_context);
+
+ js_function_name = JSStringCreateWithUTF8CString ("_EpiphanyInternalFormSubmitted");
+ js_function_object = JSObjectMakeFunctionWithCallback (js_context,
+ js_function_name,
+ (JSObjectCallAsFunctionCallback)do_form_submitted_cb);
+ JSObjectSetProperty (js_context, js_global, js_function_name, js_function_object, 0, NULL);
+ JSStringRelease (js_function_name);
+
+ do_hook_into_forms (js_context, js_function_object, EPHY_WEB_VIEW (web_view));
+}
+
static void
ephy_web_view_finalize (GObject *object)
{
@@ -1058,6 +1507,21 @@ mime_type_policy_decision_requested_cb (WebKitWebView *web_view,
}
static void
+load_status_cb (WebKitWebView *web_view,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ WebKitLoadStatus status = webkit_web_view_get_load_status (web_view);
+
+ if (status == WEBKIT_LOAD_FINISHED) {
+ if (!eel_gconf_get_boolean (CONF_PRIVACY_REMEMBER_PASSWORDS))
+ return;
+
+ _ephy_web_view_hook_into_forms (EPHY_WEB_VIEW (web_view));
+ }
+}
+
+static void
ephy_web_view_init (EphyWebView *web_view)
{
EphyWebViewPrivate *priv;
@@ -1088,6 +1552,10 @@ ephy_web_view_init (EphyWebView *web_view)
G_CALLBACK (mime_type_policy_decision_requested_cb),
NULL);
+ g_signal_connect (web_view, "notify::load-status",
+ G_CALLBACK (load_status_cb),
+ NULL);
+
g_signal_connect_object (web_view, "icon-loaded",
G_CALLBACK (favicon_cb),
web_view, (GConnectFlags)0);
@@ -2462,3 +2930,5 @@ ephy_web_view_save (EphyWebView *view, const char *uri)
ephy_web_view_save_sub_resources (view, uri, subresources);
}
+
+
diff --git a/embed/ephy-web-view.h b/embed/ephy-web-view.h
index 5ed358b..31a41ae 100644
--- a/embed/ephy-web-view.h
+++ b/embed/ephy-web-view.h
@@ -221,6 +221,8 @@ void ephy_web_view_popups_manager_reset (EphyWebView
void ephy_web_view_save (EphyWebView *view,
const char *uri);
+void ephy_web_view_hook_into_forms (EphyWebView *web_view);
+
G_END_DECLS
#endif
diff --git a/lib/ephy-profile-migration.c b/lib/ephy-profile-migration.c
index ee57aed..56fee6a 100644
--- a/lib/ephy-profile-migration.c
+++ b/lib/ephy-profile-migration.c
@@ -33,6 +33,7 @@
#include "ephy-profile-migration.h"
+#include "ephy-debug.h"
#include "ephy-file-helpers.h"
#ifdef ENABLE_NSS
#include "ephy-nss-glue.h"
@@ -432,39 +433,90 @@ _ephy_profile_store_form_auth_data (const char *uri,
const char *username,
const char *password)
{
- SoupURI *fake_uri;
- char *fake_uri_str;
-
- g_return_if_fail (uri);
- g_return_if_fail (form_username);
- g_return_if_fail (form_password);
- g_return_if_fail (username);
- g_return_if_fail (password);
-
- fake_uri = soup_uri_new (uri);
- /* Store the form login and password names encoded in the
- * URL. A bit of an abuse of keyring, but oh well */
- soup_uri_set_query_from_fields (fake_uri,
- FORM_USERNAME_KEY,
- form_username,
- FORM_PASSWORD_KEY,
- form_password,
- NULL);
- fake_uri_str = soup_uri_to_string (fake_uri, FALSE);
- gnome_keyring_set_network_password (NULL,
- username,
+ SoupURI *fake_uri;
+ char *fake_uri_str;
+
+ g_return_if_fail (uri);
+ g_return_if_fail (form_username);
+ g_return_if_fail (form_password);
+ g_return_if_fail (username);
+ g_return_if_fail (password);
+
+ fake_uri = soup_uri_new (uri);
+
+ /* We normalize https? schemes here so that we use passwords
+ * we stored in https sites in their http counterparts, and
+ * vice-versa. */
+ if (g_str_equal (fake_uri->scheme, SOUP_URI_SCHEME_HTTPS))
+ soup_uri_set_scheme (fake_uri, SOUP_URI_SCHEME_HTTP);
+
+ /* Store the form login and password names encoded in the
+ * URL. A bit of an abuse of keyring, but oh well */
+ soup_uri_set_query_from_fields (fake_uri,
+ FORM_USERNAME_KEY,
+ form_username,
+ FORM_PASSWORD_KEY,
+ form_password,
+ NULL);
+ fake_uri_str = soup_uri_to_string (fake_uri, FALSE);
+ gnome_keyring_set_network_password (NULL,
+ username,
+ NULL,
+ fake_uri_str,
+ NULL,
+ fake_uri->scheme,
+ NULL,
+ fake_uri->port,
+ password,
+ (GnomeKeyringOperationGetIntCallback)store_form_password_cb,
+ NULL,
+ NULL);
+ soup_uri_free (fake_uri);
+ g_free (fake_uri_str);
+}
+
+GList*
+_ephy_profile_query_form_auth_data (const char *uri,
+ const char *form_username,
+ const char *form_password)
+{
+ SoupURI *key;
+ char *key_str;
+ GList *results = NULL;
+
+ g_return_val_if_fail (uri, NULL);
+ g_return_val_if_fail (form_username, NULL);
+ g_return_val_if_fail (form_password, NULL);
+
+ key = soup_uri_new (uri);
+
+ /* We normalize https? schemes here so that we use passwords
+ * we stored in https sites in their http counterparts, and
+ * vice-versa. */
+ if (g_str_equal (key->scheme, SOUP_URI_SCHEME_HTTPS))
+ soup_uri_set_scheme (key, SOUP_URI_SCHEME_HTTP);
+
+ soup_uri_set_query_from_fields (key,
+ "form_username",
+ form_username,
+ "form_password",
+ form_password,
+ NULL);
+ key_str = soup_uri_to_string (key, FALSE);
+
+ LOG ("QUERY %s", key_str);
+ gnome_keyring_find_network_password_sync (NULL,
NULL,
- fake_uri_str,
+ key_str,
NULL,
- fake_uri->scheme,
NULL,
- fake_uri->port,
- password,
- (GnomeKeyringOperationGetIntCallback)store_form_password_cb,
NULL,
- NULL);
- soup_uri_free (fake_uri);
- g_free (fake_uri_str);
+ 0,
+ &results);
+ soup_uri_free (key);
+ g_free (key_str);
+
+ return results;
}
#define PROFILE_MIGRATION_FILE ".migrated"
diff --git a/lib/ephy-profile-migration.h b/lib/ephy-profile-migration.h
index 7fe4945..73501a4 100644
--- a/lib/ephy-profile-migration.h
+++ b/lib/ephy-profile-migration.h
@@ -20,6 +20,8 @@
#ifndef EPHY_PROFILE_MIGRATION_H
#define EPHY_PROFILE_MIGRATION_H
+#include <glib.h>
+
#define FORM_USERNAME_KEY "form_username"
#define FORM_PASSWORD_KEY "form_password"
@@ -31,4 +33,9 @@ void _ephy_profile_store_form_auth_data (const char *uri,
const char *username,
const char *password);
+GList*
+_ephy_profile_query_form_auth_data (const char *uri,
+ const char *form_username,
+ const char *form_password);
+
#endif
This paste has no annotations.