From 8e99a57a1d9fe0d1715f18a31ce64508e4ede254 Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Tue, 21 Jan 2025 08:36:38 +0100 Subject: [PATCH] Update to 1.19.2 Resolves: RHEL-74556 --- .gitignore | 1 + sources | 2 +- xdg-desktop-portal-webextensions.patch | 1986 ----------------- xdg-desktop-portal.spec | 8 +- ...rmission-check-in-openpipewireremote.patch | 81 - 5 files changed, 3 insertions(+), 2075 deletions(-) delete mode 100644 xdg-desktop-portal-webextensions.patch delete mode 100644 xdp-fix-permission-check-in-openpipewireremote.patch diff --git a/.gitignore b/.gitignore index aef7f3b..eb92b09 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ /xdg-desktop-portal-1.18.2.tar.xz /xdg-desktop-portal-1.18.4.tar.xz /xdg-desktop-portal-1.19.1.tar.xz +/xdg-desktop-portal-1.19.2.tar.xz diff --git a/sources b/sources index c9d8fed..322a2b5 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (xdg-desktop-portal-1.19.1.tar.xz) = c480cdc6fbbdeedbfe0e37ac463b6afa9127efa1b3e6c665c28e272c23f5d41e6f3f0e8e3c69c31d0829032a009bcf04643ba7f782332a3eccf591eca5c6dff4 +SHA512 (xdg-desktop-portal-1.19.2.tar.xz) = 747b4788da5eb9db765389d20b70c8bd90e33dea5763888f09c7e031c92640cd4d231b925d61fe62a446040b6bc1ba3c2ca8e11a20e18585ddf0c4ebb8303864 diff --git a/xdg-desktop-portal-webextensions.patch b/xdg-desktop-portal-webextensions.patch deleted file mode 100644 index e45f82c..0000000 --- a/xdg-desktop-portal-webextensions.patch +++ /dev/null @@ -1,1986 +0,0 @@ -From b59c90fbd7bfd23bca1ce866e3c88e93fc678381 Mon Sep 17 00:00:00 2001 -From: Jan Horak -Date: Tue, 17 Dec 2024 17:18:33 +0100 -Subject: [PATCH] webextensions: add a portal for managing WebExtensions native - messaging servers. - -Rebase, fix and continue work on webextensions: add a portal for managing WebExtensions -native messaging servers: -https://github.com/flatpak/xdg-desktop-portal/pull/705 - -This commit builds on the work done in the original MR authored by @jhenstridge -but resolves pending items and brings it closer to completion. - -This is intended to provide a way for a confined web browser to start -native code helpers for their extensions. At present it can start the -servers installed on the host system. But in future this could be -extended to cover sandboxed native messaging servers too. ---- - data/meson.build | 1 + - data/org.freedesktop.portal.WebExtensions.xml | 158 ++++ - meson.build | 1 + - po/POTFILES.in | 1 + - src/meson.build | 1 + - src/web-extensions.c | 875 ++++++++++++++++++ - src/web-extensions.h | 24 + - src/xdg-desktop-portal.c | 4 + - src/xdp-request.c | 12 + - tests/meson.build | 3 + - tests/native-messaging-hosts/meson.build | 28 + - .../org.example.testing.json.in | 9 + - tests/native-messaging-hosts/server.sh | 3 + - tests/test-portals.c | 7 + - tests/web-extensions.c | 632 +++++++++++++ - tests/web-extensions.h | 2 + - 16 files changed, 1761 insertions(+) - create mode 100644 data/org.freedesktop.portal.WebExtensions.xml - create mode 100644 src/web-extensions.c - create mode 100644 src/web-extensions.h - create mode 100644 tests/native-messaging-hosts/meson.build - create mode 100644 tests/native-messaging-hosts/org.example.testing.json.in - create mode 100755 tests/native-messaging-hosts/server.sh - create mode 100644 tests/web-extensions.c - create mode 100644 tests/web-extensions.h - -diff --git a/data/meson.build b/data/meson.build -index 66ad68d16..11c36a053 100644 ---- a/data/meson.build -+++ b/data/meson.build -@@ -37,6 +37,7 @@ portal_sources = files( - 'org.freedesktop.portal.Trash.xml', - 'org.freedesktop.portal.Usb.xml', - 'org.freedesktop.portal.Wallpaper.xml', -+ 'org.freedesktop.portal.WebExtensions.xml', - ) - - portal_impl_sources = files( -diff --git a/data/org.freedesktop.portal.WebExtensions.xml b/data/org.freedesktop.portal.WebExtensions.xml -new file mode 100644 -index 000000000..2725c0234 ---- /dev/null -+++ b/data/org.freedesktop.portal.WebExtensions.xml -@@ -0,0 +1,158 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/meson.build b/meson.build -index 876a531a1..55342e2a6 100644 ---- a/meson.build -+++ b/meson.build -@@ -82,6 +82,7 @@ config_h = configuration_data() - config_h.set('_GNU_SOURCE', 1) - config_h.set_quoted('G_LOG_DOMAIN', 'xdg-desktop-portal') - config_h.set_quoted('DATADIR', datadir) -+config_h.set_quoted('LIBDIR', libdir) - config_h.set_quoted('LIBEXECDIR', libexecdir) - config_h.set_quoted('LOCALEDIR', localedir) - config_h.set_quoted('SYSCONFDIR', sysconfdir) -diff --git a/po/POTFILES.in b/po/POTFILES.in -index d514b2167..fd3232518 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -7,3 +7,4 @@ src/screenshot.c - src/settings.c - src/usb.c - src/wallpaper.c -+src/web-extensions.c -diff --git a/src/meson.build b/src/meson.build -index 5b6d553b6..e306b3eab 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -96,6 +96,7 @@ xdg_desktop_portal_sources = files( - 'settings.c', - 'trash.c', - 'wallpaper.c', -+ 'web-extensions.c', - 'xdg-desktop-portal.c', - 'xdp-app-launch-context.c', - 'xdp-background-monitor.c', -diff --git a/src/web-extensions.c b/src/web-extensions.c -new file mode 100644 -index 000000000..a33fee3e1 ---- /dev/null -+++ b/src/web-extensions.c -@@ -0,0 +1,875 @@ -+/* -+ * Copyright © 2022 Canonical Ltd -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2 of the License, or (at your option) any later version. -+ * -+ * This library 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 -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with this library. If not, see . -+ * -+ */ -+ -+#include "config.h" -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "gio/gdesktopappinfo.h" -+#include "gio/gio.h" -+#include "glib.h" -+#include "xdp-session.h" -+#include "web-extensions.h" -+#include "xdp-request.h" -+#include "xdp-permissions.h" -+#include "xdp-dbus.h" -+#include "xdp-impl-dbus.h" -+#include "xdp-utils.h" -+#include "xdp-app-info.h" -+ -+#define PERMISSION_TABLE "webextensions" -+ -+typedef struct _WebExtensions WebExtensions; -+typedef struct _WebExtensionsClass WebExtensionsClass; -+ -+struct _WebExtensions -+{ -+ XdpDbusWebExtensionsSkeleton parent_instance; -+}; -+ -+struct _WebExtensionsClass -+{ -+ XdpDbusWebExtensionsSkeletonClass parent_class; -+}; -+ -+static XdpDbusImplAccess *access_impl; -+static WebExtensions *web_extensions; -+ -+GType web_extensions_get_type (void); -+static void web_extensions_iface_init (XdpDbusWebExtensionsIface *iface); -+ -+G_DEFINE_TYPE_WITH_CODE (WebExtensions, web_extensions, XDP_DBUS_TYPE_WEB_EXTENSIONS_SKELETON, -+ G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_WEB_EXTENSIONS, -+ web_extensions_iface_init)); -+ -+typedef enum _WebExtensionsSessionMode -+{ -+ WEB_EXTENSIONS_SESSION_MODE_CHROMIUM, -+ WEB_EXTENSIONS_SESSION_MODE_MOZILLA, -+} WebExtensionsSessionMode; -+ -+typedef enum _WebExtensionsSessionState -+{ -+ WEB_EXTENSIONS_SESSION_STATE_INIT, -+ WEB_EXTENSIONS_SESSION_STATE_STARTING, -+ WEB_EXTENSIONS_SESSION_STATE_STARTED, -+ WEB_EXTENSIONS_SESSION_STATE_CLOSED, -+} WebExtensionsSessionState; -+ -+typedef struct _WebExtensionsSession -+{ -+ XdpSession parent; -+ -+ WebExtensionsSessionMode mode; -+ WebExtensionsSessionState state; -+ -+ GPid child_pid; -+ guint child_watch_id; -+ -+ int standard_input; -+ int standard_output; -+ int standard_error; -+} WebExtensionsSession; -+ -+typedef struct _WebExtensionsSessionClass -+{ -+ XdpSessionClass parent_class; -+} WebExtensionsSessionClass; -+ -+GType web_extensions_session_get_type (void); -+ -+G_DEFINE_TYPE (WebExtensionsSession, web_extensions_session, xdp_session_get_type ()); -+ -+static void -+web_extensions_session_init (WebExtensionsSession *session) -+{ -+ session->child_pid = -1; -+ session->child_watch_id = 0; -+ -+ session->standard_input = -1; -+ session->standard_output = -1; -+ session->standard_error = -1; -+} -+ -+static void -+web_extensions_session_close (XdpSession *session) -+{ -+ WebExtensionsSession *web_extensions_session = (WebExtensionsSession *)session; -+ -+ /* This function can be called repeatedly, e.g. by an explicit -+ "org.freedesktop.portal.Session::Close" message followed by -+ a call to finalize due to session's refcount reaching zero. -+ */ -+ if (web_extensions_session->state == WEB_EXTENSIONS_SESSION_STATE_CLOSED) return; -+ -+ /* We can assume that it is safe to transition to -+ WEB_EXTENSIONS_SESSION_STATE_CLOSED here, because we arrive -+ at web_extensions_session_close via one of two ways: -+ -+ 1. via the session_class->close function pointer from the -+ session_close function in src/session.c, which expects -+ its caller to lock the session's mutex (usually via the -+ SESSION_AUTOLOCK_UNREF macro); or -+ 2. from web_extensions_session_finalize, at which point -+ the last reference to the session has been released. -+ */ -+ web_extensions_session->state = WEB_EXTENSIONS_SESSION_STATE_CLOSED; -+ -+ if (web_extensions_session->child_watch_id != 0) -+ { -+ g_source_remove (web_extensions_session->child_watch_id); -+ web_extensions_session->child_watch_id = 0; -+ } -+ -+ if (web_extensions_session->child_pid > 0) -+ { -+ /* The responsibility of gracefully killing the process is -+ delegated to the browser, and the following SIGKILL is -+ a final attempt to clean up if necessary. -+ */ -+ kill (web_extensions_session->child_pid, SIGKILL); -+ waitpid (web_extensions_session->child_pid, NULL, 0); -+ g_spawn_close_pid (web_extensions_session->child_pid); -+ web_extensions_session->child_pid = -1; -+ } -+ -+ if (web_extensions_session->standard_input >= 0) -+ { -+ close (web_extensions_session->standard_input); -+ web_extensions_session->standard_input = -1; -+ } -+ if (web_extensions_session->standard_output >= 0) -+ { -+ close (web_extensions_session->standard_output); -+ web_extensions_session->standard_output = -1; -+ } -+ if (web_extensions_session->standard_error >= 0) -+ { -+ close (web_extensions_session->standard_error); -+ web_extensions_session->standard_error = -1; -+ } -+} -+ -+static void -+web_extensions_session_finalize (GObject *object) -+{ -+ XdpSession *session = (XdpSession *)object; -+ -+ web_extensions_session_close (session); -+ G_OBJECT_CLASS (web_extensions_session_parent_class)->finalize (object); -+} -+ -+static void -+web_extensions_session_class_init (WebExtensionsSessionClass *klass) -+{ -+ /* Called at the first instantiation of WebExtensionsSession, -+ i.e. in web_extensions_session_new with the call to -+ g_initable_new. -+ https://docs.gtk.org/gobject/concepts.html#object-instantiation -+ https://docs.gtk.org/gio/type_func.Initable.new.html -+ */ -+ GObjectClass *object_class; -+ XdpSessionClass *session_class; -+ -+ object_class = G_OBJECT_CLASS (klass); -+ /* finalize is called when the session refcount reaches zero. -+ https://docs.gtk.org/gobject/concepts.html#reference-count -+ */ -+ object_class->finalize = web_extensions_session_finalize; -+ -+ session_class = (XdpSessionClass *)klass; -+ /* Register handler for org.freedesktop.portal.Session::Close */ -+ session_class->close = web_extensions_session_close; -+} -+ -+static WebExtensionsSession * -+web_extensions_session_new (GVariant *options, -+ XdpCall *call, -+ GDBusConnection *connection, -+ GError **error) -+{ -+ XdpSession *session; -+ WebExtensionsSession *web_extensions_session; -+ WebExtensionsSessionMode mode = WEB_EXTENSIONS_SESSION_MODE_MOZILLA; -+ const char *mode_str = NULL; -+ const char *session_token; -+ -+ g_variant_lookup (options, "mode", "&s", &mode_str); -+ if (mode_str != NULL) -+ { -+ if (!strcmp(mode_str, "chromium")) -+ mode = WEB_EXTENSIONS_SESSION_MODE_CHROMIUM; -+ else if (!strcmp(mode_str, "mozilla")) -+ mode = WEB_EXTENSIONS_SESSION_MODE_MOZILLA; -+ else -+ { -+ g_set_error (error, -+ XDG_DESKTOP_PORTAL_ERROR, -+ XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, -+ "Invalid mode"); -+ return NULL; -+ } -+ } -+ -+ session_token = lookup_session_token (options); -+ session = g_initable_new (web_extensions_session_get_type (), NULL, error, -+ "sender", call->sender, -+ "app-id", xdp_app_info_get_id (call->app_info), -+ "token", session_token, -+ "connection", connection, -+ NULL); -+ -+ if (session) -+ g_debug ("webextensions session owned by '%s' created", session->sender); -+ -+ if (!session) -+ { -+ g_warning ("Could not create WebExtensions session: %s", (*error)->message); -+ return NULL; -+ } -+ web_extensions_session = (WebExtensionsSession *)session; -+ web_extensions_session->mode = mode; -+ return web_extensions_session; -+} -+ -+static gboolean -+handle_create_session (XdpDbusWebExtensions *object, -+ GDBusMethodInvocation *invocation, -+ GVariant *arg_options) -+{ -+ XdpCall *call = xdp_call_from_invocation (invocation); -+ GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation); -+ g_autoptr(GError) error = NULL; -+ XdpSession *session; -+ -+ session = (XdpSession *)web_extensions_session_new (arg_options, call, connection, &error); -+ if (!session) -+ { -+ g_dbus_method_invocation_return_gerror (invocation, error); -+ return TRUE; -+ } -+ if (!xdp_session_export (session, &error)) -+ { -+ g_dbus_method_invocation_return_gerror (invocation, error); -+ xdp_session_close (session, FALSE); -+ return TRUE; -+ } -+ xdp_session_register (session); -+ -+ xdp_dbus_web_extensions_complete_create_session (object, invocation, session->id); -+ -+ return TRUE; -+} -+ -+static void -+on_host_exited (GPid pid, -+ gint status, -+ gpointer user_data) -+{ -+ XdpSession *session = user_data; -+ WebExtensionsSession *web_extensions_session = (WebExtensionsSession *)session; -+ -+ SESSION_AUTOLOCK (session); -+ web_extensions_session->child_pid = -1; -+ web_extensions_session->child_watch_id = 0; -+ xdp_session_close (session, TRUE); -+} -+ -+static gboolean -+array_contains (JsonArray *array, -+ const char *value) -+{ -+ guint length, i; -+ -+ if (array == NULL) -+ return FALSE; -+ -+ length = json_array_get_length (array); -+ for (i = 0; i < length; i++) -+ { -+ const char *element = json_array_get_string_element (array, i); -+ if (g_strcmp0 (element, value) == 0) -+ return TRUE; -+ } -+ return FALSE; -+} -+ -+static gboolean -+is_valid_name (const char *name) -+{ -+ /* This regexp comes from the Mozilla documentation on valid native -+ messaging host names: -+ -+ https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#native_messaging_manifests -+ -+ That is, one or more dot-separated groups composed of -+ alphanumeric characters and underscores. -+ */ -+ return g_regex_match_simple ("^\\w+(\\.\\w+)*$", name, 0, 0); -+} -+ -+static GStrv -+get_manifest_search_path (WebExtensionsSessionMode mode) -+{ -+ /* IMPORTANT: -+ The safety model depends on the inability of the sandboxed -+ browser to write to the search locations specified below. -+ -+ As this portal allows browser extensions to run a native -+ messaging application outside of the sandbox through the portal, -+ the sandboxing mechanism must ensure that these locations are -+ inaccessible to the browser. If the locations are both readable -+ AND writable by the sandboxed browser, then a vulnerability -+ resulting in a file writing primitive within the sandbox could -+ result in arbitrary code execution outside of the sandbox through -+ the portal. -+ -+ For example, the Firefox Snap package meets this criterion, -+ because all strictly confined Snap packages (including Firefox) -+ are prohibited by AppArmor from accessing most directories and -+ files in the user's home directory, except where explicitly -+ specified, for instance using the 'personal-files' interface. -+ https://snapcraft.io/docs/personal-files-interface -+ */ -+ const char *hosts_path_str; -+ g_autoptr(GPtrArray) search_path = NULL; -+ -+ hosts_path_str = g_getenv ("XDG_DESKTOP_PORTAL_WEB_EXTENSIONS_PATH"); -+ if (hosts_path_str != NULL) -+ return g_strsplit (hosts_path_str, ":", -1); -+ -+ search_path = g_ptr_array_new_with_free_func (g_free); -+ switch (mode) -+ { -+ case WEB_EXTENSIONS_SESSION_MODE_CHROMIUM: -+ /* Chrome and Chromium search paths documented here: -+ * https://developer.chrome.com/docs/extensions/nativeMessaging/#native-messaging-host-location -+ */ -+ /* Add per-user directories */ -+ g_ptr_array_add (search_path, g_build_filename (g_get_user_config_dir (), "google-chrome", "NativeMessagingHosts", NULL)); -+ g_ptr_array_add (search_path, g_build_filename (g_get_user_config_dir (), "chromium", "NativeMessagingHosts", NULL)); -+ /* Add system wide directories */ -+ g_ptr_array_add (search_path, g_strdup ("/etc/opt/chrome/native-messaging-hosts")); -+ g_ptr_array_add (search_path, g_strdup ("/etc/chromium/native-messaging-hosts")); -+ /* And the same for xdg-desktop-portal's configured prefix */ -+ g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "/opt/chrome/native-messaging-hosts")); -+ g_ptr_array_add (search_path, g_strdup (SYSCONFDIR "/chromium/native-messaging-hosts")); -+ break; -+ -+ case WEB_EXTENSIONS_SESSION_MODE_MOZILLA: -+ /* Firefox search paths documented here: -+ * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location -+ */ -+ /* Add per-user directories */ -+ g_ptr_array_add (search_path, g_build_filename (g_get_home_dir (), ".mozilla", "native-messaging-hosts", NULL)); -+ g_ptr_array_add (search_path, g_build_filename (g_get_user_config_dir (), "mozilla", "native-messaging-hosts", NULL)); -+ /* Add system wide directories */ -+ g_ptr_array_add (search_path, g_strdup ("/usr/lib/mozilla/native-messaging-hosts")); -+ g_ptr_array_add (search_path, g_strdup ("/usr/lib64/mozilla/native-messaging-hosts")); -+ /* And the same for xdg-desktop-portal's configured prefix. -+ This is helpful on Debian-based systems where LIBDIR is -+ suffixed with 'dpkg-architecture -qDEB_HOST_MULTIARCH', -+ e.g. '/usr/lib/x86_64-linux-gnu'. -+ https://salsa.debian.org/debian/debhelper/-/blob/5b96b19b456fe5e094f2870327a753b4b3ece0dc/lib/Debian/Debhelper/Buildsystem/meson.pm#L78 -+ */ -+ g_ptr_array_add (search_path, g_strdup (LIBDIR "/mozilla/native-messaging-hosts")); -+ break; -+ } -+ -+ g_ptr_array_add (search_path, NULL); -+ return (GStrv)g_ptr_array_free (g_steal_pointer (&search_path), FALSE); -+} -+ -+static char * -+find_messaging_host (WebExtensionsSessionMode mode, -+ const char *messaging_host_name, -+ const char *extension_or_origin, -+ char **out_description, -+ char **out_manifest_filename, -+ char **out_json_manifest, -+ GError **error) -+{ -+ g_auto(GStrv) search_path = NULL; -+ g_autoptr(JsonParser) parser = NULL; -+ g_autofree char *metadata_basename = NULL; -+ int i; -+ -+ /* Check that the we have a valid native messaging host name */ -+ if (!is_valid_name (messaging_host_name)) -+ { -+ g_set_error (error, -+ XDG_DESKTOP_PORTAL_ERROR, -+ XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT, -+ "Invalid native messaging host name"); -+ return NULL; -+ } -+ -+ search_path = get_manifest_search_path (mode); -+ parser = json_parser_new (); -+ metadata_basename = g_strconcat (messaging_host_name, ".json", NULL); -+ -+ for (i = 0; search_path[i] != NULL; i++) -+ { -+ g_autofree char *metadata_filename = NULL; -+ g_autoptr(GError) load_error = NULL; -+ JsonObject *metadata_root; -+ const char *host_path; -+ -+ metadata_filename = g_build_filename (search_path[i], metadata_basename, NULL); -+ if (!json_parser_load_from_file (parser, metadata_filename, &load_error)) -+ { -+ /* If the file doesn't exist, continue searching. Error out -+ on anything else. */ -+ if (g_error_matches (load_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) -+ continue; -+ g_propagate_error (error, g_steal_pointer (&load_error)); -+ return NULL; -+ } -+ -+ metadata_root = json_node_get_object (json_parser_get_root (parser)); -+ -+ /* Skip if metadata contains an unexpected name */ -+ if (g_strcmp0 (json_object_get_string_member (metadata_root, "name"), messaging_host_name) != 0) -+ continue; -+ -+ /* Skip if this is not a "stdio" type native messaging host */ -+ if (g_strcmp0 (json_object_get_string_member (metadata_root, "type"), "stdio") != 0) -+ continue; -+ -+ /* Skip if this host isn't available to the extension. Note -+ * that this ID is provided by the sandboxed browser, so this -+ * check is just to help implement its security policy. */ -+ switch (mode) -+ { -+ case WEB_EXTENSIONS_SESSION_MODE_CHROMIUM: -+ if (!array_contains (json_object_get_array_member (metadata_root, "allowed_origins"), extension_or_origin)) -+ continue; -+ break; -+ case WEB_EXTENSIONS_SESSION_MODE_MOZILLA: -+ if (!array_contains (json_object_get_array_member (metadata_root, "allowed_extensions"), extension_or_origin)) -+ continue; -+ break; -+ } -+ -+ host_path = json_object_get_string_member (metadata_root, "path"); -+ if (!g_path_is_absolute (host_path)) -+ { -+ g_set_error (error, -+ XDG_DESKTOP_PORTAL_ERROR, -+ XDG_DESKTOP_PORTAL_ERROR_FAILED, -+ "Native messaging host path is not absolute"); -+ return NULL; -+ } -+ -+ /* Host matches: return its executable path and description */ -+ if (out_description != NULL) -+ *out_description = g_strdup (json_object_get_string_member (metadata_root, "description")); -+ if (out_manifest_filename != NULL) -+ *out_manifest_filename = g_steal_pointer (&metadata_filename); -+ if (out_json_manifest != NULL) -+ *out_json_manifest = json_to_string (json_parser_get_root (parser), FALSE); -+ return g_strdup (host_path); -+ } -+ -+ g_set_error (error, -+ XDG_DESKTOP_PORTAL_ERROR, -+ XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND, -+ "Could not find native messaging host"); -+ return NULL; -+} -+ -+static gboolean -+handle_get_manifest (XdpDbusWebExtensions *object, -+ GDBusMethodInvocation *invocation, -+ const char *arg_session_handle, -+ const char *arg_name, -+ const char *arg_extension_or_origin) -+{ -+ XdpCall *call = xdp_call_from_invocation (invocation); -+ XdpSession *session; -+ WebExtensionsSession *web_extensions_session; -+ g_autofree char *host_path = NULL; -+ g_autofree char *json_manifest = NULL; -+ g_autoptr(GError) error = NULL; -+ -+ session = xdp_session_from_call (arg_session_handle, call); -+ if (!session) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_ACCESS_DENIED, -+ "Invalid session"); -+ return TRUE; -+ } -+ -+ SESSION_AUTOLOCK_UNREF (session); -+ web_extensions_session = (WebExtensionsSession *)session; -+ -+ if (web_extensions_session->state != WEB_EXTENSIONS_SESSION_STATE_INIT) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_FAILED, -+ "Session already started"); -+ return TRUE; -+ } -+ -+ host_path = find_messaging_host (web_extensions_session->mode, -+ arg_name, arg_extension_or_origin, -+ NULL, NULL, &json_manifest, &error); -+ if (!host_path) -+ { -+ g_dbus_method_invocation_return_gerror (invocation, error); -+ return TRUE; -+ } -+ -+ xdp_dbus_web_extensions_complete_get_manifest (object, invocation, json_manifest); -+ return TRUE; -+} -+ -+static void -+handle_start_in_thread (GTask *task, -+ gpointer source_object, -+ gpointer task_data, -+ GCancellable *cancellable) -+{ -+ XdpRequest *request = (XdpRequest *)task_data; -+ XdpSession *session; -+ WebExtensionsSession *web_extensions_session; -+ const char *arg_name; -+ char *arg_extension_or_origin; -+ const char *app_id; -+ g_autofree char *host_path = NULL; -+ g_autofree char *description = NULL; -+ g_autofree char *manifest_filename = NULL; -+ guint response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER; -+ gboolean should_close_session; -+ XdpPermission permission; -+ gboolean allowed; -+ char *argv[] = {NULL, NULL, NULL, NULL}; -+ g_autoptr(GError) error = NULL; -+ -+ REQUEST_AUTOLOCK (request); -+ session = g_object_get_data (G_OBJECT (request), "session"); -+ SESSION_AUTOLOCK_UNREF (g_object_ref (session)); -+ g_object_set_data (G_OBJECT (request), "session", NULL); -+ web_extensions_session = (WebExtensionsSession *)session; -+ -+ if (!request->exported || web_extensions_session->state != WEB_EXTENSIONS_SESSION_STATE_STARTING) -+ goto out; -+ -+ arg_name = g_object_get_data (G_OBJECT (request), "name"); -+ arg_extension_or_origin = g_object_get_data (G_OBJECT (request), "extension-or-origin"); -+ -+ host_path = find_messaging_host (web_extensions_session->mode, -+ arg_name, arg_extension_or_origin, -+ &description, &manifest_filename, NULL, -+ &error); -+ if (host_path == NULL) -+ { -+ g_warning ("Could not find WebExtensions backend: %s", error->message); -+ fflush(stderr); -+ fflush(stdout); -+ goto out; -+ } -+ -+ app_id = xdp_app_info_get_id (request->app_info); -+ permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, arg_name); -+ if (permission == XDP_PERMISSION_ASK || permission == XDP_PERMISSION_UNSET) -+ { -+ guint access_response = 2; -+ g_autoptr(GVariant) access_results = NULL; -+ GVariantBuilder opt_builder; -+ const char *display_name; -+ g_autofree gchar *app_info_id = NULL; -+ g_autofree gchar *title = NULL; -+ g_autofree gchar *subtitle = NULL; -+ g_autofree gchar *body = NULL; -+ GAppInfo* info = xdp_app_info_get_gappinfo (request->app_info); -+ -+ if (info) -+ { -+ g_auto(GStrv) app_id_components = g_strsplit (g_app_info_get_id (info), ".desktop", 2); -+ app_info_id = g_strdup (app_id_components[0]); -+ } -+ display_name = info ? g_app_info_get_display_name (info) : app_id; -+ title = g_strdup_printf (_("Allow %s to start WebExtension backend?"), display_name); -+ subtitle = g_strdup_printf (_("%s is requesting to launch \"%s\" (%s)."), display_name, description, arg_name); -+ body = g_strdup (_("This permission can be changed at any time from the privacy settings.")); -+ -+ g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT); -+ g_variant_builder_add (&opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Don't allow"))); -+ g_variant_builder_add (&opt_builder, "{sv}", "grant_label", g_variant_new_string (_("Allow"))); -+ if (!xdp_dbus_impl_access_call_access_dialog_sync (access_impl, -+ request->id, -+ app_info_id ? app_info_id : app_id, -+ "", -+ title, -+ subtitle, -+ body, -+ g_variant_builder_end (&opt_builder), -+ &access_response, -+ &access_results, -+ cancellable, -+ &error)) -+ { -+ g_warning ("AccessDialog call failed: %s", error->message); -+ g_clear_error (&error); -+ } -+ allowed = access_response == 0; -+ -+ // access_response == 2 means dialog has been closed -+ if (permission == XDP_PERMISSION_UNSET && access_response != 2) -+ xdp_set_permission_sync (app_id, PERMISSION_TABLE, arg_name, allowed ? XDP_PERMISSION_YES : XDP_PERMISSION_NO); -+ } -+ else -+ { -+ allowed = permission == XDP_PERMISSION_YES ? TRUE : FALSE; -+ } -+ -+ if (!allowed) -+ { -+ response = XDG_DESKTOP_PORTAL_RESPONSE_CANCELLED; -+ goto out; -+ } -+ -+ argv[0] = host_path; -+ switch (web_extensions_session->mode) -+ { -+ case WEB_EXTENSIONS_SESSION_MODE_CHROMIUM: -+ /* Pass the origin -+ https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging -+ */ -+ argv[1] = arg_extension_or_origin; -+ break; -+ case WEB_EXTENSIONS_SESSION_MODE_MOZILLA: -+ /* Pass the manifest filename and extension ID -+ https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_messaging -+ https://searchfox.org/mozilla-central/rev/9fcc11127fbfbdc88cbf37489dac90542e141c77/toolkit/components/extensions/NativeMessaging.sys.mjs#104-110 -+ */ -+ argv[1] = manifest_filename; -+ argv[2] = arg_extension_or_origin; -+ break; -+ } -+ if (!g_spawn_async_with_pipes (NULL, /* working_directory */ -+ argv, -+ NULL, /* envp */ -+ G_SPAWN_DO_NOT_REAP_CHILD, -+ NULL, /* child_setup */ -+ NULL, /* user_data */ -+ &web_extensions_session->child_pid, -+ &web_extensions_session->standard_input, -+ &web_extensions_session->standard_output, -+ &web_extensions_session->standard_error, -+ &error)) -+ { -+ web_extensions_session->child_pid = -1; -+ goto out; -+ } -+ -+ web_extensions_session->child_watch_id = g_child_watch_add_full (G_PRIORITY_DEFAULT, -+ web_extensions_session->child_pid, -+ on_host_exited, -+ g_object_ref (web_extensions_session), -+ g_object_unref); -+ web_extensions_session->state = WEB_EXTENSIONS_SESSION_STATE_STARTED; -+ -+ response = XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS; -+ -+out: -+ should_close_session = !request->exported || response != XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS; -+ -+ if (request->exported) -+ { -+ GVariantBuilder results; -+ -+ g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT); -+ xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results)); -+ xdp_request_unexport (request); -+ } -+ -+ if (should_close_session) -+ xdp_session_close (session, TRUE); -+} -+ -+static gboolean -+handle_start (XdpDbusWebExtensions *object, -+ GDBusMethodInvocation *invocation, -+ const char *arg_session_handle, -+ const char *arg_name, -+ const char *arg_extension_or_origin, -+ GVariant *arg_options) -+{ -+ XdpRequest *request = xdp_request_from_invocation (invocation); -+ XdpSession *session; -+ WebExtensionsSession *web_extensions_session; -+ g_autoptr(GTask) task = NULL; -+ -+ REQUEST_AUTOLOCK (request); -+ -+ session = xdp_session_from_request (arg_session_handle, request); -+ if (!session) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_ACCESS_DENIED, -+ "Invalid session"); -+ return TRUE; -+ } -+ -+ SESSION_AUTOLOCK_UNREF (session); -+ web_extensions_session = (WebExtensionsSession *)session; -+ -+ if (web_extensions_session->state != WEB_EXTENSIONS_SESSION_STATE_INIT) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_FAILED, -+ "Session already started"); -+ return TRUE; -+ } -+ -+ web_extensions_session->state = WEB_EXTENSIONS_SESSION_STATE_STARTING; -+ g_object_set_data_full (G_OBJECT (request), "session", g_object_ref (session), g_object_unref); -+ g_object_set_data_full (G_OBJECT (request), "name", g_strdup (arg_name), g_free); -+ g_object_set_data_full (G_OBJECT (request), "extension-or-origin", g_strdup (arg_extension_or_origin), g_free); -+ -+ xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); -+ xdp_dbus_web_extensions_complete_start (object, invocation, request->id); -+ -+ task = g_task_new (object, NULL, NULL, NULL); -+ g_task_set_task_data (task, g_object_ref (request), g_object_unref); -+ g_task_run_in_thread (task, handle_start_in_thread); -+ -+ return TRUE; -+} -+ -+ -+static gboolean -+handle_get_pipes (XdpDbusWebExtensions *object, -+ GDBusMethodInvocation *invocation, -+ GUnixFDList *fd_list, -+ const char *arg_session_handle, -+ GVariant *arg_options) -+{ -+ XdpCall *call = xdp_call_from_invocation (invocation); -+ XdpSession *session; -+ WebExtensionsSession *web_extensions_session; -+ int fds[3]; -+ g_autoptr(GUnixFDList) out_fd_list = NULL; -+ -+ session = xdp_session_from_call (arg_session_handle, call); -+ if (!session) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_ACCESS_DENIED, -+ "Invalid session"); -+ return TRUE; -+ } -+ -+ SESSION_AUTOLOCK_UNREF (session); -+ web_extensions_session = (WebExtensionsSession *)session; -+ -+ if (web_extensions_session->state != WEB_EXTENSIONS_SESSION_STATE_STARTED) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_FAILED, -+ "Session not started"); -+ return TRUE; -+ } -+ -+ if (web_extensions_session->standard_input < 0 || -+ web_extensions_session->standard_output < 0 || -+ web_extensions_session->standard_error < 0) -+ { -+ g_dbus_method_invocation_return_error (invocation, -+ G_DBUS_ERROR, -+ G_DBUS_ERROR_FAILED, -+ "GetPipes already called"); -+ return TRUE; -+ } -+ -+ fds[0] = web_extensions_session->standard_input; -+ fds[1] = web_extensions_session->standard_output; -+ fds[2] = web_extensions_session->standard_error; -+ out_fd_list = g_unix_fd_list_new_from_array (fds, G_N_ELEMENTS (fds)); -+ /* out_fd_list now owns the file descriptors */ -+ web_extensions_session->standard_input = -1; -+ web_extensions_session->standard_output = -1; -+ web_extensions_session->standard_error = -1; -+ -+ xdp_dbus_web_extensions_complete_get_pipes (object, invocation, out_fd_list, -+ g_variant_new_handle (0), -+ g_variant_new_handle (1), -+ g_variant_new_handle (2)); -+ return TRUE; -+} -+ -+static void -+web_extensions_iface_init (XdpDbusWebExtensionsIface *iface) -+{ -+ iface->handle_create_session = handle_create_session; -+ iface->handle_get_manifest = handle_get_manifest; -+ iface->handle_start = handle_start; -+ iface->handle_get_pipes = handle_get_pipes; -+} -+ -+static void -+web_extensions_init (WebExtensions *web_extensions) -+{ -+ xdp_dbus_web_extensions_set_version (XDP_DBUS_WEB_EXTENSIONS (web_extensions), 1); -+} -+ -+static void -+web_extensions_class_init (WebExtensionsClass *klass) -+{ -+ /* Called at the first instantiation of WebExtensions, -+ i.e. in web_extensions_create with the call to g_object_new. -+ https://docs.gtk.org/gobject/concepts.html#object-instantiation -+ */ -+} -+ -+GDBusInterfaceSkeleton * -+web_extensions_create (GDBusConnection *connection, -+ const char *dbus_name_access) -+{ -+ g_autoptr(GError) error = NULL; -+ -+ web_extensions = g_object_new (web_extensions_get_type (), NULL); -+ -+ access_impl = xdp_dbus_impl_access_proxy_new_sync (connection, -+ G_DBUS_PROXY_FLAGS_NONE, -+ dbus_name_access, -+ DESKTOP_PORTAL_OBJECT_PATH, -+ NULL, -+ &error); -+ if (access_impl == NULL) -+ { -+ g_warning ("Failed to create access proxy: %s", error->message); -+ return NULL; -+ } -+ -+ return G_DBUS_INTERFACE_SKELETON (web_extensions); -+} -diff --git a/src/web-extensions.h b/src/web-extensions.h -new file mode 100644 -index 000000000..72b947a31 ---- /dev/null -+++ b/src/web-extensions.h -@@ -0,0 +1,24 @@ -+/* -+ * Copyright © 2022 Canonical Ltd -+ * -+ * This program is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2 of the License, or (at your option) any later version. -+ * -+ * This library 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 -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with this library. If not, see . -+ * -+ */ -+ -+#pragma once -+ -+#include -+ -+GDBusInterfaceSkeleton *web_extensions_create (GDBusConnection *connection, -+ const char *access_dbus_name); -diff --git a/src/xdg-desktop-portal.c b/src/xdg-desktop-portal.c -index 55d44f9d0..e1559179f 100644 ---- a/src/xdg-desktop-portal.c -+++ b/src/xdg-desktop-portal.c -@@ -69,6 +69,7 @@ - #include "trash.h" - #include "usb.h" - #include "wallpaper.h" -+#include "web-extensions.h" - - static int global_exit_status = 0; - static GMainLoop *loop = NULL; -@@ -285,6 +286,9 @@ on_bus_acquired (GDBusConnection *connection, - { - XdpPortalImplementation *tmp; - -+ export_portal_implementation (connection, -+ web_extensions_create (connection, -+ access_impl->dbus_name)); - #ifdef HAVE_GEOCLUE - export_portal_implementation (connection, - location_create (connection, -diff --git a/src/xdp-request.c b/src/xdp-request.c -index 6aaa7ed23..46ceca861 100644 ---- a/src/xdp-request.c -+++ b/src/xdp-request.c -@@ -188,6 +188,18 @@ get_token (GDBusMethodInvocation *invocation) - if (method_info->option_arg >= 0) - options = g_variant_get_child_value (parameters, method_info->option_arg); - } -+ else if (strcmp (interface, "org.freedesktop.portal.WebExtensions") == 0) -+ { -+ if (strcmp (method, "Start") == 0) -+ { -+ options = g_variant_get_child_value (parameters, 3); -+ } -+ else -+ { -+ g_warning ("Support for %s::%s missing in %s", -+ interface, method, G_STRLOC); -+ } -+ } - else - { - g_warning ("Support for %s::%s missing in %s", -diff --git a/tests/meson.build b/tests/meson.build -index d3a46dc5f..df1ec61c4 100644 ---- a/tests/meson.build -+++ b/tests/meson.build -@@ -18,6 +18,7 @@ subdir('dbs') - subdir('portals') - subdir('services') - subdir('share') -+subdir('native-messaging-hosts') - - test_db = executable( - 'testdb', -@@ -106,6 +107,7 @@ if have_libportal - 'screenshot.c', - 'trash.c', - 'wallpaper.c', -+ 'web-extensions.c', - 'glib-backports.c', - ) - endif -@@ -174,6 +176,7 @@ portal_tests = [ - 'settings', - 'trash', - 'wallpaper', -+ 'webextensions', - ] - - test_env = env_tests -diff --git a/tests/native-messaging-hosts/meson.build b/tests/native-messaging-hosts/meson.build -new file mode 100644 -index 000000000..5ee790531 ---- /dev/null -+++ b/tests/native-messaging-hosts/meson.build -@@ -0,0 +1,28 @@ -+configure_file(input: 'server.sh', -+ output: '@PLAINNAME@', -+ copy: true, -+ install_mode: 'rwxr-xr-x', -+ install: enable_installed_tests, -+ install_dir: installed_tests_dir / 'native-messaging-hosts', -+) -+ -+config = configuration_data() -+config.set('server_path', meson.current_build_dir() / 'server.sh') -+configure_file(input: 'org.example.testing.json.in', -+ output: '@BASENAME@', -+ configuration: config, -+) -+ -+# create a second version to be installed -+if enable_installed_tests -+ config = configuration_data() -+ config.set('server_path', installed_tests_dir / 'native-messaging-hosts/server.sh') -+ configure_file(input: 'org.example.testing.json.in', -+ output: 'installed-org.example.testing.json', -+ configuration: config, -+ ) -+ install_data(meson.current_build_dir() / 'installed-org.example.testing.json', -+ rename: ['org.example.testing.json'], -+ install_dir: installed_tests_dir / 'native-messaging-hosts', -+ ) -+endif -diff --git a/tests/native-messaging-hosts/org.example.testing.json.in b/tests/native-messaging-hosts/org.example.testing.json.in -new file mode 100644 -index 000000000..58cd31029 ---- /dev/null -+++ b/tests/native-messaging-hosts/org.example.testing.json.in -@@ -0,0 +1,9 @@ -+{ -+ "name": "org.example.testing", -+ "description": "Test native messaging host", -+ "path": "@server_path@", -+ "type": "stdio", -+ "allowed_extensions": [ -+ "some-extension@example.org" -+ ] -+} -diff --git a/tests/native-messaging-hosts/server.sh b/tests/native-messaging-hosts/server.sh -new file mode 100755 -index 000000000..b0f3e402d ---- /dev/null -+++ b/tests/native-messaging-hosts/server.sh -@@ -0,0 +1,3 @@ -+#!/bin/sh -+ -+exec cat -diff --git a/tests/test-portals.c b/tests/test-portals.c -index bf56ea977..7593c84b3 100644 ---- a/tests/test-portals.c -+++ b/tests/test-portals.c -@@ -22,6 +22,7 @@ - #include "screenshot.h" - #include "trash.h" - #include "wallpaper.h" -+#include "web-extensions.h" - #endif - - #include "utils.h" -@@ -133,6 +134,7 @@ global_setup (void) - g_autofree gchar *backends_executable = NULL; - g_autofree gchar *services = NULL; - g_autofree gchar *portal_dir = NULL; -+ g_autofree gchar *web_extensions_dir = NULL; - g_autofree gchar *argv0 = NULL; - g_autoptr(GSubprocessLauncher) launcher = NULL; - g_autoptr(GSubprocess) subprocess = NULL; -@@ -281,12 +283,14 @@ global_setup (void) - NULL); - - portal_dir = g_test_build_filename (G_TEST_BUILT, "portals", "test", NULL); -+ web_extensions_dir = g_test_build_filename (G_TEST_BUILT, "native-messaging-hosts", NULL); - - g_clear_object (&launcher); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); - g_subprocess_launcher_setenv (launcher, "G_DEBUG", "fatal-criticals", TRUE); - g_subprocess_launcher_setenv (launcher, "DBUS_SESSION_BUS_ADDRESS", g_test_dbus_get_bus_address (dbus), TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DESKTOP_PORTAL_DIR", portal_dir, TRUE); -+ g_subprocess_launcher_setenv (launcher, "XDG_DESKTOP_PORTAL_WEB_EXTENSIONS_PATH", web_extensions_dir, TRUE); - g_subprocess_launcher_setenv (launcher, "XDG_DATA_HOME", outdir, TRUE); - g_subprocess_launcher_setenv (launcher, "PATH", g_getenv ("PATH"), TRUE); - g_subprocess_launcher_take_stdout_fd (launcher, xdup (STDERR_FILENO)); -@@ -593,6 +597,9 @@ main (int argc, char **argv) - g_test_add_func ("/portal/notification/display-hint", test_notification_display_hint); - g_test_add_func ("/portal/notification/category", test_notification_category); - g_test_add_func ("/portal/notification/supported-properties", test_notification_supported_properties); -+ -+ g_test_add_func ("/portal/webextensions/basic", test_web_extensions_basic); -+ g_test_add_func ("/portal/webextensions/bad-name", test_web_extensions_bad_name); - #endif - - global_setup (); -diff --git a/tests/web-extensions.c b/tests/web-extensions.c -new file mode 100644 -index 000000000..044d536a4 ---- /dev/null -+++ b/tests/web-extensions.c -@@ -0,0 +1,632 @@ -+#include -+ -+#include "web-extensions.h" -+#include "xdp-utils.h" -+ -+#include -+#include -+#include "xdp-impl-dbus.h" -+ -+extern char outdir[]; -+ -+// TODO: convert these simple client wrappers to proper libportal APIs -+ -+static void -+create_session_returned (GObject *object, -+ GAsyncResult *result, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = data; -+ g_autoptr(GVariant) ret = NULL; -+ GError *error = NULL; -+ g_autofree char *session_handle = NULL; -+ -+ ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); -+ if (!ret) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ g_variant_get (ret, "(o)", &session_handle); -+ g_task_return_pointer (task, g_steal_pointer (&session_handle), g_free); -+} -+ -+static void -+create_session (GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = NULL; -+ g_autoptr(GDBusConnection) session_bus = NULL; -+ GError *error = NULL; -+ g_autofree char *session_token = NULL; -+ GVariantBuilder options; -+ -+ task = g_task_new (NULL, cancellable, callback, data); -+ -+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); -+ if (session_bus == NULL) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ -+ session_token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT)); -+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); -+ g_variant_builder_add (&options, "{sv}", "mode", g_variant_new_string ("mozilla")); -+ g_variant_builder_add (&options, "{sv}", "session_handle_token", g_variant_new_string (session_token)); -+ g_dbus_connection_call (session_bus, -+ "org.freedesktop.portal.Desktop", -+ "/org/freedesktop/portal/desktop", -+ "org.freedesktop.portal.WebExtensions", -+ "CreateSession", -+ g_variant_new ("(a{sv})", &options), -+ NULL, -+ G_DBUS_CALL_FLAGS_NONE, -+ -1, -+ cancellable, -+ create_session_returned, -+ g_steal_pointer (&task)); -+} -+ -+static char * -+create_session_finish (GAsyncResult *result, GError **error) -+{ -+ return g_task_propagate_pointer (G_TASK (result), error); -+} -+ -+static void -+get_manifest_returned (GObject *object, -+ GAsyncResult *result, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = data; -+ g_autoptr(GVariant) ret = NULL; -+ GError *error = NULL; -+ g_autofree char *json_manifest = NULL; -+ -+ ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); -+ if (!ret) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ g_variant_get (ret, "(s)", &json_manifest); -+ g_task_return_pointer (task, g_steal_pointer (&json_manifest), g_free); -+} -+ -+static void -+get_manifest (const char *session_handle, -+ const char *name, -+ const char *extension_or_origin, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = NULL; -+ g_autoptr(GDBusConnection) session_bus = NULL; -+ GError *error = NULL; -+ -+ task = g_task_new (NULL, cancellable, callback, data); -+ -+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); -+ if (session_bus == NULL) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ -+ g_dbus_connection_call (session_bus, -+ "org.freedesktop.portal.Desktop", -+ "/org/freedesktop/portal/desktop", -+ "org.freedesktop.portal.WebExtensions", -+ "GetManifest", -+ g_variant_new ("(oss)", session_handle, name, extension_or_origin), -+ NULL, -+ G_DBUS_CALL_FLAGS_NONE, -+ -1, -+ cancellable, -+ get_manifest_returned, -+ g_steal_pointer (&task)); -+} -+ -+static char * -+get_manifest_finish (GAsyncResult *result, GError **error) -+{ -+ return g_task_propagate_pointer (G_TASK (result), error); -+} -+ -+static void -+start_returned (GObject *object, -+ GAsyncResult *result, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = data; -+ g_autoptr(GVariant) ret = NULL; -+ GError *error = NULL; -+ -+ ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); -+ if (!ret) -+ { -+ guint signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "response-signal-id")); -+ g_dbus_connection_signal_unsubscribe (G_DBUS_CONNECTION (object), signal_id); -+ g_task_return_error (task, error); -+ return; -+ } -+} -+ -+static void -+start_completed (GDBusConnection *session_bus, -+ const char *sender_name, -+ const char *object_path, -+ const char *interface_name, -+ const char *signal_name, -+ GVariant *parameters, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = g_object_ref (data); -+ guint signal_id; -+ guint32 response; -+ g_autoptr(GVariant) ret = NULL; -+ -+ signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "response-signal-id")); -+ g_dbus_connection_signal_unsubscribe (session_bus, signal_id); -+ -+ g_variant_get (parameters, "(u@a{sv})", &response, &ret); -+ switch (response) -+ { -+ case 0: -+ g_task_return_boolean (task, TRUE); -+ break; -+ case 1: -+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Start cancelled"); -+ break; -+ case 2: -+ default: -+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Start failed"); -+ break; -+ } -+} -+ -+static void -+start (const char *session_handle, -+ const char *name, -+ const char *extension_or_origin, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = NULL; -+ g_autoptr(GDBusConnection) session_bus = NULL; -+ GError *error = NULL; -+ g_autofree char *token = NULL; -+ g_autofree char *sender = NULL; -+ g_autofree char *request_path = NULL; -+ int i; -+ guint signal_id; -+ GVariantBuilder options; -+ -+ task = g_task_new (NULL, cancellable, callback, data); -+ -+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); -+ if (session_bus == NULL) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ -+ token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT)); -+ sender = g_strdup (g_dbus_connection_get_unique_name (session_bus) + 1); -+ for (i = 0; sender[i]; i++) -+ if (sender[i] == '.') -+ sender[i] = '_'; -+ request_path = g_strconcat ("/org/freedesktop/portal/desktop/request/", sender, "/", token, NULL); -+ signal_id = g_dbus_connection_signal_subscribe (session_bus, -+ "org.freedesktop.portal.Desktop", -+ "org.freedesktop.portal.Request", -+ "Response", -+ request_path, -+ NULL, -+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, -+ start_completed, -+ g_object_ref (task), -+ g_object_unref); -+ g_object_set_data (G_OBJECT (task), "response-signal-id", GUINT_TO_POINTER (signal_id)); -+ -+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); -+ g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token)); -+ g_dbus_connection_call (session_bus, -+ "org.freedesktop.portal.Desktop", -+ "/org/freedesktop/portal/desktop", -+ "org.freedesktop.portal.WebExtensions", -+ "Start", -+ g_variant_new ("(ossa{sv})", session_handle, name, extension_or_origin, &options), -+ NULL, -+ G_DBUS_CALL_FLAGS_NONE, -+ -1, -+ cancellable, -+ start_returned, -+ g_steal_pointer (&task)); -+} -+ -+static gboolean -+start_finish (GAsyncResult *result, GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+static void -+get_pipes_returned (GObject *object, -+ GAsyncResult *result, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = data; -+ g_autoptr(GVariant) ret = NULL; -+ g_autoptr(GUnixFDList) fd_list = NULL; -+ GError *error = NULL; -+ -+ ret = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (object), &fd_list, result, &error); -+ if (!ret) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ g_task_return_pointer (task, g_steal_pointer (&fd_list), g_object_unref); -+} -+ -+static void -+get_pipes (const char *session_handle, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = NULL; -+ g_autoptr(GDBusConnection) session_bus = NULL; -+ GError *error = NULL; -+ -+ task = g_task_new (NULL, cancellable, callback, data); -+ -+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); -+ if (session_bus == NULL) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ -+ g_dbus_connection_call_with_unix_fd_list (session_bus, -+ "org.freedesktop.portal.Desktop", -+ "/org/freedesktop/portal/desktop", -+ "org.freedesktop.portal.WebExtensions", -+ "GetPipes", -+ g_variant_new ("(oa{sv})", session_handle, NULL), -+ NULL, -+ G_DBUS_CALL_FLAGS_NONE, -+ -1, -+ NULL, -+ cancellable, -+ get_pipes_returned, -+ g_steal_pointer (&task)); -+} -+ -+static gboolean -+get_pipes_finish (int *stdin_fileno, int *stdout_fileno, int *stderr_fileno, GAsyncResult *result, GError **error) -+{ -+ g_autoptr(GUnixFDList) fd_list = NULL; -+ -+ fd_list = g_task_propagate_pointer (G_TASK (result), error); -+ if (fd_list == NULL) -+ return FALSE; -+ -+ if (stdin_fileno != NULL) -+ { -+ *stdin_fileno = g_unix_fd_list_get (fd_list, 0, error); -+ if (*stdin_fileno < 0) -+ return FALSE; -+ } -+ if (stdout_fileno != NULL) -+ { -+ *stdout_fileno = g_unix_fd_list_get (fd_list, 1, error); -+ if (*stdout_fileno < 0) -+ return FALSE; -+ } -+ if (stderr_fileno != NULL) -+ { -+ *stderr_fileno = g_unix_fd_list_get (fd_list, 2, error); -+ if (*stderr_fileno < 0) -+ return FALSE; -+ } -+ return TRUE; -+} -+ -+static void -+close_session_returned (GObject *object, -+ GAsyncResult *result, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = data; -+ g_autoptr(GVariant) ret = NULL; -+ GError *error = NULL; -+ -+ ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); -+ if (!ret) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ g_task_return_boolean (task, TRUE); -+} -+ -+static void -+close_session (const char *session_handle, -+ GCancellable *cancellable, -+ GAsyncReadyCallback callback, -+ gpointer data) -+{ -+ g_autoptr(GTask) task = NULL; -+ g_autoptr(GDBusConnection) session_bus = NULL; -+ GError *error = NULL; -+ -+ task = g_task_new (NULL, cancellable, callback, data); -+ -+ session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); -+ if (session_bus == NULL) -+ { -+ g_task_return_error (task, error); -+ return; -+ } -+ -+ g_dbus_connection_call (session_bus, -+ "org.freedesktop.portal.Desktop", -+ session_handle, -+ "org.freedesktop.portal.Session", -+ "Close", -+ NULL, -+ NULL, -+ G_DBUS_CALL_FLAGS_NONE, -+ -1, -+ cancellable, -+ close_session_returned, -+ g_steal_pointer (&task)); -+} -+ -+static gboolean -+close_session_finish (GAsyncResult *result, GError **error) -+{ -+ return g_task_propagate_boolean (G_TASK (result), error); -+} -+ -+ -+static int got_info = 0; -+ -+extern XdpDbusImplPermissionStore *permission_store; -+ -+static void -+set_web_extensions_permissions (const char *permission) -+{ -+ const char *permissions[2] = { NULL, NULL }; -+ g_autoptr(GError) error = NULL; -+ -+ permissions[0] = permission; -+ xdp_dbus_impl_permission_store_call_set_permission_sync (permission_store, -+ "webextensions", -+ TRUE, -+ "org.example.testing", -+ "", -+ permissions, -+ NULL, -+ &error); -+ g_assert_no_error (error); -+} -+ -+static gboolean -+cancel_call (gpointer data) -+{ -+ GCancellable *cancellable = data; -+ -+ g_debug ("cancel call"); -+ g_cancellable_cancel (cancellable); -+ -+ return G_SOURCE_REMOVE; -+} -+ -+typedef struct { -+ GCancellable *cancellable; -+ char *session_handle; -+ const char *messaging_host_name; -+} TestData; -+ -+static void -+close_session_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ g_autoptr(GError) error = NULL; -+ gboolean ret; -+ -+ ret = close_session_finish (result, &error); -+ if (ret) -+ { -+ g_assert_no_error (error); -+ } -+ else -+ { -+ /* The native messaging host may have closed before we tried to -+ close it. */ -+ g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD); -+ } -+ -+ got_info++; -+ g_main_context_wakeup (NULL); -+} -+ -+static void -+get_pipes_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ TestData *test_data = data; -+ g_autoptr(GError) error = NULL; -+ gboolean ret; -+ int stdin_fileno = -1, stdout_fileno = -1, stderr_fileno = -1; -+ -+ ret = get_pipes_finish (&stdin_fileno, &stdout_fileno, &stderr_fileno, result, &error); -+ g_assert_no_error (error); -+ g_assert_true (ret); -+ g_assert_cmpint (stdin_fileno, >, 0); -+ g_assert_cmpint (stdout_fileno, >, 0); -+ g_assert_cmpint (stderr_fileno, >, 0); -+ -+ close (stdin_fileno); -+ close (stdout_fileno); -+ close (stderr_fileno); -+ -+ close_session (test_data->session_handle, -+ test_data->cancellable, -+ close_session_cb, -+ test_data); -+} -+ -+static void -+start_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ TestData *test_data = data; -+ g_autoptr(GError) error = NULL; -+ gboolean ret; -+ -+ ret = start_finish (result, &error); -+ g_assert_no_error (error); -+ g_assert_true (ret); -+ -+ get_pipes (test_data->session_handle, -+ test_data->cancellable, -+ get_pipes_cb, -+ test_data); -+} -+ -+ -+static void -+get_manifest_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ TestData *test_data = data; -+ g_autoptr(GError) error = NULL; -+ g_autofree char *json_manifest = NULL; -+ g_autofree char *host_path = NULL; -+ g_autofree char *expected = NULL; -+ -+ host_path = g_test_build_filename (G_TEST_BUILT, "native-messaging-hosts", "server.sh", NULL); -+ expected = g_strdup_printf ("{\"name\":\"org.example.testing\",\"description\":\"Test native messaging host\",\"path\":\"%s\",\"type\":\"stdio\",\"allowed_extensions\":[\"some-extension@example.org\"]}", host_path); -+ -+ json_manifest = get_manifest_finish (result, &error); -+ g_assert_no_error (error); -+ g_assert_cmpstr (json_manifest, ==, expected); -+ -+ start (test_data->session_handle, -+ "org.example.testing", -+ "some-extension@example.org", -+ test_data->cancellable, -+ start_cb, -+ test_data); -+} -+ -+static void -+create_session_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ TestData *test_data = data; -+ g_autoptr(GError) error = NULL; -+ -+ test_data->session_handle = create_session_finish (result, &error); -+ g_assert_no_error (error); -+ g_assert_nonnull (test_data->session_handle); -+ -+ get_manifest (test_data->session_handle, -+ "org.example.testing", -+ "some-extension@example.org", -+ test_data->cancellable, -+ get_manifest_cb, -+ test_data); -+} -+ -+void -+test_web_extensions_basic (void) -+{ -+ g_autoptr(GCancellable) cancellable = NULL; -+ TestData test_data = { cancellable, NULL }; -+ g_autoptr(GKeyFile) keyfile = NULL; -+ g_autofree char *path = NULL; -+ g_autoptr(GError) error = NULL; -+ -+ got_info = 0; -+ keyfile = g_key_file_new (); -+ -+ g_key_file_set_integer (keyfile, "backend", "delay", 0); -+ g_key_file_set_integer (keyfile, "backend", "response", 0); -+ g_key_file_set_integer (keyfile, "result", "response", 0); -+ -+ path = g_build_filename (outdir, "access", NULL); -+ g_key_file_save_to_file (keyfile, path, &error); -+ g_assert_no_error (error); -+ -+ g_key_file_unref (keyfile); -+ -+ set_web_extensions_permissions ("yes"); -+ create_session (cancellable, create_session_cb, &test_data); -+ -+ g_timeout_add (100, cancel_call, cancellable); -+ while (!got_info) -+ g_main_context_iteration (NULL, TRUE); -+ g_free (test_data.session_handle); -+} -+ -+static void -+start_bad_name_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ g_autoptr(GError) error = NULL; -+ gboolean ret; -+ -+ ret = start_finish (result, &error); -+ g_assert_false (ret); -+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); -+ -+ got_info++; -+ g_main_context_wakeup (NULL); -+} -+ -+static void -+create_session_bad_name_cb (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ TestData *test_data = data; -+ g_autoptr(GError) error = NULL; -+ -+ test_data->session_handle = create_session_finish (result, &error); -+ g_assert_no_error (error); -+ g_assert_nonnull (test_data->session_handle); -+ -+ start (test_data->session_handle, -+ test_data->messaging_host_name, -+ "some-extension@example.org", -+ test_data->cancellable, -+ start_bad_name_cb, -+ test_data); -+} -+ -+void -+test_web_extensions_bad_name (void) -+{ -+ const char *messaging_host_name[] = { -+ "no-dashes", -+ "../foo", -+ "no_trailing_dot.", -+ }; -+ int i; -+ -+ for (i = 0; i < G_N_ELEMENTS (messaging_host_name); i++) -+ { -+ g_autoptr(GCancellable) cancellable = NULL; -+ TestData test_data = { cancellable, NULL, messaging_host_name[i] }; -+ -+ got_info = 0; -+ set_web_extensions_permissions ("yes"); -+ create_session (cancellable, create_session_bad_name_cb, &test_data); -+ -+ g_timeout_add (100, cancel_call, cancellable); -+ while (!got_info) -+ g_main_context_iteration (NULL, TRUE); -+ g_free (test_data.session_handle); -+ } -+} -diff --git a/tests/web-extensions.h b/tests/web-extensions.h -new file mode 100644 -index 000000000..0090184b9 ---- /dev/null -+++ b/tests/web-extensions.h -@@ -0,0 +1,2 @@ -+void test_web_extensions_basic (void); -+void test_web_extensions_bad_name (void); diff --git a/xdg-desktop-portal.spec b/xdg-desktop-portal.spec index 8197fea..fff3c5b 100644 --- a/xdg-desktop-portal.spec +++ b/xdg-desktop-portal.spec @@ -8,7 +8,7 @@ %global pipewire_version 0.2.90 Name: xdg-desktop-portal -Version: 1.19.1 +Version: 1.19.2 Release: %autorelease Summary: Portal frontend service to flatpak @@ -17,12 +17,6 @@ License: LGPL-2.1-or-later URL: https://github.com/flatpak/xdg-desktop-portal/ Source0: https://github.com/flatpak/xdg-desktop-portal/releases/download/%{version}/%{name}-%{version}.tar.xz -# Backport of webextensions portal -# PR: https://github.com/flatpak/xdg-desktop-portal/pull/1537 -Patch0: xdg-desktop-portal-webextensions.patch -# Camera: Fix permission check in OpenPipeWireRemote -# https://github.com/flatpak/xdg-desktop-portal/pull/1576 -Patch1: xdp-fix-permission-check-in-openpipewireremote.patch BuildRequires: gcc BuildRequires: gettext diff --git a/xdp-fix-permission-check-in-openpipewireremote.patch b/xdp-fix-permission-check-in-openpipewireremote.patch deleted file mode 100644 index 8160508..0000000 --- a/xdp-fix-permission-check-in-openpipewireremote.patch +++ /dev/null @@ -1,81 +0,0 @@ -From 316b950becdf4b7b3bd3f443955d890b1abee0db Mon Sep 17 00:00:00 2001 -From: Michael Catanzaro -Date: Wed, 15 Jan 2025 15:15:51 -0600 -Subject: [PATCH] camera: fix permission check in OpenPipeWireRemote - -6cd99b04d438b1de9b25c7bd928e541609904db3 changed the logic that the -camera portal uses to look up permissions for the AccessCamera method. -Applications first call AccessCamera to ensure they have camera -permission and to prompt the user if permission is missing, then they -call OpenPipeWireRemote, which fails if permission is missing. The -permission lookup logic needs to be the same in both places. Currently -when running Snapshot launched by GNOME Shell (rather than launched in a -terminal), Snapshot passes AccessCamera's permission check, then fails -OpenPipeWireRemote's permission check, causing camera access to be -denied without allowing the user to grant permission. - -Also, since the same commit the code uses the XdpAppInfo on a secondary -thread. I suspect this is unsafe, and the original code avoided doing -so; therefore, let's be careful and move this logic to the main thread -so that the secondary thread only receives a copy of the app ID, as -before. - -https://gitlab.gnome.org/GNOME/snapshot/-/issues/267 ---- - src/camera.c | 22 +++++++++++++--------- - 1 file changed, 13 insertions(+), 9 deletions(-) - -diff --git a/src/camera.c b/src/camera.c -index 4224a0a7b..9b43b36d8 100644 ---- a/src/camera.c -+++ b/src/camera.c -@@ -78,11 +78,7 @@ query_permission_sync (XdpRequest *request) - const char *app_id; - gboolean allowed; - -- if (xdp_app_info_is_host (request->app_info)) -- app_id = ""; -- else -- app_id = (const char *)g_object_get_data (G_OBJECT (request), "app-id"); -- -+ app_id = (const char *)g_object_get_data (G_OBJECT (request), "app-id"); - permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_DEVICE_CAMERA); - if (permission == XDP_PERMISSION_ASK || permission == XDP_PERMISSION_UNSET) - { -@@ -190,6 +186,16 @@ handle_access_camera_in_thread_func (GTask *task, - } - } - -+static const char * -+app_id_from_app_info (XdpAppInfo *app_info) -+{ -+ /* Automatically grant camera access to unsandboxed apps. */ -+ if (xdp_app_info_is_host (app_info)) -+ return ""; -+ -+ return xdp_app_info_get_id (app_info); -+} -+ - static gboolean - handle_access_camera (XdpDbusCamera *object, - GDBusMethodInvocation *invocation, -@@ -211,9 +217,7 @@ handle_access_camera (XdpDbusCamera *object, - - REQUEST_AUTOLOCK (request); - -- app_id = xdp_app_info_get_id (request->app_info); -- -- -+ app_id = app_id_from_app_info (request->app_info); - g_object_set_data_full (G_OBJECT (request), "app-id", g_strdup (app_id), g_free); - - xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation)); -@@ -288,7 +292,7 @@ handle_open_pipewire_remote (XdpDbusCamera *object, - } - - app_info = xdp_invocation_lookup_app_info_sync (invocation, NULL, &error); -- app_id = xdp_app_info_get_id (app_info); -+ app_id = app_id_from_app_info (app_info); - permission = xdp_get_permission_sync (app_id, PERMISSION_TABLE, PERMISSION_DEVICE_CAMERA); - if (permission != XDP_PERMISSION_YES) - {