[epiphany/pgriffis/web-extension/cookies: 3/3] WebExtensions: Implement cookies API
- From: Marge Bot <marge-bot src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/pgriffis/web-extension/cookies: 3/3] WebExtensions: Implement cookies API
- Date: Sat, 11 Jun 2022 20:36:54 +0000 (UTC)
commit 4951b8467640cf2c3966051c2e22c1817e5262cc
Author: Patrick Griffis <pgriffis igalia com>
Date: Sat Jun 11 12:21:21 2022 -0500
WebExtensions: Implement cookies API
This implements nearly all of the cookies API with two exceptions:
- WebKitCookiesManager::changed doesn't give us enough information to
implement cookies.onChanged.
- WebKitCookiesManager doesn't expose the ability easily list all cookies
for every URL.
Part-of: <https://gitlab.gnome.org/GNOME/epiphany/-/merge_requests/1142>
.../resources/js/webextensions.js | 12 +-
src/webextension/api/cookies.c | 620 +++++++++++++++++++++
src/webextension/api/cookies.h | 35 ++
src/webextension/ephy-web-extension-manager.c | 2 +
src/webextension/ephy-web-extension.c | 56 +-
src/webextension/ephy-web-extension.h | 3 +
src/webextension/meson.build | 1 +
7 files changed, 717 insertions(+), 12 deletions(-)
---
diff --git a/embed/web-process-extension/resources/js/webextensions.js
b/embed/web-process-extension/resources/js/webextensions.js
index 6d67ec647..0d3f738ce 100644
--- a/embed/web-process-extension/resources/js/webextensions.js
+++ b/embed/web-process-extension/resources/js/webextensions.js
@@ -125,4 +125,14 @@ window.browser.windows = {
onCreated: new EphyEventListener (),
onRemoved: new EphyEventListener (),
onFocusChanged: new EphyEventListener (),
-};
\ No newline at end of file
+};
+
+window.browser.cookies = {
+ get: function (...args) { return ephy_message ('cookies.get', args); },
+ getAll: function (...args) { return ephy_message ('cookies.getAll', args); },
+ getAllCookieStores: function (...args) { return ephy_message ('cookies.getAllCookieStores', args); },
+ remove: function (...args) { return ephy_message ('cookies.remove', args); },
+ set: function (...args) { return ephy_message ('cookies.set', args); },
+ // This is a stub as WebKitCookieManager::changed doesn't tell us enough information.
+ onChanged: new EphyEventListener (),
+};
diff --git a/src/webextension/api/cookies.c b/src/webextension/api/cookies.c
new file mode 100644
index 000000000..b62d57a22
--- /dev/null
+++ b/src/webextension/api/cookies.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright © 2022 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "ephy-shell.h"
+#include "cookies.h"
+
+static WebKitCookieManager *
+get_cookie_manager (void)
+{
+ WebKitWebContext *web_context = ephy_embed_shell_get_web_context (ephy_embed_shell_get_default ());
+ WebKitWebsiteDataManager *data_manager = webkit_web_context_get_website_data_manager (web_context);
+ return webkit_website_data_manager_get_cookie_manager (data_manager);
+}
+
+static char *
+get_string_property (JSCValue *obj,
+ const char *name)
+{
+ g_autoptr (JSCValue) value = jsc_value_object_get_property (obj, name);
+ if (!jsc_value_is_string (value))
+ return NULL;
+ return jsc_value_to_string (value);
+}
+
+static gint32
+get_int_property (JSCValue *args,
+ const char *name)
+{
+ g_autoptr (JSCValue) value = jsc_value_object_get_property (args, name);
+
+ if (jsc_value_is_undefined (value))
+ return -1;
+
+ return jsc_value_to_int32 (value);
+}
+
+static gboolean
+get_boolean_property (JSCValue *obj,
+ const char *name)
+{
+ g_autoptr (JSCValue) value = jsc_value_object_get_property (obj, name);
+ return jsc_value_to_boolean (value);
+}
+
+
+static const char *
+samesite_to_string (SoupSameSitePolicy policy)
+{
+ switch (policy) {
+ case SOUP_SAME_SITE_POLICY_NONE:
+ return "no_restriction";
+ case SOUP_SAME_SITE_POLICY_LAX:
+ return "lax";
+ case SOUP_SAME_SITE_POLICY_STRICT:
+ return "strict";
+ }
+
+ g_assert_not_reached ();
+ return "no_restriction";
+}
+
+static SoupSameSitePolicy
+string_to_samesite (const char *policy)
+{
+ if (g_strcmp0 (policy, "strict") == 0)
+ return SOUP_SAME_SITE_POLICY_STRICT;
+ if (g_strcmp0 (policy, "lax") == 0)
+ return SOUP_SAME_SITE_POLICY_LAX;
+ return SOUP_SAME_SITE_POLICY_NONE;
+}
+
+static void
+add_cookie_to_json (JsonBuilder *builder,
+ SoupCookie *cookie)
+{
+#if SOUP_CHECK_VERSION (2, 99, 4)
+ GDateTime *expires = soup_cookie_get_expires (cookie);
+#else
+ SoupDate *expires = soup_cookie_get_expires (cookie);
+#endif
+
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "name");
+ json_builder_add_string_value (builder, soup_cookie_get_name (cookie));
+ json_builder_set_member_name (builder, "value");
+ json_builder_add_string_value (builder, soup_cookie_get_value (cookie));
+ json_builder_set_member_name (builder, "domain");
+ json_builder_add_string_value (builder, soup_cookie_get_domain (cookie));
+ json_builder_set_member_name (builder, "path");
+ json_builder_add_string_value (builder, soup_cookie_get_path (cookie));
+ json_builder_set_member_name (builder, "httpOnly");
+ json_builder_add_boolean_value (builder, soup_cookie_get_http_only (cookie));
+ json_builder_set_member_name (builder, "secure");
+ json_builder_add_boolean_value (builder, soup_cookie_get_secure (cookie));
+ json_builder_set_member_name (builder, "sameSite");
+ json_builder_add_string_value (builder, samesite_to_string (soup_cookie_get_same_site_policy (cookie)));
+ if (expires) {
+ json_builder_set_member_name (builder, "expirationDate");
+#if SOUP_CHECK_VERSION (2, 99, 4)
+ json_builder_add_int_value (builder, g_date_time_to_unix (expires));
+#else
+ json_builder_add_int_value (builder, soup_date_to_time_t (expires));
+#endif
+ }
+ json_builder_end_object (builder);
+}
+
+static char *
+cookie_to_json (SoupCookie *cookie)
+{
+ g_autoptr (JsonBuilder) builder = json_builder_new ();
+ g_autoptr (JsonNode) root = NULL;
+
+ add_cookie_to_json (builder, cookie);
+
+ root = json_builder_get_root (builder);
+ return json_to_string (root, FALSE);
+}
+
+typedef struct {
+ GTask *task;
+ char *cookie_name;
+ SoupCookie *cookie;
+ gboolean remove_after_find;
+} CookiesCallbackData;
+
+static void
+cookies_callback_data_free (CookiesCallbackData *data)
+{
+ if (data) {
+ g_clear_pointer (&data->cookie_name, g_free);
+ g_clear_pointer (&data->cookie, soup_cookie_free);
+ g_free (data);
+ }
+}
+
+static SoupCookie *
+compare_best_cookie (SoupCookie *previous,
+ SoupCookie *current)
+{
+ gint64 path_diff;
+
+ /* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/cookies/get
+ * > If more than one cookie with the same name exists for a given URL, the one with the longest path will
be returned.
+ * > For cookies with the same path length, the cookie with the earliest creation time will be returned. */
+
+ if (!previous)
+ return current;
+
+ path_diff = strlen (soup_cookie_get_path (previous)) - strlen (soup_cookie_get_path (current));
+
+ if (path_diff < 0)
+ return current;
+ if (path_diff > 0)
+ return previous;
+
+ /* We don't have a creation date, for now assume earlier in list is older. */
+ return previous;
+}
+
+static void
+delete_cookie_ready_cb (WebKitCookieManager *cookie_manager,
+ GAsyncResult *result,
+ CookiesCallbackData *data)
+{
+ g_autoptr (GError) error = NULL;
+ gboolean success;
+
+ success = webkit_cookie_manager_delete_cookie_finish (cookie_manager, result, &error);
+
+ if (!success) {
+ g_task_return_error (data->task, g_steal_pointer (&error));
+ cookies_callback_data_free (data);
+ return;
+ }
+
+ g_task_return_pointer (data->task, cookie_to_json (data->cookie), g_free);
+ cookies_callback_data_free (data);
+}
+
+static void
+get_cookies_ready_cb (WebKitCookieManager *cookie_manager,
+ GAsyncResult *result,
+ CookiesCallbackData *data)
+{
+ g_autoptr (GError) error = NULL;
+ GList *cookies = webkit_cookie_manager_get_cookies_finish (cookie_manager, result, &error);
+ SoupCookie *best_match = NULL;
+
+ if (error) {
+ g_task_return_error (data->task, g_steal_pointer (&error));
+ cookies_callback_data_free (data);
+ return;
+ }
+
+ for (GList *l = cookies; l; l = g_list_next (l)) {
+ SoupCookie *cookie = l->data;
+ if (strcmp (soup_cookie_get_name (cookie), data->cookie_name) != 0)
+ continue;
+
+ best_match = compare_best_cookie (best_match, cookie);
+ }
+
+ if (!best_match)
+ g_task_return_pointer (data->task, g_strdup ("null"), g_free);
+ else if (!data->remove_after_find)
+ g_task_return_pointer (data->task, cookie_to_json (best_match), g_free);
+ else {
+ data->cookie = soup_cookie_copy (best_match);
+ webkit_cookie_manager_delete_cookie (cookie_manager, data->cookie, NULL,
+ (GAsyncReadyCallback)delete_cookie_ready_cb,
+ data);
+ data = NULL;
+ }
+
+ g_list_free_full (cookies, (GDestroyNotify)soup_cookie_free);
+ cookies_callback_data_free (data);
+}
+
+static void
+cookies_handler_get (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_autoptr (JSCValue) details = jsc_value_object_get_property_at_index (args, 0);
+ WebKitCookieManager *cookie_manager = get_cookie_manager ();
+ g_autofree char *cookie_name = NULL;
+ g_autofree char *url = NULL;
+ CookiesCallbackData *callback_data;
+
+ if (!jsc_value_is_object (details)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.get(): Missing details object");
+ return;
+ }
+
+ cookie_name = get_string_property (details, "name");
+ url = get_string_property (details, "url");
+
+ if (!url || !cookie_name) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.get(): details missing url or name");
+ return;
+ }
+
+ if (!ephy_web_extension_has_host_permission (self, url)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_PERMISSION_DENIED,
"cookies.get(): Permission denied for host '%s'", url);
+ return;
+ }
+
+ callback_data = g_new0 (CookiesCallbackData, 1);
+ callback_data->task = task;
+ callback_data->cookie_name = g_steal_pointer (&cookie_name);
+
+ /* FIXME: The WebKit API doesn't expose details like first-party URLs to better filter this. The
underlying libsoup API does so
+ * this just requires additions to WebKitGTK. */
+ webkit_cookie_manager_get_cookies (cookie_manager, url, NULL, (GAsyncReadyCallback)get_cookies_ready_cb,
callback_data);
+}
+
+static void
+add_cookie_ready_cb (WebKitCookieManager *cookie_manager,
+ GAsyncResult *result,
+ CookiesCallbackData *data)
+{
+ g_autoptr (GError) error = NULL;
+ gboolean success = webkit_cookie_manager_add_cookie_finish (cookie_manager, result, &error);
+
+ if (!success) {
+ g_task_return_error (data->task, g_steal_pointer (&error));
+ cookies_callback_data_free (data);
+ return;
+ }
+
+ g_task_return_pointer (data->task, cookie_to_json (data->cookie), g_free);
+ cookies_callback_data_free (data);
+}
+
+static void
+cookies_handler_set (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_autoptr (JSCValue) details = jsc_value_object_get_property_at_index (args, 0);
+ g_autofree char *url = NULL;
+ g_autofree char *domain = NULL;
+ g_autofree char *cookie_name = NULL;
+ g_autofree char *value = NULL;
+ g_autofree char *path = NULL;
+ g_autofree char *same_site_str = NULL;
+ gboolean secure;
+ gboolean http_only;
+ gint32 expiration;
+ g_autoptr (SoupCookie) new_cookie = NULL;
+ g_autoptr (GUri) parsed_uri = NULL;
+ g_autoptr (GError) error = NULL;
+ WebKitCookieManager *cookie_manager = get_cookie_manager ();
+ CookiesCallbackData *callback_data;
+
+ if (!jsc_value_is_object (details)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.set(): Missing details object");
+ return;
+ }
+
+ url = get_string_property (details, "url");
+ domain = get_string_property (details, "domain");
+ cookie_name = get_string_property (details, "name");
+ value = get_string_property (details, "value");
+ path = get_string_property (details, "path");
+ same_site_str = get_string_property (details, "sameSite");
+ expiration = get_int_property (details, "expirationDate");
+ secure = get_boolean_property (details, "secure");
+ http_only = get_boolean_property (details, "httpOnline");
+
+ if (!url) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.set(): Missing url property");
+ return;
+ }
+
+ if (!ephy_web_extension_has_host_permission (self, url) || (domain &&
!ephy_web_extension_has_host_permission (self, domain))) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_PERMISSION_DENIED,
"cookies.set(): Permission denied for host '%s'", url);
+ return;
+ }
+
+ parsed_uri = g_uri_parse (url, G_URI_FLAGS_ENCODED_PATH | G_URI_FLAGS_ENCODED_QUERY |
G_URI_FLAGS_SCHEME_NORMALIZE, &error);
+ if (error) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.set(): Failed to parse URI '%s': %s", url, error->message);
+ return;
+ }
+
+ new_cookie = soup_cookie_new (cookie_name ? cookie_name : "",
+ value ? value : "",
+ domain ? domain : g_uri_get_host (parsed_uri),
+ path ? path : g_uri_get_path (parsed_uri),
+ expiration);
+ soup_cookie_set_secure (new_cookie, secure);
+ soup_cookie_set_http_only (new_cookie, http_only);
+ soup_cookie_set_same_site_policy (new_cookie, string_to_samesite (same_site_str));
+
+ callback_data = g_new0 (CookiesCallbackData, 1);
+ callback_data->task = task;
+ callback_data->cookie = g_steal_pointer (&new_cookie);
+
+ webkit_cookie_manager_add_cookie (cookie_manager, callback_data->cookie, NULL,
(GAsyncReadyCallback)add_cookie_ready_cb, callback_data);
+}
+
+static void
+cookies_handler_remove (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_autoptr (JSCValue) details = jsc_value_object_get_property_at_index (args, 0);
+ g_autofree char *url = NULL;
+ g_autofree char *cookie_name = NULL;
+ WebKitCookieManager *cookie_manager = get_cookie_manager ();
+ CookiesCallbackData *callback_data;
+
+ if (!jsc_value_is_object (details)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.remove(): Missing details object");
+ return;
+ }
+
+ url = get_string_property (details, "url");
+ cookie_name = get_string_property (details, "name");
+
+ if (!url || !name) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.remove(): Missing url or name property");
+ return;
+ }
+
+ if (!ephy_web_extension_has_host_permission (self, url)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_PERMISSION_DENIED,
"cookies.remove(): Permission denied for host '%s'", url);
+ return;
+ }
+
+ callback_data = g_new0 (CookiesCallbackData, 1);
+ callback_data->task = task;
+ callback_data->cookie_name = g_steal_pointer (&cookie_name);
+ callback_data->remove_after_find = TRUE;
+
+ webkit_cookie_manager_get_cookies (cookie_manager, url, NULL, (GAsyncReadyCallback)get_cookies_ready_cb,
callback_data);
+}
+
+typedef struct {
+ GTask *task;
+ char *domain;
+ char *name;
+ char *path;
+ int secure;
+ int session;
+} GetAllCookiesCallbackData;
+
+static void
+get_all_cookies_callback_data_free (GetAllCookiesCallbackData *data)
+{
+ g_free (data->domain);
+ g_free (data->name);
+ g_free (data->path);
+ g_free (data);
+}
+
+static gboolean
+cookie_matches_filter (SoupCookie *cookie,
+ GetAllCookiesCallbackData *data)
+{
+ if (data->name && strcmp (soup_cookie_get_name (cookie), data->name) != 0)
+ return FALSE;
+ if (data->domain && !soup_cookie_domain_matches (cookie, data->domain))
+ return FALSE;
+ if (data->path && strcmp (soup_cookie_get_path (cookie), data->path) != 0)
+ return FALSE;
+ if (data->secure != -1 && soup_cookie_get_secure (cookie) != data->secure)
+ return FALSE;
+ if (data->session != -1) {
+ gpointer expires = soup_cookie_get_expires (cookie);
+ if (data->session && expires)
+ return FALSE;
+ if (!data->session && !expires)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int
+cookie_compare_func (SoupCookie *a,
+ SoupCookie *b)
+{
+ const char *path_a = soup_cookie_get_path (a);
+ const char *path_b = soup_cookie_get_path (b);
+
+ return strlen (path_b) - strlen (path_a);
+}
+
+static void
+get_all_cookies_ready_cb (WebKitCookieManager *cookie_manager,
+ GAsyncResult *result,
+ GetAllCookiesCallbackData *data)
+{
+ g_autoptr (GError) error = NULL;
+ g_autoptr (JsonBuilder) builder = json_builder_new ();
+ g_autoptr (JsonNode) root = NULL;
+ GList *cookies = webkit_cookie_manager_get_cookies_finish (cookie_manager, result, &error);
+
+ if (error) {
+ g_task_return_error (data->task, g_steal_pointer (&error));
+ get_all_cookies_callback_data_free (data);
+ return;
+ }
+
+ /* Sort by path length. */
+ cookies = g_list_sort (cookies, (GCompareFunc)cookie_compare_func);
+
+ json_builder_begin_array (builder);
+ for (GList *l = cookies; l; l = g_list_next (l)) {
+ SoupCookie *cookie = l->data;
+
+ if (cookie_matches_filter (cookie, data))
+ add_cookie_to_json (builder, cookie);
+ }
+ json_builder_end_array (builder);
+
+ root = json_builder_get_root (builder);
+ g_task_return_pointer (data->task, json_to_string (root, FALSE), g_free);
+
+ g_list_free_full (cookies, (GDestroyNotify)soup_cookie_free);
+ get_all_cookies_callback_data_free (data);
+}
+
+static void
+cookies_handler_get_all (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_autoptr (JSCValue) details = jsc_value_object_get_property_at_index (args, 0);
+ WebKitCookieManager *cookie_manager = get_cookie_manager ();
+ g_autofree char *url = NULL;
+ GetAllCookiesCallbackData *callback_data;
+
+ if (!jsc_value_is_object (details)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.getAll(): Missing details object");
+ return;
+ }
+
+ url = get_string_property (details, "url");
+
+ /* TODO: We can handle the case of no url by using webkit_website_data_manager_fetch() to list all domains
and then get all cookies
+ * for all domains, but this is rather an ugly amount of work compared to libsoup directly. */
+ if (!url) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_INVALID_ARGUMENT,
"cookies.getAll(): details missing url");
+ return;
+ }
+
+ if (!ephy_web_extension_has_host_permission (self, url)) {
+ g_task_return_new_error (task, WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_PERMISSION_DENIED,
"cookies.getAll(): Permission denied for host '%s'", url);
+ return;
+ }
+
+ callback_data = g_new0 (GetAllCookiesCallbackData, 1);
+ callback_data->task = task;
+ callback_data->name = get_string_property (details, "name");
+ callback_data->domain = get_string_property (details, "domain");
+ callback_data->path = get_string_property (details, "path");
+ callback_data->secure = get_int_property (details, "secure");
+ callback_data->session = get_int_property (details, "session");
+
+ /* FIXME: The WebKit API doesn't expose details like first-party URLs to better filter this. The
underlying libsoup API does so
+ * this just requires additions to WebKitGTK. */
+ webkit_cookie_manager_get_cookies (cookie_manager, url, NULL,
(GAsyncReadyCallback)get_all_cookies_ready_cb, callback_data);
+}
+
+static JsonNode *
+create_array_of_all_tab_ids (void)
+{
+ JsonNode *node = json_node_init_array (json_node_alloc (), json_array_new ());
+ JsonArray *array = json_node_get_array (node);
+ GList *windows;
+
+ windows = gtk_application_get_windows (GTK_APPLICATION (ephy_shell_get_default ()));
+ for (GList *win_list = windows; win_list; win_list = g_list_next (win_list)) {
+ EphyWindow *window = EPHY_WINDOW (win_list->data);
+ EphyTabView *tab_view = ephy_window_get_tab_view (window);
+
+ for (int i = 0; i < ephy_tab_view_get_n_pages (tab_view); i++) {
+ EphyWebView *web_view = ephy_embed_get_web_view (EPHY_EMBED (ephy_tab_view_get_nth_page (tab_view,
i)));
+
+ json_array_add_int_element (array, ephy_web_view_get_uid (web_view));
+ }
+ }
+
+ return node;
+}
+
+static void
+cookies_handler_get_all_cookie_stores (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_autoptr (JsonBuilder) builder = json_builder_new ();
+ g_autoptr (JsonNode) root = NULL;
+
+ /* We only have a single store so we create a single object with a list
+ * of every tab. */
+ json_builder_begin_array (builder);
+ json_builder_begin_object (builder);
+ json_builder_set_member_name (builder, "id");
+ json_builder_add_string_value (builder, "default");
+ json_builder_set_member_name (builder, "incognito");
+ json_builder_add_boolean_value (builder, ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) ==
EPHY_EMBED_SHELL_MODE_INCOGNITO);
+ json_builder_set_member_name (builder, "tabIds");
+ json_builder_add_value (builder, create_array_of_all_tab_ids ());
+ json_builder_end_object (builder);
+ json_builder_end_array (builder);
+
+ root = json_builder_get_root (builder);
+ g_task_return_pointer (task, json_to_string (root, FALSE), g_free);
+}
+
+static EphyWebExtensionAsyncApiHandler cookies_async_handlers[] = {
+ {"get", cookies_handler_get},
+ {"getAll", cookies_handler_get_all},
+ {"getAllCookieStores", cookies_handler_get_all_cookie_stores},
+ {"set", cookies_handler_set},
+ {"remove", cookies_handler_remove},
+};
+
+void
+ephy_web_extension_api_cookies_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *args,
+ WebKitWebView *web_view,
+ GTask *task)
+{
+ g_autoptr (GError) error = NULL;
+
+ if (!ephy_web_extension_has_permission (self, "cookies")) {
+ g_warning ("Extension %s tried to use cookies without permission.", ephy_web_extension_get_name (self));
+ error = g_error_new_literal (WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_PERMISSION_DENIED, "Permission
Denied");
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ for (guint idx = 0; idx < G_N_ELEMENTS (cookies_async_handlers); idx++) {
+ EphyWebExtensionAsyncApiHandler handler = cookies_async_handlers[idx];
+
+ if (g_strcmp0 (handler.name, name) == 0) {
+ handler.execute (self, name, args, web_view, task);
+ return;
+ }
+ }
+
+ g_warning ("%s(): '%s' not implemented by Epiphany!", __FUNCTION__, name);
+ error = g_error_new_literal (WEB_EXTENSION_ERROR, WEB_EXTENSION_ERROR_NOT_IMPLEMENTED, "Not Implemented");
+ g_task_return_error (task, g_steal_pointer (&error));
+}
diff --git a/src/webextension/api/cookies.h b/src/webextension/api/cookies.h
new file mode 100644
index 000000000..8c8d1dfdd
--- /dev/null
+++ b/src/webextension/api/cookies.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2022 Igalia S.L.
+ *
+ * This file is part of Epiphany.
+ *
+ * Epiphany is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Epiphany is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "ephy-web-extension.h"
+
+#include <webkit2/webkit2.h>
+
+G_BEGIN_DECLS
+
+void ephy_web_extension_api_cookies_handler (EphyWebExtension *self,
+ char *name,
+ JSCValue *value,
+ WebKitWebView *web_view,
+ GTask *task);
+
+G_END_DECLS
diff --git a/src/webextension/ephy-web-extension-manager.c b/src/webextension/ephy-web-extension-manager.c
index 0b4269b23..9af1d7c97 100644
--- a/src/webextension/ephy-web-extension-manager.c
+++ b/src/webextension/ephy-web-extension-manager.c
@@ -36,6 +36,7 @@
#include "ephy-web-view.h"
#include "api/alarms.h"
+#include "api/cookies.h"
#include "api/notifications.h"
#include "api/pageaction.h"
#include "api/runtime.h"
@@ -66,6 +67,7 @@ G_DEFINE_TYPE (EphyWebExtensionManager, ephy_web_extension_manager, G_TYPE_OBJEC
EphyWebExtensionAsyncApiHandler api_handlers[] = {
{"alarms", ephy_web_extension_api_alarms_handler},
+ {"cookies", ephy_web_extension_api_cookies_handler},
{"notifications", ephy_web_extension_api_notifications_handler},
{"pageAction", ephy_web_extension_api_pageaction_handler},
{"runtime", ephy_web_extension_api_runtime_handler},
diff --git a/src/webextension/ephy-web-extension.c b/src/webextension/ephy-web-extension.c
index bbbb2e012..1af140789 100644
--- a/src/webextension/ephy-web-extension.c
+++ b/src/webextension/ephy-web-extension.c
@@ -1370,6 +1370,22 @@ parse_uri_with_wildcard_scheme (const char *uri,
return g_uri_parse (uri_to_check, G_URI_FLAGS_ENCODED_PATH | G_URI_FLAGS_ENCODED_QUERY |
G_URI_FLAGS_SCHEME_NORMALIZE, error);
}
+static gboolean
+is_default_port (const char *scheme,
+ int port)
+{
+ static const char * const default_port_80[] = { "http", "ws", NULL };
+ static const char * const default_port_443[] = { "https", "wss", NULL };
+
+ switch (port) {
+ case 80:
+ return g_strv_contains (default_port_80, scheme);
+ case 443:
+ return g_strv_contains (default_port_443, scheme);
+ }
+ return FALSE;
+}
+
static gboolean
permission_matches_uri (const char *permission,
GUri *uri)
@@ -1378,10 +1394,8 @@ permission_matches_uri (const char *permission,
g_autoptr (GError) error = NULL;
g_autofree char *permission_path_and_query = NULL;
g_autofree char *uri_path_and_query = NULL;
-
- /* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns#all_urls */
- if (strcmp (permission, "<all_urls>") == 0)
- return is_supported_scheme (g_uri_get_scheme (uri));
+ const char *permission_scheme;
+ int permission_port;
permission_uri = parse_uri_with_wildcard_scheme (permission, &error);
if (error) {
@@ -1389,15 +1403,18 @@ permission_matches_uri (const char *permission,
return FALSE;
}
- /* Ports are forbidden. */
- if (g_uri_get_port (permission_uri) != -1)
+ permission_scheme = g_uri_get_scheme (permission_uri);
+ permission_port = g_uri_get_port (permission_uri);
+
+ /* Ports are forbidden, however GUri normalizes these to the default. */
+ if (permission_port != -1 && !is_default_port (permission_scheme, permission_port))
return FALSE;
/* Empty paths are forbidden. */
if (strcmp (g_uri_get_path (permission_uri), "") == 0)
return FALSE;
- if (!scheme_matches (g_uri_get_scheme (permission_uri), g_uri_get_scheme (uri)))
+ if (!scheme_matches (permission_scheme, g_uri_get_scheme (uri)))
return FALSE;
if (!host_matches (g_uri_get_host (permission_uri), g_uri_get_host (uri)))
@@ -1420,6 +1437,7 @@ ephy_web_extension_has_permission_internal (EphyWebExtension *self,
{
EphyWebView *active_web_view = ephy_shell_get_active_web_view (ephy_shell_get_default ());
gboolean is_active_tab = active_web_view == web_view;
+ GUri *host;
/* https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns */
@@ -1434,12 +1452,10 @@ ephy_web_extension_has_permission_internal (EphyWebExtension *self,
}
/* Note this one is NULL terminated. */
+ host = g_uri_parse (ephy_web_view_get_address (web_view), G_URI_FLAGS_ENCODED_PATH |
G_URI_FLAGS_ENCODED_QUERY | G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
+ g_assert (host); /* WebKitGTK shouldn't ever expose an invalid URI. */
for (guint i = 0; i < self->host_permissions->len - 1; i++) {
- GUri *host = g_uri_parse (ephy_web_view_get_address (web_view), G_URI_FLAGS_ENCODED_PATH |
G_URI_FLAGS_ENCODED_QUERY | G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
const char *permission = g_ptr_array_index (self->host_permissions, i);
-
- g_assert (host);
-
if (permission_matches_uri (permission, host))
return TRUE;
}
@@ -1463,6 +1479,24 @@ ephy_web_extension_has_tab_or_host_permission (EphyWebExtension *self,
return ephy_web_extension_has_permission_internal (self, web_view, is_user_interaction, TRUE);
}
+gboolean
+ephy_web_extension_has_host_permission (EphyWebExtension *self,
+ const char *host)
+{
+ GUri *uri = g_uri_parse (host, G_URI_FLAGS_ENCODED_PATH | G_URI_FLAGS_ENCODED_QUERY |
G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
+ if (!uri)
+ return FALSE;
+
+ for (guint i = 0; i < self->host_permissions->len - 1; i++) {
+ const char *permission = g_ptr_array_index (self->host_permissions, i);
+ if (permission_matches_uri (permission, uri))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
gboolean
ephy_web_extension_has_permission (EphyWebExtension *self,
const char *permission)
diff --git a/src/webextension/ephy-web-extension.h b/src/webextension/ephy-web-extension.h
index 78cd1e557..9840d05cb 100644
--- a/src/webextension/ephy-web-extension.h
+++ b/src/webextension/ephy-web-extension.h
@@ -159,6 +159,9 @@ gboolean ephy_web_extension_has_host_or_active_permission (EphyW
gboolean ephy_web_extension_has_permission (EphyWebExtension *self,
const char *permission);
+gboolean ephy_web_extension_has_host_permission (EphyWebExtension *self,
+ const char *host);
+
const char * const *ephy_web_extension_get_host_permissions (EphyWebExtension *self);
JsonNode *ephy_web_extension_get_local_storage (EphyWebExtension *self);
diff --git a/src/webextension/meson.build b/src/webextension/meson.build
index 80ee092e6..ae9cedce0 100644
--- a/src/webextension/meson.build
+++ b/src/webextension/meson.build
@@ -1,5 +1,6 @@
ephywebextension_src = [
'webextension/api/alarms.c',
+ 'webextension/api/cookies.c',
'webextension/api/notifications.c',
'webextension/api/pageaction.c',
'webextension/api/runtime.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]