diff options
author | DongHun Kwak <dh0128.kwak@samsung.com> | 2021-10-29 10:30:56 +0900 |
---|---|---|
committer | DongHun Kwak <dh0128.kwak@samsung.com> | 2021-10-29 10:30:56 +0900 |
commit | 12500068fcfd930af96d75506201fda8d829220b (patch) | |
tree | 84c49eeeae1de699480669d815d01ad1f5c888d3 /gio | |
parent | 71ffb6083fde389da869fa671affd9da39c40984 (diff) | |
download | glib-12500068fcfd930af96d75506201fda8d829220b.tar.gz glib-12500068fcfd930af96d75506201fda8d829220b.tar.bz2 glib-12500068fcfd930af96d75506201fda8d829220b.zip |
Imported Upstream version 2.67.0upstream/2.67.0
Diffstat (limited to 'gio')
57 files changed, 2815 insertions, 3316 deletions
diff --git a/gio/gcredentials.c b/gio/gcredentials.c index 146829e7c..9423f9ef9 100644 --- a/gio/gcredentials.c +++ b/gio/gcredentials.c @@ -225,7 +225,7 @@ g_credentials_to_string (GCredentials *credentials) { GString *ret; #if G_CREDENTIALS_USE_APPLE_XUCRED - __typeof__(credentials->native.cr_ngroups) i; + glib_typeof (credentials->native.cr_ngroups) i; #endif g_return_val_if_fail (G_IS_CREDENTIALS (credentials), NULL); diff --git a/gio/gdatainputstream.c b/gio/gdatainputstream.c index 2cdcbda19..2e7750cb5 100644 --- a/gio/gdatainputstream.c +++ b/gio/gdatainputstream.c @@ -27,7 +27,6 @@ #include "gioenumtypes.h" #include "gioerror.h" #include "glibintl.h" -#include "gstrfuncsprivate.h" #include <string.h> @@ -857,7 +856,7 @@ static gssize scan_for_chars (GDataInputStream *stream, gsize *checked_out, const char *stop_chars, - gsize stop_chars_len) + gssize stop_chars_len) { GBufferedInputStream *bstream; const char *buffer; @@ -953,7 +952,7 @@ typedef struct gsize checked; gchar *stop_chars; - gsize stop_chars_len; + gssize stop_chars_len; gsize length; } GDataInputStreamReadData; @@ -1079,17 +1078,12 @@ g_data_input_stream_read_async (GDataInputStream *stream, { GDataInputStreamReadData *data; GTask *task; - gsize stop_chars_len_unsigned; data = g_slice_new0 (GDataInputStreamReadData); - - if (stop_chars_len < 0) - stop_chars_len_unsigned = strlen (stop_chars); - else - stop_chars_len_unsigned = (gsize) stop_chars_len; - - data->stop_chars = g_memdup2 (stop_chars, stop_chars_len_unsigned); - data->stop_chars_len = stop_chars_len_unsigned; + if (stop_chars_len == -1) + stop_chars_len = strlen (stop_chars); + data->stop_chars = g_memdup (stop_chars, stop_chars_len); + data->stop_chars_len = stop_chars_len; data->last_saw_cr = FALSE; task = g_task_new (stream, cancellable, callback, user_data); @@ -1344,20 +1338,17 @@ g_data_input_stream_read_upto (GDataInputStream *stream, gssize found_pos; gssize res; char *data_until; - gsize stop_chars_len_unsigned; g_return_val_if_fail (G_IS_DATA_INPUT_STREAM (stream), NULL); if (stop_chars_len < 0) - stop_chars_len_unsigned = strlen (stop_chars); - else - stop_chars_len_unsigned = (gsize) stop_chars_len; + stop_chars_len = strlen (stop_chars); bstream = G_BUFFERED_INPUT_STREAM (stream); checked = 0; - while ((found_pos = scan_for_chars (stream, &checked, stop_chars, stop_chars_len_unsigned)) == -1) + while ((found_pos = scan_for_chars (stream, &checked, stop_chars, stop_chars_len)) == -1) { if (g_buffered_input_stream_get_available (bstream) == g_buffered_input_stream_get_buffer_size (bstream)) diff --git a/gio/gdbus-2.0/codegen/codegen.py b/gio/gdbus-2.0/codegen/codegen.py index 5234b3807..cda047173 100644 --- a/gio/gdbus-2.0/codegen/codegen.py +++ b/gio/gdbus-2.0/codegen/codegen.py @@ -1522,7 +1522,7 @@ class CodeGenerator: ' *\n' ' * If a signal handler returns %%TRUE, it means the signal handler will handle the invocation (e.g. take a reference to @invocation and eventually call %s_complete_%s() or e.g. g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler handles the invocation, the %%G_DBUS_ERROR_UNKNOWN_METHOD error is returned.\n' ' *\n' - ' * Returns: %%TRUE if the invocation was handled, %%FALSE to let other signal handlers run.\n' + ' * Returns: %%G_DBUS_METHOD_INVOCATION_HANDLED or %%TRUE if the invocation was handled, %%G_DBUS_METHOD_INVOCATION_UNHANDLED or %%FALSE to let other signal handlers run.\n' %(i.name, m.name, i.name_lower, m.name_lower), False)) self.write_gtkdoc_deprecated_and_since_and_close(m, self.outfile, 2) if m.unix_fd: diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c index 0044cd3c6..3dd3cc84b 100644 --- a/gio/gdbusaddress.c +++ b/gio/gdbusaddress.c @@ -30,7 +30,6 @@ #include "gdbusaddress.h" #include "gdbuserror.h" #include "gioenumtypes.h" -#include "glib-private.h" #include "gnetworkaddress.h" #include "gsocketclient.h" #include "giostream.h" @@ -1280,7 +1279,6 @@ g_dbus_address_get_for_bus_sync (GBusType bus_type, GCancellable *cancellable, GError **error) { - gboolean has_elevated_privileges = GLIB_PRIVATE_CALL (g_check_setuid) (); gchar *ret, *s = NULL; const gchar *starter_bus; GError *local_error; @@ -1319,16 +1317,10 @@ g_dbus_address_get_for_bus_sync (GBusType bus_type, _g_dbus_debug_print_unlock (); } - /* Don’t load the addresses from the environment if running as setuid, as they - * come from an unprivileged caller. */ switch (bus_type) { case G_BUS_TYPE_SYSTEM: - if (has_elevated_privileges) - ret = NULL; - else - ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS")); - + ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS")); if (ret == NULL) { ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket"); @@ -1336,33 +1328,7 @@ g_dbus_address_get_for_bus_sync (GBusType bus_type, break; case G_BUS_TYPE_SESSION: - if (has_elevated_privileges) - { -#ifdef G_OS_UNIX - if (geteuid () == getuid ()) - { - /* Ideally we shouldn't do this, because setgid and - * filesystem capabilities are also elevated privileges - * with which we should not be trusting environment variables - * from the caller. Unfortunately, there are programs with - * elevated privileges that rely on the session bus being - * available. We already prevent the really dangerous - * transports like autolaunch: and unixexec: when our - * privileges are elevated, so this can only make us connect - * to the wrong AF_UNIX or TCP socket. */ - ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")); - } - else -#endif - { - ret = NULL; - } - } - else - { - ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")); - } - + ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")); if (ret == NULL) { ret = get_session_address_platform_specific (&local_error); diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c index 75b818069..54aaf43d9 100644 --- a/gio/gdbusconnection.c +++ b/gio/gdbusconnection.c @@ -110,7 +110,6 @@ #include "gasyncinitable.h" #include "giostream.h" #include "gasyncresult.h" -#include "gstrfuncsprivate.h" #include "gtask.h" #include "gmarshal-internal.h" @@ -121,13 +120,6 @@ #include "glibintl.h" -#define G_DBUS_CONNECTION_FLAGS_ALL \ - (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | \ - G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER | \ - G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS | \ - G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION | \ - G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING) - /** * SECTION:gdbusconnection * @short_description: D-Bus Connections @@ -2713,7 +2705,6 @@ g_dbus_connection_new (GIOStream *stream, _g_dbus_initialize (); g_return_if_fail (G_IS_IO_STREAM (stream)); - g_return_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0); g_async_initable_new_async (G_TYPE_DBUS_CONNECTION, G_PRIORITY_DEFAULT, @@ -2801,7 +2792,6 @@ g_dbus_connection_new_sync (GIOStream *stream, { _g_dbus_initialize (); g_return_val_if_fail (G_IS_IO_STREAM (stream), NULL); - g_return_val_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); return g_initable_new (G_TYPE_DBUS_CONNECTION, cancellable, @@ -2859,7 +2849,6 @@ g_dbus_connection_new_for_address (const gchar *address, _g_dbus_initialize (); g_return_if_fail (address != NULL); - g_return_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0); g_async_initable_new_async (G_TYPE_DBUS_CONNECTION, G_PRIORITY_DEFAULT, @@ -2947,7 +2936,6 @@ g_dbus_connection_new_for_address_sync (const gchar *address, _g_dbus_initialize (); g_return_val_if_fail (address != NULL, NULL); - g_return_val_if_fail ((flags & ~G_DBUS_CONNECTION_FLAGS_ALL) == 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); return g_initable_new (G_TYPE_DBUS_CONNECTION, cancellable, @@ -4021,7 +4009,7 @@ _g_dbus_interface_vtable_copy (const GDBusInterfaceVTable *vtable) /* Don't waste memory by copying padding - remember to update this * when changing struct _GDBusInterfaceVTable in gdbusconnection.h */ - return g_memdup2 ((gconstpointer) vtable, 3 * sizeof (gpointer)); + return g_memdup ((gconstpointer) vtable, 3 * sizeof (gpointer)); } static void @@ -4038,7 +4026,7 @@ _g_dbus_subtree_vtable_copy (const GDBusSubtreeVTable *vtable) /* Don't waste memory by copying padding - remember to update this * when changing struct _GDBusSubtreeVTable in gdbusconnection.h */ - return g_memdup2 ((gconstpointer) vtable, 3 * sizeof (gpointer)); + return g_memdup ((gconstpointer) vtable, 3 * sizeof (gpointer)); } static void diff --git a/gio/gdbusinterfaceskeleton.c b/gio/gdbusinterfaceskeleton.c index a0cd25087..a0125c541 100644 --- a/gio/gdbusinterfaceskeleton.c +++ b/gio/gdbusinterfaceskeleton.c @@ -28,7 +28,6 @@ #include "gdbusmethodinvocation.h" #include "gdbusconnection.h" #include "gmarshal-internal.h" -#include "gstrfuncsprivate.h" #include "gtask.h" #include "gioerror.h" @@ -703,7 +702,7 @@ add_connection_locked (GDBusInterfaceSkeleton *interface_, * properly before building the hooked_vtable, so we create it * once at the last minute. */ - interface_->priv->hooked_vtable = g_memdup2 (g_dbus_interface_skeleton_get_vtable (interface_), sizeof (GDBusInterfaceVTable)); + interface_->priv->hooked_vtable = g_memdup (g_dbus_interface_skeleton_get_vtable (interface_), sizeof (GDBusInterfaceVTable)); interface_->priv->hooked_vtable->method_call = skeleton_intercept_handle_method_call; } diff --git a/gio/gdbusmethodinvocation.h b/gio/gdbusmethodinvocation.h index 061256ffe..775070a2c 100644 --- a/gio/gdbusmethodinvocation.h +++ b/gio/gdbusmethodinvocation.h @@ -33,6 +33,41 @@ G_BEGIN_DECLS #define G_DBUS_METHOD_INVOCATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_DBUS_METHOD_INVOCATION, GDBusMethodInvocation)) #define G_IS_DBUS_METHOD_INVOCATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_DBUS_METHOD_INVOCATION)) +/** + * G_DBUS_METHOD_INVOCATION_HANDLED: + * + * The value returned by handlers of the signals generated by + * the `gdbus-codegen` tool to indicate that a method call has been + * handled by an implementation. It is equal to %TRUE, but using + * this macro is sometimes more readable. + * + * In code that needs to be backwards-compatible with older GLib, + * use %TRUE instead, often written like this: + * + * |[ + * g_dbus_method_invocation_return_error (invocation, ...); + * return TRUE; // handled + * ]| + * + * Since: 2.68 + */ +#define G_DBUS_METHOD_INVOCATION_HANDLED TRUE GLIB_AVAILABLE_MACRO_IN_2_68 + +/** + * G_DBUS_METHOD_INVOCATION_UNHANDLED: + * + * The value returned by handlers of the signals generated by + * the `gdbus-codegen` tool to indicate that a method call has not been + * handled by an implementation. It is equal to %FALSE, but using + * this macro is sometimes more readable. + * + * In code that needs to be backwards-compatible with older GLib, + * use %FALSE instead. + * + * Since: 2.68 + */ +#define G_DBUS_METHOD_INVOCATION_UNHANDLED FALSE GLIB_AVAILABLE_MACRO_IN_2_68 + GLIB_AVAILABLE_IN_ALL GType g_dbus_method_invocation_get_type (void) G_GNUC_CONST; GLIB_AVAILABLE_IN_ALL diff --git a/gio/gdbusprivate.c b/gio/gdbusprivate.c index 2551e4791..5c980b40b 100644 --- a/gio/gdbusprivate.c +++ b/gio/gdbusprivate.c @@ -1085,11 +1085,8 @@ write_message_continue_writing (MessageToWriteData *data) else { #ifdef G_OS_UNIX - if (data->total_written == 0 && fd_list != NULL) + if (fd_list != NULL) { - /* We were trying to write byte 0 of the message, which needs - * the fd list to be attached to it, but this connection doesn't - * support doing that. */ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, diff --git a/gio/gdbusserver.c b/gio/gdbusserver.c index 64bac73ae..4a8dab0ea 100644 --- a/gio/gdbusserver.c +++ b/gio/gdbusserver.c @@ -57,10 +57,6 @@ #include "glibintl.h" -#define G_DBUS_SERVER_FLAGS_ALL \ - (G_DBUS_SERVER_FLAGS_RUN_IN_THREAD | \ - G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS) - /** * SECTION:gdbusserver * @short_description: Helper for accepting connections @@ -516,7 +512,6 @@ g_dbus_server_new_sync (const gchar *address, g_return_val_if_fail (address != NULL, NULL); g_return_val_if_fail (g_dbus_is_guid (guid), NULL); - g_return_val_if_fail ((flags & ~G_DBUS_SERVER_FLAGS_ALL) == 0, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); server = g_initable_new (G_TYPE_DBUS_SERVER, diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 2b139a01c..b779b309b 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -305,55 +305,6 @@ desktop_file_dir_app_name_is_masked (DesktopFileDir *dir, return FALSE; } -/* Not much to go on from https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html - * so validate it as a non-empty alphanumeric ASCII string with `-` and `_` allowed. - * - * Validation is important as the desktop IDs are used to construct filenames, - * and may be set by an unprivileged caller if running in a setuid program. */ -static gboolean -validate_xdg_desktop (const gchar *desktop) -{ - gsize i; - - for (i = 0; desktop[i] != '\0'; i++) - if (desktop[i] != '-' && desktop[i] != '_' && - !g_ascii_isalnum (desktop[i])) - return FALSE; - - if (i == 0) - return FALSE; - - return TRUE; -} - -static char ** -get_valid_current_desktops (const char *value) -{ - char **tmp; - gsize i; - GPtrArray *valid_desktops; - - if (value == NULL) - value = g_getenv ("XDG_CURRENT_DESKTOP"); - if (value == NULL) - value = ""; - - tmp = g_strsplit (value, G_SEARCHPATH_SEPARATOR_S, 0); - valid_desktops = g_ptr_array_new_full (g_strv_length (tmp) + 1, g_free); - for (i = 0; tmp[i]; i++) - { - if (validate_xdg_desktop (tmp[i])) - g_ptr_array_add (valid_desktops, tmp[i]); - else - g_free (tmp[i]); - } - g_ptr_array_add (valid_desktops, NULL); - g_free (tmp); - tmp = (char **) g_ptr_array_steal (valid_desktops, NULL); - g_ptr_array_unref (valid_desktops); - return tmp; -} - static const gchar * const * get_lowercase_current_desktops (void) { @@ -361,15 +312,23 @@ get_lowercase_current_desktops (void) if (g_once_init_enter (&result)) { - char **tmp = get_valid_current_desktops (NULL); - gsize i, j; + const gchar *envvar; + gchar **tmp; + + envvar = g_getenv ("XDG_CURRENT_DESKTOP"); - for (i = 0; tmp[i]; i++) + if (envvar) { - /* Convert to lowercase. */ - for (j = 0; tmp[i][j]; j++) - tmp[i][j] = g_ascii_tolower (tmp[i][j]); + gint i, j; + + tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0); + + for (i = 0; tmp[i]; i++) + for (j = 0; tmp[i][j]; j++) + tmp[i][j] = g_ascii_tolower (tmp[i][j]); } + else + tmp = g_new0 (gchar *, 0 + 1); g_once_init_leave (&result, tmp); } @@ -384,7 +343,15 @@ get_current_desktops (const gchar *value) if (g_once_init_enter (&result)) { - char **tmp = get_valid_current_desktops (value); + gchar **tmp; + + if (!value) + value = g_getenv ("XDG_CURRENT_DESKTOP"); + + if (!value) + value = ""; + + tmp = g_strsplit (value, ":", 0); g_once_init_leave (&result, tmp); } diff --git a/gio/gfile.c b/gio/gfile.c index c16912b89..f92c07612 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -60,7 +60,6 @@ #include "gasyncresult.h" #include "gioerror.h" #include "glibintl.h" -#include "gstrfuncsprivate.h" /** @@ -2696,14 +2695,36 @@ should_copy (GFileAttributeInfo *info, return info->flags & G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE; } -static gboolean -build_attribute_list_for_copy (GFile *file, - GFileCopyFlags flags, - char **out_attributes, - GCancellable *cancellable, - GError **error) +/** + * g_file_build_attribute_list_for_copy: + * @file: a #GFile to copy attributes to + * @flags: a set of #GFileCopyFlags + * @cancellable: (nullable): optional #GCancellable object, + * %NULL to ignore + * @error: a #GError, %NULL to ignore + * + * Prepares the file attribute query string for copying to @file. + * + * This function prepares an attribute query string to be + * passed to g_file_query_info() to get a list of attributes + * normally copied with the file (see g_file_copy_attributes() + * for the detailed description). This function is used by the + * implementation of g_file_copy_attributes() and is useful + * when one needs to query and set the attributes in two + * stages (e.g., for recursive move of a directory). + * + * Returns: an attribute query string for g_file_query_info(), + * or %NULL if an error occurs. + * + * Since: 2.68 + */ +char * +g_file_build_attribute_list_for_copy (GFile *file, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; + char *ret = NULL; GFileAttributeInfoList *attributes = NULL, *namespaces = NULL; GString *s = NULL; gboolean first; @@ -2711,6 +2732,10 @@ build_attribute_list_for_copy (GFile *file, gboolean copy_all_attributes; gboolean skip_perms; + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + copy_all_attributes = flags & G_FILE_COPY_ALL_METADATA; skip_perms = (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) != 0; @@ -2764,8 +2789,7 @@ build_attribute_list_for_copy (GFile *file, } } - ret = TRUE; - *out_attributes = g_string_free (s, FALSE); + ret = g_string_free (s, FALSE); s = NULL; out: if (s) @@ -2811,8 +2835,9 @@ g_file_copy_attributes (GFile *source, GFileInfo *info; gboolean source_nofollow_symlinks; - if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read, - cancellable, error)) + attrs_to_read = g_file_build_attribute_list_for_copy (destination, flags, + cancellable, error); + if (!attrs_to_read) return FALSE; source_nofollow_symlinks = flags & G_FILE_COPY_NOFOLLOW_SYMLINKS; @@ -3158,6 +3183,7 @@ file_copy_fallback (GFile *source, char *attrs_to_read; gboolean do_set_attributes = FALSE; GFileCreateFlags create_flags; + GError *tmp_error = NULL; /* need to know the file type */ info = g_file_query_info (source, @@ -3199,47 +3225,43 @@ file_copy_fallback (GFile *source, goto out; in = G_INPUT_STREAM (file_in); - if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read, - cancellable, error)) + attrs_to_read = g_file_build_attribute_list_for_copy (destination, flags, + cancellable, error); + if (!attrs_to_read) goto out; - if (attrs_to_read != NULL) - { - GError *tmp_error = NULL; + /* Ok, ditch the previous lightweight info (on Unix we just + * called lstat()); at this point we gather all the information + * we need about the source from the opened file descriptor. + */ + g_object_unref (info); - /* Ok, ditch the previous lightweight info (on Unix we just - * called lstat()); at this point we gather all the information - * we need about the source from the opened file descriptor. + info = g_file_input_stream_query_info (file_in, attrs_to_read, + cancellable, &tmp_error); + if (!info) + { + /* Not all gvfs backends implement query_info_on_read(), we + * can just fall back to the pathname again. + * https://bugzilla.gnome.org/706254 */ - g_object_unref (info); - - info = g_file_input_stream_query_info (file_in, attrs_to_read, - cancellable, &tmp_error); - if (!info) + if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) { - /* Not all gvfs backends implement query_info_on_read(), we - * can just fall back to the pathname again. - * https://bugzilla.gnome.org/706254 - */ - if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) - { - g_clear_error (&tmp_error); - info = g_file_query_info (source, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); - } - else - { - g_free (attrs_to_read); - g_propagate_error (error, tmp_error); - goto out; - } + g_clear_error (&tmp_error); + info = g_file_query_info (source, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + } + else + { + g_free (attrs_to_read); + g_propagate_error (error, tmp_error); + goto out; } - g_free (attrs_to_read); - if (!info) - goto out; - - do_set_attributes = TRUE; } + g_free (attrs_to_read); + if (!info) + goto out; + + do_set_attributes = TRUE; /* In the local file path, we pass down the source info which * includes things like unix::mode, to ensure that the target file @@ -7558,7 +7580,6 @@ replace_contents_close_callback (GObject *obj, /* Ignore errors here, we're only reading anyway */ g_output_stream_close_finish (stream, close_res, NULL); - g_object_unref (stream); if (!data->failed) { @@ -7640,6 +7661,7 @@ replace_contents_open_callback (GObject *obj, g_task_get_cancellable (data->task), replace_contents_write_callback, data); + g_object_unref (stream); /* ownership is transferred to the write_async() call above */ } else { @@ -7872,7 +7894,7 @@ measure_disk_usage_progress (gboolean reporting, g_main_context_invoke_full (g_task_get_context (task), g_task_get_priority (task), measure_disk_usage_invoke_progress, - g_memdup2 (&progress, sizeof progress), + g_memdup (&progress, sizeof progress), g_free); } @@ -7890,7 +7912,7 @@ measure_disk_usage_thread (GTask *task, data->progress_callback ? measure_disk_usage_progress : NULL, task, &result.disk_usage, &result.num_dirs, &result.num_files, &error)) - g_task_return_pointer (task, g_memdup2 (&result, sizeof result), g_free); + g_task_return_pointer (task, g_memdup (&result, sizeof result), g_free); else g_task_return_error (task, error); } @@ -7914,7 +7936,7 @@ g_file_real_measure_disk_usage_async (GFile *file, task = g_task_new (file, cancellable, callback, user_data); g_task_set_source_tag (task, g_file_real_measure_disk_usage_async); - g_task_set_task_data (task, g_memdup2 (&data, sizeof data), g_free); + g_task_set_task_data (task, g_memdup (&data, sizeof data), g_free); g_task_set_priority (task, io_priority); g_task_run_in_thread (task, measure_disk_usage_thread); diff --git a/gio/gfile.h b/gio/gfile.h index 8b6d08385..4cff1a372 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -1094,6 +1094,12 @@ gboolean g_file_eject_mountable_with_operation_finish (GFile GAsyncResult *result, GError **error); +GLIB_AVAILABLE_IN_2_68 +char * g_file_build_attribute_list_for_copy (GFile *file, + GFileCopyFlags flags, + GCancellable *cancellable, + GError **error); + GLIB_AVAILABLE_IN_ALL gboolean g_file_copy_attributes (GFile *source, GFile *destination, diff --git a/gio/gio-tool-info.c b/gio/gio-tool-info.c index a06263545..7cf568370 100644 --- a/gio/gio-tool-info.c +++ b/gio/gio-tool-info.c @@ -182,8 +182,7 @@ show_info (GFile *file, GFileInfo *info) gchar *root_string = NULL; gchar *mount; gchar *fs; - const gchar *options; - gchar *options_string = NULL; + gchar *options; device = g_strescape (g_unix_mount_get_device_path (entry), NULL); root = g_unix_mount_get_root_path (entry); @@ -195,22 +194,16 @@ show_info (GFile *file, GFileInfo *info) } mount = g_strescape (g_unix_mount_get_mount_path (entry), NULL); fs = g_strescape (g_unix_mount_get_fs_type (entry), NULL); - - options = g_unix_mount_get_options (entry); - if (options != NULL) - { - options_string = g_strescape (options, NULL); - } + options = g_strescape (g_unix_mount_get_options (entry), NULL); g_print (_("unix mount: %s%s %s %s %s\n"), device, - root_string ? root_string : "", mount, fs, - options_string ? options_string : ""); + root_string ? root_string : "", mount, fs, options); g_free (device); g_free (root_string); g_free (mount); g_free (fs); - g_free (options_string); + g_free (options); g_unix_mount_free (entry); } diff --git a/gio/giomodule.c b/gio/giomodule.c index aaf46364c..dc4d6d3b3 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -30,7 +30,6 @@ #include "giomodule.h" #include "giomodule-priv.h" -#include "glib-private.h" #include "glocalfilemonitor.h" #include "gnativevolumemonitor.h" #include "gproxyresolver.h" @@ -807,9 +806,6 @@ _g_io_module_get_default_type (const gchar *extension_point, return G_TYPE_INVALID; } - /* It’s OK to query the environment here, even when running as setuid, because - * it only allows a choice between existing already-loaded modules. No new - * code is loaded based on the environment variable value. */ use_this = envvar ? g_getenv (envvar) : NULL; if (g_strcmp0 (use_this, "help") == 0) { @@ -959,9 +955,6 @@ _g_io_module_get_default (const gchar *extension_point, return NULL; } - /* It’s OK to query the environment here, even when running as setuid, because - * it only allows a choice between existing already-loaded modules. No new - * code is loaded based on the environment variable value. */ use_this = envvar ? g_getenv (envvar) : NULL; if (g_strcmp0 (use_this, "help") == 0) { @@ -1157,16 +1150,8 @@ static gchar * get_gio_module_dir (void) { gchar *module_dir; - gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) (); - - /* If running as setuid, loading modules from an arbitrary directory - * controlled by the unprivileged user who is running the program could allow - * for execution of arbitrary code (in constructors in modules). - * Don’t allow it. - * - * If a setuid program somehow needs to load additional GIO modules, it should - * explicitly call g_io_modules_scan_all_in_directory(). */ - module_dir = !is_setuid ? g_strdup (g_getenv ("GIO_MODULE_DIR")) : NULL; + + module_dir = g_strdup (g_getenv ("GIO_MODULE_DIR")); if (module_dir == NULL) { #ifdef G_OS_WIN32 @@ -1198,14 +1183,13 @@ _g_io_modules_ensure_loaded (void) if (!loaded_dirs) { - gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) (); gchar *module_dir; loaded_dirs = TRUE; scope = g_io_module_scope_new (G_IO_MODULE_SCOPE_BLOCK_DUPLICATES); - /* First load any overrides, extras (but not if running as setuid!) */ - module_path = !is_setuid ? g_getenv ("GIO_EXTRA_MODULES") : NULL; + /* First load any overrides, extras */ + module_path = g_getenv ("GIO_EXTRA_MODULES"); if (module_path) { gchar **paths; diff --git a/gio/giowin32-private.c b/gio/giowin32-private.c index 47e840805..7120ae0ea 100644 --- a/gio/giowin32-private.c +++ b/gio/giowin32-private.c @@ -16,12 +16,11 @@ * along with this library; if not, see <http://www.gnu.org/licenses/>. */ -#include "gstrfuncsprivate.h" -static gsize +static gssize g_utf16_len (const gunichar2 *str) { - gsize result; + gssize result; for (result = 0; str[0] != 0; str++, result++) ; @@ -32,20 +31,17 @@ g_utf16_len (const gunichar2 *str) static gunichar2 * g_wcsdup (const gunichar2 *str, gssize str_len) { - gsize str_len_unsigned; - gsize str_size; + gssize str_size; g_return_val_if_fail (str != NULL, NULL); - if (str_len < 0) - str_len_unsigned = g_utf16_len (str); - else - str_len_unsigned = (gsize) str_len; + if (str_len == -1) + str_len = g_utf16_len (str); - g_assert (str_len_unsigned <= G_MAXSIZE / sizeof (gunichar2) - 1); - str_size = (str_len_unsigned + 1) * sizeof (gunichar2); + g_assert (str_len <= G_MAXSIZE / sizeof (gunichar2) - 1); + str_size = (str_len + 1) * sizeof (gunichar2); - return g_memdup2 (str, str_size); + return g_memdup (str, str_size); } static const gunichar2 * diff --git a/gio/gkeyfilesettingsbackend.c b/gio/gkeyfilesettingsbackend.c index de216e615..cd5765afd 100644 --- a/gio/gkeyfilesettingsbackend.c +++ b/gio/gkeyfilesettingsbackend.c @@ -33,7 +33,6 @@ #include "gfilemonitor.h" #include "gsimplepermission.h" #include "gsettingsbackendinternal.h" -#include "gstrfuncsprivate.h" #include "giomodule-priv.h" #include "gportalsupport.h" @@ -146,8 +145,8 @@ convert_path (GKeyfileSettingsBackend *kfsb, gchar **group, gchar **basename) { - gsize key_len = strlen (key); - const gchar *last_slash; + gint key_len = strlen (key); + gint i; if (key_len < kfsb->prefix_len || memcmp (key, kfsb->prefix, kfsb->prefix_len) != 0) @@ -156,48 +155,38 @@ convert_path (GKeyfileSettingsBackend *kfsb, key_len -= kfsb->prefix_len; key += kfsb->prefix_len; - last_slash = strrchr (key, '/'); - - /* Disallow empty group names or key names */ - if (key_len == 0 || - (last_slash != NULL && - (*(last_slash + 1) == '\0' || - last_slash == key))) - return FALSE; + for (i = key_len; i >= 0; i--) + if (key[i] == '/') + break; if (kfsb->root_group) { /* if a root_group was specified, make sure the user hasn't given * a path that ghosts that group name */ - if (last_slash != NULL && (last_slash - key) == kfsb->root_group_len && memcmp (key, kfsb->root_group, last_slash - key) == 0) + if (i == kfsb->root_group_len && memcmp (key, kfsb->root_group, i) == 0) return FALSE; } else { /* if no root_group was given, ensure that the user gave a path */ - if (last_slash == NULL) + if (i == -1) return FALSE; } if (group) { - if (last_slash != NULL) + if (i >= 0) { - *group = g_memdup2 (key, (last_slash - key) + 1); - (*group)[(last_slash - key)] = '\0'; + *group = g_memdup (key, i + 1); + (*group)[i] = '\0'; } else *group = g_strdup (kfsb->root_group); } if (basename) - { - if (last_slash != NULL) - *basename = g_memdup2 (last_slash + 1, key_len - (last_slash - key)); - else - *basename = g_strdup (key); - } + *basename = g_memdup (key + i + 1, key_len - i); return TRUE; } diff --git a/gio/glocalfileinfo.c b/gio/glocalfileinfo.c index a4abef089..90fcb3336 100644 --- a/gio/glocalfileinfo.c +++ b/gio/glocalfileinfo.c @@ -1547,45 +1547,15 @@ win32_get_file_user_info (const gchar *filename, /* support for '.hidden' files */ G_LOCK_DEFINE_STATIC (hidden_cache); static GHashTable *hidden_cache; -static GSource *hidden_cache_source = NULL; /* Under the hidden_cache lock */ -static guint hidden_cache_ttl_secs = 5; -static guint hidden_cache_ttl_jitter_secs = 2; - -typedef struct -{ - GHashTable *hidden_files; - gint64 timestamp_secs; -} HiddenCacheData; static gboolean remove_from_hidden_cache (gpointer user_data) { - HiddenCacheData *data; - GHashTableIter iter; - gboolean retval; - gint64 timestamp_secs; - G_LOCK (hidden_cache); - timestamp_secs = g_source_get_time (hidden_cache_source) / G_USEC_PER_SEC; - - g_hash_table_iter_init (&iter, hidden_cache); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data)) - { - if (timestamp_secs > data->timestamp_secs + hidden_cache_ttl_secs) - g_hash_table_iter_remove (&iter); - } - - if (g_hash_table_size (hidden_cache) == 0) - { - g_clear_pointer (&hidden_cache_source, g_source_unref); - retval = G_SOURCE_REMOVE; - } - else - retval = G_SOURCE_CONTINUE; - + g_hash_table_remove (hidden_cache, user_data); G_UNLOCK (hidden_cache); - return retval; + return FALSE; } static GHashTable * @@ -1623,19 +1593,16 @@ read_hidden_file (const gchar *dirname) } static void -free_hidden_file_data (gpointer user_data) +maybe_unref_hash_table (gpointer data) { - HiddenCacheData *data = user_data; - - g_clear_pointer (&data->hidden_files, g_hash_table_unref); - g_free (data); + if (data != NULL) + g_hash_table_unref (data); } static gboolean file_is_hidden (const gchar *path, const gchar *basename) { - HiddenCacheData *data; gboolean result; gchar *dirname; gpointer table; @@ -1646,38 +1613,28 @@ file_is_hidden (const gchar *path, if G_UNLIKELY (hidden_cache == NULL) hidden_cache = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, free_hidden_file_data); + g_free, maybe_unref_hash_table); if (!g_hash_table_lookup_extended (hidden_cache, dirname, - NULL, (gpointer *) &data)) + NULL, &table)) { gchar *mydirname; - - data = g_new0 (HiddenCacheData, 1); - data->hidden_files = table = read_hidden_file (dirname); - data->timestamp_secs = g_get_monotonic_time () / G_USEC_PER_SEC; + GSource *remove_from_cache_source; g_hash_table_insert (hidden_cache, mydirname = g_strdup (dirname), - data); + table = read_hidden_file (dirname)); - if (!hidden_cache_source) - { - hidden_cache_source = - g_timeout_source_new_seconds (hidden_cache_ttl_secs + - hidden_cache_ttl_jitter_secs); - g_source_set_priority (hidden_cache_source, G_PRIORITY_DEFAULT); - g_source_set_name (hidden_cache_source, - "[gio] remove_from_hidden_cache"); - g_source_set_callback (hidden_cache_source, - remove_from_hidden_cache, - NULL, NULL); - g_source_attach (hidden_cache_source, - GLIB_PRIVATE_CALL (g_get_worker_context) ()); - } + remove_from_cache_source = g_timeout_source_new_seconds (5); + g_source_set_priority (remove_from_cache_source, G_PRIORITY_DEFAULT); + g_source_set_callback (remove_from_cache_source, + remove_from_hidden_cache, + mydirname, + NULL); + g_source_attach (remove_from_cache_source, + GLIB_PRIVATE_CALL (g_get_worker_context) ()); + g_source_unref (remove_from_cache_source); } - else - table = data->hidden_files; result = table != NULL && g_hash_table_contains (table, basename); diff --git a/gio/glocalfileoutputstream.c b/gio/glocalfileoutputstream.c index 4c512ea81..f34c3e439 100644 --- a/gio/glocalfileoutputstream.c +++ b/gio/glocalfileoutputstream.c @@ -63,12 +63,6 @@ #define O_BINARY 0 #endif -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#else -#define HAVE_O_CLOEXEC 1 -#endif - struct _GLocalFileOutputStreamPrivate { char *tmp_filename; char *original_filename; @@ -856,12 +850,11 @@ handle_overwrite_open (const char *filename, int res; int mode; int errsv; - gboolean replace_destination_set = (flags & G_FILE_CREATE_REPLACE_DESTINATION); mode = mode_from_flags_or_info (flags, reference_info); /* We only need read access to the original file if we are creating a backup. - * We also add O_CREAT to avoid a race if the file was just removed */ + * We also add O_CREATE to avoid a race if the file was just removed */ if (create_backup || readable) open_flags = O_RDWR | O_CREAT | O_BINARY; else @@ -884,22 +877,16 @@ handle_overwrite_open (const char *filename, /* Could be a symlink, or it could be a regular ELOOP error, * but then the next open will fail too. */ is_symlink = TRUE; - if (!replace_destination_set) - fd = g_open (filename, open_flags, mode); + fd = g_open (filename, open_flags, mode); } -#else /* if !O_NOFOLLOW */ +#else + fd = g_open (filename, open_flags, mode); + errsv = errno; /* This is racy, but we do it as soon as possible to minimize the race */ is_symlink = g_file_test (filename, G_FILE_TEST_IS_SYMLINK); - - if (!is_symlink || !replace_destination_set) - { - fd = g_open (filename, open_flags, mode); - errsv = errno; - } #endif - if (fd == -1 && - (!is_symlink || !replace_destination_set)) + if (fd == -1) { char *display_name = g_filename_display_name (filename); g_set_error (error, G_IO_ERROR, @@ -910,30 +897,15 @@ handle_overwrite_open (const char *filename, return -1; } - if (!is_symlink) - { - res = g_local_file_fstat (fd, - G_LOCAL_FILE_STAT_FIELD_TYPE | - G_LOCAL_FILE_STAT_FIELD_MODE | - G_LOCAL_FILE_STAT_FIELD_UID | - G_LOCAL_FILE_STAT_FIELD_GID | - G_LOCAL_FILE_STAT_FIELD_MTIME | - G_LOCAL_FILE_STAT_FIELD_NLINK, - G_LOCAL_FILE_STAT_FIELD_ALL, &original_stat); - errsv = errno; - } - else - { - res = g_local_file_lstat (filename, - G_LOCAL_FILE_STAT_FIELD_TYPE | - G_LOCAL_FILE_STAT_FIELD_MODE | - G_LOCAL_FILE_STAT_FIELD_UID | - G_LOCAL_FILE_STAT_FIELD_GID | - G_LOCAL_FILE_STAT_FIELD_MTIME | - G_LOCAL_FILE_STAT_FIELD_NLINK, - G_LOCAL_FILE_STAT_FIELD_ALL, &original_stat); - errsv = errno; - } + res = g_local_file_fstat (fd, + G_LOCAL_FILE_STAT_FIELD_TYPE | + G_LOCAL_FILE_STAT_FIELD_MODE | + G_LOCAL_FILE_STAT_FIELD_UID | + G_LOCAL_FILE_STAT_FIELD_GID | + G_LOCAL_FILE_STAT_FIELD_MTIME | + G_LOCAL_FILE_STAT_FIELD_NLINK, + G_LOCAL_FILE_STAT_FIELD_ALL, &original_stat); + errsv = errno; if (res != 0) { @@ -950,27 +922,16 @@ handle_overwrite_open (const char *filename, if (!S_ISREG (_g_stat_mode (&original_stat))) { if (S_ISDIR (_g_stat_mode (&original_stat))) - { - g_set_error_literal (error, - G_IO_ERROR, - G_IO_ERROR_IS_DIRECTORY, - _("Target file is a directory")); - goto err_out; - } - else if (!is_symlink || -#ifdef S_ISLNK - !S_ISLNK (_g_stat_mode (&original_stat)) -#else - FALSE -#endif - ) - { - g_set_error_literal (error, + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_IS_DIRECTORY, + _("Target file is a directory")); + else + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, _("Target file is not a regular file")); - goto err_out; - } + goto err_out; } if (etag != NULL) @@ -999,7 +960,7 @@ handle_overwrite_open (const char *filename, * to a backup file and rewrite the contents of the file. */ - if (replace_destination_set || + if ((flags & G_FILE_CREATE_REPLACE_DESTINATION) || (!(_g_stat_nlink (&original_stat) > 1) && !is_symlink)) { char *dirname, *tmp_filename; @@ -1018,7 +979,7 @@ handle_overwrite_open (const char *filename, /* try to keep permissions (unless replacing) */ - if (!replace_destination_set && + if ( ! (flags & G_FILE_CREATE_REPLACE_DESTINATION) && ( #ifdef HAVE_FCHOWN fchown (tmpfd, _g_stat_uid (&original_stat), _g_stat_gid (&original_stat)) == -1 || @@ -1053,8 +1014,7 @@ handle_overwrite_open (const char *filename, } } - if (fd >= 0) - g_close (fd, NULL); + g_close (fd, NULL); *temp_filename = tmp_filename; return tmpfd; } @@ -1160,7 +1120,7 @@ handle_overwrite_open (const char *filename, } } - if (replace_destination_set) + if (flags & G_FILE_CREATE_REPLACE_DESTINATION) { g_close (fd, NULL); @@ -1245,7 +1205,7 @@ _g_local_file_output_stream_replace (const char *filename, sync_on_close = FALSE; /* If the file doesn't exist, create it */ - open_flags = O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC; + open_flags = O_CREAT | O_EXCL | O_BINARY; if (readable) open_flags |= O_RDWR; else @@ -1275,11 +1235,8 @@ _g_local_file_output_stream_replace (const char *filename, set_error_from_open_errno (filename, error); return NULL; } -#if !defined(HAVE_O_CLOEXEC) && defined(F_SETFD) - else - fcntl (fd, F_SETFD, FD_CLOEXEC); -#endif - + + stream = g_object_new (G_TYPE_LOCAL_FILE_OUTPUT_STREAM, NULL); stream->priv->fd = fd; stream->priv->sync_on_close = sync_on_close; diff --git a/gio/gresource.c b/gio/gresource.c index 13632d971..b7222b8eb 100644 --- a/gio/gresource.c +++ b/gio/gresource.c @@ -32,8 +32,6 @@ #include <gio/gzlibdecompressor.h> #include <gio/gconverterinputstream.h> -#include "glib-private.h" - struct _GResource { int ref_count; @@ -161,7 +159,7 @@ G_DEFINE_BOXED_TYPE (GResource, g_resource, g_resource_ref, g_resource_unref) * replace resources in the program or library, without recompiling, for debugging or quick hacking and testing * purposes. Since GLib 2.50, it is possible to use the `G_RESOURCE_OVERLAYS` environment variable to selectively overlay * resources with replacements from the filesystem. It is a %G_SEARCHPATH_SEPARATOR-separated list of substitutions to perform - * during resource lookups. It is ignored when running in a setuid process. + * during resource lookups. * * A substitution has the form * @@ -332,13 +330,10 @@ g_resource_find_overlay (const gchar *path, if (g_once_init_enter (&overlay_dirs)) { - gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) (); const gchar * const *result; const gchar *envvar; - /* Don’t load overlays if setuid, as they could allow reading privileged - * files. */ - envvar = !is_setuid ? g_getenv ("G_RESOURCE_OVERLAYS") : NULL; + envvar = g_getenv ("G_RESOURCE_OVERLAYS"); if (envvar != NULL) { gchar **parts; diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index 1282c10a1..0b94f76f6 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -18,10 +18,8 @@ #include "config.h" -#include "glib-private.h" #include "gsettingsschema-internal.h" #include "gsettings.h" -#include "gstrfuncsprivate.h" #include "gvdb/gvdb-reader.h" #include "strinfo.c" @@ -345,7 +343,6 @@ initialise_schema_sources (void) */ if G_UNLIKELY (g_once_init_enter (&initialised)) { - gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) (); const gchar * const *dirs; const gchar *path; gchar **extra_schema_dirs; @@ -360,9 +357,7 @@ initialise_schema_sources (void) try_prepend_data_dir (g_get_user_data_dir ()); - /* Disallow loading extra schemas if running as setuid, as that could - * allow reading privileged files. */ - if (!is_setuid && (path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL) + if ((path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL) { extra_schema_dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0); for (i = 0; extra_schema_dirs[i]; i++); @@ -1072,9 +1067,9 @@ g_settings_schema_list_children (GSettingsSchema *schema) if (g_str_has_suffix (key, "/")) { - gsize length = strlen (key); + gint length = strlen (key); - strv[j] = g_memdup2 (key, length); + strv[j] = g_memdup (key, length); strv[j][length - 1] = '\0'; j++; } diff --git a/gio/gsocket.c b/gio/gsocket.c index 5a2468e0a..0f8f9259f 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -75,7 +75,6 @@ #include "gcredentialsprivate.h" #include "glibintl.h" #include "gioprivate.h" -#include "gstrfuncsprivate.h" /** * SECTION:gsocket @@ -170,7 +169,7 @@ static gboolean g_socket_datagram_based_condition_wait (GDatagramBased GError **error); static GSocketAddress * -cache_recv_address (GSocket *socket, struct sockaddr *native, size_t native_len); +cache_recv_address (GSocket *socket, struct sockaddr *native, int native_len); static gssize g_socket_receive_message_with_timeout (GSocket *socket, @@ -256,7 +255,7 @@ struct _GSocketPrivate struct { GSocketAddress *addr; struct sockaddr *native; - gsize native_len; + gint native_len; guint64 last_used; } recv_addr_cache[RECV_ADDR_CACHE_SIZE]; }; @@ -5264,14 +5263,14 @@ g_socket_send_messages_with_timeout (GSocket *socket, } static GSocketAddress * -cache_recv_address (GSocket *socket, struct sockaddr *native, size_t native_len) +cache_recv_address (GSocket *socket, struct sockaddr *native, int native_len) { GSocketAddress *saddr; gint i; guint64 oldest_time = G_MAXUINT64; gint oldest_index = 0; - if (native_len == 0) + if (native_len <= 0) return NULL; saddr = NULL; @@ -5279,7 +5278,7 @@ cache_recv_address (GSocket *socket, struct sockaddr *native, size_t native_len) { GSocketAddress *tmp = socket->priv->recv_addr_cache[i].addr; gpointer tmp_native = socket->priv->recv_addr_cache[i].native; - gsize tmp_native_len = socket->priv->recv_addr_cache[i].native_len; + gint tmp_native_len = socket->priv->recv_addr_cache[i].native_len; if (!tmp) continue; @@ -5309,7 +5308,7 @@ cache_recv_address (GSocket *socket, struct sockaddr *native, size_t native_len) g_free (socket->priv->recv_addr_cache[oldest_index].native); } - socket->priv->recv_addr_cache[oldest_index].native = g_memdup2 (native, native_len); + socket->priv->recv_addr_cache[oldest_index].native = g_memdup (native, native_len); socket->priv->recv_addr_cache[oldest_index].native_len = native_len; socket->priv->recv_addr_cache[oldest_index].addr = g_object_ref (saddr); socket->priv->recv_addr_cache[oldest_index].last_used = g_get_monotonic_time (); @@ -5457,9 +5456,6 @@ g_socket_receive_message_with_timeout (GSocket *socket, /* do it */ while (1) { - /* addrlen has to be of type int because that’s how WSARecvFrom() is defined */ - G_STATIC_ASSERT (sizeof addr <= G_MAXINT); - addrlen = sizeof addr; if (address) result = WSARecvFrom (socket->priv->fd, @@ -5479,10 +5475,10 @@ g_socket_receive_message_with_timeout (GSocket *socket, if (errsv == WSAEINTR) continue; - win32_unset_event_mask (socket, FD_READ); - if (errsv == WSAEWOULDBLOCK) { + win32_unset_event_mask (socket, FD_READ); + if (timeout_us != 0) { if (!block_on_timeout (socket, G_IO_IN, timeout_us, diff --git a/gio/gsocketclient.c b/gio/gsocketclient.c index c9943309c..ce3c186fb 100644 --- a/gio/gsocketclient.c +++ b/gio/gsocketclient.c @@ -24,6 +24,10 @@ #include "config.h" #include "gsocketclient.h" +#ifndef G_OS_WIN32 +#include <netinet/in.h> +#endif + #include <stdlib.h> #include <string.h> @@ -39,6 +43,7 @@ #include <gio/gioerror.h> #include <gio/gsocket.h> #include <gio/gnetworkaddress.h> +#include <gio/gnetworking.h> #include <gio/gnetworkservice.h> #include <gio/gproxy.h> #include <gio/gproxyresolver.h> @@ -142,6 +147,10 @@ create_socket (GSocketClient *client, if (client->priv->local_address) { +#ifdef IP_BIND_ADDRESS_NO_PORT + g_socket_set_option (socket, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, 1, NULL); +#endif + if (!g_socket_bind (socket, client->priv->local_address, FALSE, @@ -819,7 +828,7 @@ g_socket_client_class_init (GSocketClientClass *class) * multiple times (or not at all) for a given connectable (in * particular, if @client ends up attempting to connect to more than * one address). However, if @client emits the #GSocketClient::event - * signal at all for a given connectable, that it will always emit + * signal at all for a given connectable, then it will always emit * it with %G_SOCKET_CLIENT_COMPLETE when it is done. * * Note that there may be additional #GSocketClientEvent values in @@ -936,7 +945,7 @@ g_socket_client_class_init (GSocketClientClass *class) static void g_socket_client_emit_event (GSocketClient *client, - GSocketClientEvent event, + GSocketClientEvent event, GSocketConnectable *connectable, GIOStream *connection) { @@ -944,6 +953,72 @@ g_socket_client_emit_event (GSocketClient *client, event, connectable, connection); } +/* Originally, GSocketClient returned whatever error occured last. Turns + * out this doesn't work well in practice. Consider the following case: + * DNS returns an IPv4 and IPv6 address. First we'll connect() to the + * IPv4 address, and say that succeeds, but TLS is enabled and the TLS + * handshake fails. Then we try the IPv6 address and receive ENETUNREACH + * because IPv6 isn't supported. We wind up returning NETWORK_UNREACHABLE + * even though the address can be pinged and a TLS error would be more + * appropriate. So instead, we now try to return the error corresponding + * to the latest attempted GSocketClientEvent in the connection process. + * TLS errors take precedence over proxy errors, which take precedence + * over connect() errors, which take precedence over DNS errors. + * + * Note that the example above considers a sync codepath, but this is an + * issue for the async codepath too, where events and errors may occur + * in confusing orders. + */ +typedef struct +{ + GError *tmp_error; + GError *best_error; + GSocketClientEvent best_error_event; +} SocketClientErrorInfo; + +static SocketClientErrorInfo * +socket_client_error_info_new (void) +{ + return g_new0 (SocketClientErrorInfo, 1); +} + +static void +socket_client_error_info_free (SocketClientErrorInfo *info) +{ + g_assert (info->tmp_error == NULL); + g_clear_error (&info->best_error); + g_free (info); +} + +static void +consider_tmp_error (SocketClientErrorInfo *info, + GSocketClientEvent event) +{ + if (info->tmp_error == NULL) + return; + + /* If we ever add more GSocketClientEvents in the future, then we'll + * no longer be able to use >= for this comparison, because future + * events will compare greater than G_SOCKET_CLIENT_COMPLETE. Until + * then, this is convenient. Note G_SOCKET_CLIENT_RESOLVING is 0 so we + * need to use >= here or those errors would never be set. That means + * if we get two errors on the same GSocketClientEvent, we wind up + * preferring the last one, which is fine. + */ + g_assert (event <= G_SOCKET_CLIENT_COMPLETE); + if (event >= info->best_error_event) + { + g_clear_error (&info->best_error); + info->best_error = info->tmp_error; + info->tmp_error = NULL; + info->best_error_event = event; + } + else + { + g_clear_error (&info->tmp_error); + } +} + /** * g_socket_client_connect: * @client: a #GSocketClient. @@ -982,9 +1057,10 @@ g_socket_client_connect (GSocketClient *client, { GIOStream *connection = NULL; GSocketAddressEnumerator *enumerator = NULL; - GError *last_error, *tmp_error; + SocketClientErrorInfo *error_info; + gboolean ever_resolved = FALSE; - last_error = NULL; + error_info = socket_client_error_info_new (); if (can_use_proxy (client)) { @@ -1009,44 +1085,38 @@ g_socket_client_connect (GSocketClient *client, if (g_cancellable_is_cancelled (cancellable)) { - g_clear_error (error); - g_clear_error (&last_error); - g_cancellable_set_error_if_cancelled (cancellable, error); + g_clear_error (&error_info->best_error); + g_cancellable_set_error_if_cancelled (cancellable, &error_info->best_error); break; } - tmp_error = NULL; - g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVING, - connectable, NULL); + if (!ever_resolved) + { + g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVING, + connectable, NULL); + } address = g_socket_address_enumerator_next (enumerator, cancellable, - &tmp_error); + &error_info->tmp_error); + consider_tmp_error (error_info, G_SOCKET_CLIENT_RESOLVING); + if (!ever_resolved) + { + g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVED, + connectable, NULL); + ever_resolved = TRUE; + } if (address == NULL) { - if (tmp_error) - { - g_clear_error (&last_error); - g_propagate_error (error, tmp_error); - } - else if (last_error) - { - g_propagate_error (error, last_error); - } - else - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Unknown error on connect")); + /* Enumeration is finished. */ + g_assert (&error_info->best_error != NULL); break; } - g_socket_client_emit_event (client, G_SOCKET_CLIENT_RESOLVED, - connectable, NULL); using_proxy = (G_IS_PROXY_ADDRESS (address) && client->priv->enable_proxy); - /* clear error from previous attempt */ - g_clear_error (&last_error); - - socket = create_socket (client, address, &last_error); + socket = create_socket (client, address, &error_info->tmp_error); + consider_tmp_error (error_info, G_SOCKET_CLIENT_CONNECTING); if (socket == NULL) { g_object_unref (address); @@ -1058,14 +1128,15 @@ g_socket_client_connect (GSocketClient *client, g_socket_client_emit_event (client, G_SOCKET_CLIENT_CONNECTING, connectable, connection); if (g_socket_connection_connect (G_SOCKET_CONNECTION (connection), - address, cancellable, &last_error)) + address, cancellable, &error_info->tmp_error)) { g_socket_connection_set_cached_remote_address ((GSocketConnection*)connection, NULL); g_socket_client_emit_event (client, G_SOCKET_CLIENT_CONNECTED, connectable, connection); } else { - clarify_connect_error (last_error, connectable, address); + clarify_connect_error (error_info->tmp_error, connectable, address); + consider_tmp_error (error_info, G_SOCKET_CLIENT_CONNECTING); g_object_unref (connection); connection = NULL; } @@ -1086,9 +1157,10 @@ g_socket_client_connect (GSocketClient *client, g_critical ("Trying to proxy over non-TCP connection, this is " "most likely a bug in GLib IO library."); - g_set_error_literal (&last_error, + g_set_error_literal (&error_info->tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Proxying over a non-TCP connection is not supported.")); + consider_tmp_error (error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING); g_object_unref (connection); connection = NULL; @@ -1106,7 +1178,9 @@ g_socket_client_connect (GSocketClient *client, connection, proxy_addr, cancellable, - &last_error); + &error_info->tmp_error); + consider_tmp_error (error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING); + g_object_unref (connection); connection = proxy_connection; g_object_unref (proxy); @@ -1116,9 +1190,10 @@ g_socket_client_connect (GSocketClient *client, } else { - g_set_error (&last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + g_set_error (&error_info->tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Proxy protocol “%s” is not supported."), protocol); + consider_tmp_error (error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING); g_object_unref (connection); connection = NULL; } @@ -1128,7 +1203,7 @@ g_socket_client_connect (GSocketClient *client, { GIOStream *tlsconn; - tlsconn = g_tls_client_connection_new (connection, connectable, &last_error); + tlsconn = g_tls_client_connection_new (connection, connectable, &error_info->tmp_error); g_object_unref (connection); connection = tlsconn; @@ -1138,16 +1213,21 @@ g_socket_client_connect (GSocketClient *client, client->priv->tls_validation_flags); g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKING, connectable, connection); if (g_tls_connection_handshake (G_TLS_CONNECTION (tlsconn), - cancellable, &last_error)) + cancellable, &error_info->tmp_error)) { g_socket_client_emit_event (client, G_SOCKET_CLIENT_TLS_HANDSHAKED, connectable, connection); } else { + consider_tmp_error (error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING); g_object_unref (tlsconn); connection = NULL; } } + else + { + consider_tmp_error (error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING); + } } if (connection && !G_IS_SOCKET_CONNECTION (connection)) @@ -1164,6 +1244,10 @@ g_socket_client_connect (GSocketClient *client, } g_object_unref (enumerator); + if (!connection) + g_propagate_error (error, g_steal_pointer (&error_info->best_error)); + socket_client_error_info_free (error_info); + g_socket_client_emit_event (client, G_SOCKET_CLIENT_COMPLETE, connectable, connection); return G_SOCKET_CONNECTION (connection); } @@ -1341,7 +1425,7 @@ typedef struct GSList *connection_attempts; GSList *successful_connections; - GError *last_error; + SocketClientErrorInfo *error_info; gboolean enumerated_at_least_once; gboolean enumeration_completed; @@ -1361,7 +1445,7 @@ g_socket_client_async_connect_data_free (GSocketClientAsyncConnectData *data) g_slist_free_full (data->connection_attempts, connection_attempt_unref); g_slist_free_full (data->successful_connections, connection_attempt_unref); - g_clear_error (&data->last_error); + g_clear_pointer (&data->error_info, socket_client_error_info_free); g_slice_free (GSocketClientAsyncConnectData, data); } @@ -1484,14 +1568,6 @@ g_socket_client_enumerator_callback (GObject *object, gpointer user_data); static void -set_last_error (GSocketClientAsyncConnectData *data, - GError *error) -{ - g_clear_error (&data->last_error); - data->last_error = error; -} - -static void enumerator_next_async (GSocketClientAsyncConnectData *data, gboolean add_task_ref) { @@ -1500,7 +1576,8 @@ enumerator_next_async (GSocketClientAsyncConnectData *data, if (add_task_ref) g_object_ref (data->task); - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL); + if (!data->enumerated_at_least_once) + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVING, data->connectable, NULL); g_debug ("GSocketClient: Starting new address enumeration"); g_socket_address_enumerator_next_async (data->enumerator, data->enumeration_cancellable, @@ -1520,7 +1597,7 @@ g_socket_client_tls_handshake_callback (GObject *object, if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (object), result, - &data->last_error)) + &data->error_info->tmp_error)) { g_object_unref (attempt->connection); attempt->connection = G_IO_STREAM (object); @@ -1533,7 +1610,9 @@ g_socket_client_tls_handshake_callback (GObject *object, { g_object_unref (object); connection_attempt_unref (attempt); - g_debug ("GSocketClient: TLS handshake failed: %s", data->last_error->message); + + g_debug ("GSocketClient: TLS handshake failed: %s", data->error_info->tmp_error->message); + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING); try_next_connection_or_finish (data, TRUE); } } @@ -1553,7 +1632,7 @@ g_socket_client_tls_handshake (ConnectionAttempt *attempt) g_debug ("GSocketClient: Starting TLS handshake"); tlsconn = g_tls_client_connection_new (attempt->connection, data->connectable, - &data->last_error); + &data->error_info->tmp_error); if (tlsconn) { g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (tlsconn), @@ -1568,6 +1647,8 @@ g_socket_client_tls_handshake (ConnectionAttempt *attempt) else { connection_attempt_unref (attempt); + + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_TLS_HANDSHAKING); try_next_connection_or_finish (data, TRUE); } } @@ -1583,19 +1664,19 @@ g_socket_client_proxy_connect_callback (GObject *object, g_object_unref (attempt->connection); attempt->connection = g_proxy_connect_finish (G_PROXY (object), result, - &data->last_error); + &data->error_info->tmp_error); if (attempt->connection) { g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_PROXY_NEGOTIATED, data->connectable, attempt->connection); + g_socket_client_tls_handshake (attempt); } else { connection_attempt_unref (attempt); + + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING); try_next_connection_or_finish (data, TRUE); - return; } - - g_socket_client_tls_handshake (attempt); } static void @@ -1663,9 +1744,10 @@ try_next_successful_connection (GSocketClientAsyncConnectData *data) g_critical ("Trying to proxy over non-TCP connection, this is " "most likely a bug in GLib IO library."); - g_set_error_literal (&data->last_error, + g_set_error_literal (&data->error_info->tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Proxying over a non-TCP connection is not supported.")); + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING); } else if (g_hash_table_contains (data->client->priv->app_proxies, protocol)) { @@ -1692,11 +1774,10 @@ try_next_successful_connection (GSocketClientAsyncConnectData *data) } else { - g_clear_error (&data->last_error); - - g_set_error (&data->last_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + g_set_error (&data->error_info->tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Proxy protocol “%s” is not supported."), protocol); + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_PROXY_NEGOTIATING); } data->connection_in_progress = FALSE; @@ -1727,7 +1808,7 @@ try_next_connection_or_finish (GSocketClientAsyncConnectData *data, return; } - complete_connection_with_error (data, data->last_error); + complete_connection_with_error (data, g_steal_pointer (&data->error_info->best_error)); } static void @@ -1737,7 +1818,6 @@ g_socket_client_connected_callback (GObject *source, { ConnectionAttempt *attempt = user_data; GSocketClientAsyncConnectData *data = attempt->data; - GError *error = NULL; if (task_completed_or_cancelled (data) || g_cancellable_is_cancelled (attempt->cancellable)) { @@ -1753,20 +1833,20 @@ g_socket_client_connected_callback (GObject *source, } if (!g_socket_connection_connect_finish (G_SOCKET_CONNECTION (source), - result, &error)) + result, &data->error_info->tmp_error)) { if (!g_cancellable_is_cancelled (attempt->cancellable)) { - clarify_connect_error (error, data->connectable, attempt->address); - set_last_error (data, error); - g_debug ("GSocketClient: Connection attempt failed: %s", error->message); + clarify_connect_error (data->error_info->tmp_error, data->connectable, attempt->address); + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_CONNECTING); + g_debug ("GSocketClient: Connection attempt failed: %s", data->error_info->tmp_error->message); connection_attempt_remove (attempt); connection_attempt_unref (attempt); try_next_connection_or_finish (data, FALSE); } else /* Silently ignore cancelled attempts */ { - g_clear_error (&error); + g_clear_error (&data->error_info->tmp_error); g_object_unref (data->task); connection_attempt_unref (attempt); } @@ -1824,7 +1904,6 @@ g_socket_client_enumerator_callback (GObject *object, GSocketAddress *address = NULL; GSocket *socket; ConnectionAttempt *attempt; - GError *error = NULL; if (task_completed_or_cancelled (data)) { @@ -1833,7 +1912,7 @@ g_socket_client_enumerator_callback (GObject *object, } address = g_socket_address_enumerator_next_finish (data->enumerator, - result, &error); + result, &data->error_info->tmp_error); if (address == NULL) { if (G_UNLIKELY (data->enumeration_completed)) @@ -1842,7 +1921,7 @@ g_socket_client_enumerator_callback (GObject *object, data->enumeration_completed = TRUE; g_debug ("GSocketClient: Address enumeration completed (out of addresses)"); - /* As per API docs: We only care about error if its the first call, + /* As per API docs: We only care about error if it's the first call, after that the enumerator is done. Note that we don't care about cancellation errors because @@ -1853,20 +1932,11 @@ g_socket_client_enumerator_callback (GObject *object, if ((data->enumerated_at_least_once && !data->connection_attempts && !data->connection_in_progress) || !data->enumerated_at_least_once) { - g_debug ("GSocketClient: Address enumeration failed: %s", error ? error->message : NULL); - if (data->last_error) - { - g_clear_error (&error); - error = data->last_error; - data->last_error = NULL; - } - else if (!error) - { - g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, - _("Unknown error on connect")); - } - - complete_connection_with_error (data, error); + g_debug ("GSocketClient: Address enumeration failed: %s", + data->error_info->tmp_error ? data->error_info->tmp_error->message : NULL); + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_RESOLVING); + g_assert (data->error_info->best_error); + complete_connection_with_error (data, g_steal_pointer (&data->error_info->best_error)); } /* Enumeration should never trigger again, drop our ref */ @@ -1874,17 +1944,19 @@ g_socket_client_enumerator_callback (GObject *object, return; } - data->enumerated_at_least_once = TRUE; g_debug ("GSocketClient: Address enumeration succeeded"); - g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED, - data->connectable, NULL); - - g_clear_error (&data->last_error); + if (!data->enumerated_at_least_once) + { + g_socket_client_emit_event (data->client, G_SOCKET_CLIENT_RESOLVED, + data->connectable, NULL); + data->enumerated_at_least_once = TRUE; + } - socket = create_socket (data->client, address, &data->last_error); + socket = create_socket (data->client, address, &data->error_info->tmp_error); if (socket == NULL) { g_object_unref (address); + consider_tmp_error (data->error_info, G_SOCKET_CLIENT_CONNECTING); enumerator_next_async (data, FALSE); return; } @@ -1927,6 +1999,15 @@ g_socket_client_enumerator_callback (GObject *object, * * This is the asynchronous version of g_socket_client_connect(). * + * You may wish to prefer the asynchronous version even in synchronous + * command line programs because, since 2.60, it implements + * [RFC 8305](https://tools.ietf.org/html/rfc8305) "Happy Eyeballs" + * recommendations to work around long connection timeouts in networks + * where IPv6 is broken by performing an IPv4 connection simultaneously + * without waiting for IPv6 to time out, which is not supported by the + * synchronous call. (This is not an API guarantee, and may change in + * the future.) + * * When the operation is finished @callback will be * called. You can then call g_socket_client_connect_finish() to get * the result of the operation. @@ -1947,6 +2028,7 @@ g_socket_client_connect_async (GSocketClient *client, data = g_slice_new0 (GSocketClientAsyncConnectData); data->client = client; data->connectable = g_object_ref (connectable); + data->error_info = socket_client_error_info_new (); if (can_use_proxy (client)) { diff --git a/gio/gsubprocesslauncher-private.h b/gio/gsubprocesslauncher-private.h index 64374a02d..95431a0ab 100644 --- a/gio/gsubprocesslauncher-private.h +++ b/gio/gsubprocesslauncher-private.h @@ -44,6 +44,7 @@ struct _GSubprocessLauncher GArray *basic_fd_assignments; GArray *needdup_fd_assignments; + gboolean closed_fd; GSpawnChildSetupFunc child_setup_func; gpointer child_setup_user_data; diff --git a/gio/gsubprocesslauncher.c b/gio/gsubprocesslauncher.c index 8c2f1c179..c27960949 100644 --- a/gio/gsubprocesslauncher.c +++ b/gio/gsubprocesslauncher.c @@ -46,6 +46,7 @@ #include "gioenumtypes.h" #include "gsubprocess.h" #include "ginitable.h" +#include "gioerror.h" #ifdef G_OS_UNIX #include <unistd.h> @@ -131,47 +132,27 @@ g_subprocess_launcher_set_property (GObject *object, guint prop_id, } static void -g_subprocess_launcher_finalize (GObject *object) +g_subprocess_launcher_dispose (GObject *object) { GSubprocessLauncher *self = G_SUBPROCESS_LAUNCHER (object); #ifdef G_OS_UNIX - guint i; - - g_free (self->stdin_path); - g_free (self->stdout_path); - g_free (self->stderr_path); - - if (self->stdin_fd != -1) - close (self->stdin_fd); + g_clear_pointer (&self->stdin_path, g_free); + g_clear_pointer (&self->stdout_path, g_free); + g_clear_pointer (&self->stderr_path, g_free); - if (self->stdout_fd != -1) - close (self->stdout_fd); - - if (self->stderr_fd != -1) - close (self->stderr_fd); - - if (self->basic_fd_assignments) - { - for (i = 0; i < self->basic_fd_assignments->len; i++) - (void) close (g_array_index (self->basic_fd_assignments, int, i)); - g_array_unref (self->basic_fd_assignments); - } - if (self->needdup_fd_assignments) - { - for (i = 0; i < self->needdup_fd_assignments->len; i += 2) - (void) close (g_array_index (self->needdup_fd_assignments, int, i)); - g_array_unref (self->needdup_fd_assignments); - } + g_subprocess_launcher_close (self); if (self->child_setup_destroy_notify) (* self->child_setup_destroy_notify) (self->child_setup_user_data); + self->child_setup_destroy_notify = NULL; + self->child_setup_user_data = NULL; #endif - g_strfreev (self->envp); - g_free (self->cwd); + g_clear_pointer (&self->envp, g_strfreev); + g_clear_pointer (&self->cwd, g_free); - G_OBJECT_CLASS (g_subprocess_launcher_parent_class)->finalize (object); + G_OBJECT_CLASS (g_subprocess_launcher_parent_class)->dispose (object); } static void @@ -194,7 +175,7 @@ g_subprocess_launcher_class_init (GSubprocessLauncherClass *class) GObjectClass *gobject_class = G_OBJECT_CLASS (class); gobject_class->set_property = g_subprocess_launcher_set_property; - gobject_class->finalize = g_subprocess_launcher_finalize; + gobject_class->dispose = g_subprocess_launcher_dispose; g_object_class_install_property (gobject_class, 1, g_param_spec_flags ("flags", "Flags", "GSubprocessFlags for launched processes", @@ -634,13 +615,69 @@ g_subprocess_launcher_take_fd (GSubprocessLauncher *self, { if (source_fd == target_fd) { - g_array_append_val (self->basic_fd_assignments, source_fd); + if (self->basic_fd_assignments) + g_array_append_val (self->basic_fd_assignments, source_fd); } else { - g_array_append_val (self->needdup_fd_assignments, source_fd); - g_array_append_val (self->needdup_fd_assignments, target_fd); + if (self->needdup_fd_assignments) + { + g_array_append_val (self->needdup_fd_assignments, source_fd); + g_array_append_val (self->needdup_fd_assignments, target_fd); + } + } +} + +/** + * g_subprocess_launcher_close: + * @self: a #GSubprocessLauncher + * + * Closes all the file descriptors previously passed to the object with + * g_subprocess_launcher_take_fd(), g_subprocess_launcher_take_stderr_fd(), etc. + * + * After calling this method, any subsequent calls to g_subprocess_launcher_spawn() or g_subprocess_launcher_spawnv() will + * return %G_IO_ERROR_CLOSED. This method is idempotent if + * called more than once. + * + * This function is called automatically when the #GSubprocessLauncher + * is disposed, but is provided separately so that garbage collected + * language bindings can call it earlier to guarantee when FDs are closed. + * + * Since: 2.68 + */ +void +g_subprocess_launcher_close (GSubprocessLauncher *self) +{ + guint i; + + g_return_if_fail (G_IS_SUBPROCESS_LAUNCHER (self)); + + if (self->stdin_fd != -1) + close (self->stdin_fd); + self->stdin_fd = -1; + + if (self->stdout_fd != -1) + close (self->stdout_fd); + self->stdout_fd = -1; + + if (self->stderr_fd != -1) + close (self->stderr_fd); + self->stderr_fd = -1; + + if (self->basic_fd_assignments) + { + for (i = 0; i < self->basic_fd_assignments->len; i++) + (void) close (g_array_index (self->basic_fd_assignments, int, i)); + g_clear_pointer (&self->basic_fd_assignments, g_array_unref); } + if (self->needdup_fd_assignments) + { + for (i = 0; i < self->needdup_fd_assignments->len; i += 2) + (void) close (g_array_index (self->needdup_fd_assignments, int, i)); + g_clear_pointer (&self->needdup_fd_assignments, g_array_unref); + } + + self->closed_fd = TRUE; } /** @@ -745,6 +782,17 @@ g_subprocess_launcher_spawnv (GSubprocessLauncher *launcher, g_return_val_if_fail (argv != NULL && argv[0] != NULL && argv[0][0] != '\0', NULL); +#ifdef G_OS_UNIX + if (launcher->closed_fd) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_CLOSED, + "Can't spawn a new child because a passed file descriptor has been closed."); + return NULL; + } +#endif + subprocess = g_object_new (G_TYPE_SUBPROCESS, "argv", argv, "flags", launcher->flags, diff --git a/gio/gsubprocesslauncher.h b/gio/gsubprocesslauncher.h index 05d83f131..0654c2b70 100644 --- a/gio/gsubprocesslauncher.h +++ b/gio/gsubprocesslauncher.h @@ -103,6 +103,9 @@ void g_subprocess_launcher_take_fd (GSubpro gint source_fd, gint target_fd); +GLIB_AVAILABLE_IN_2_68 +void g_subprocess_launcher_close (GSubprocessLauncher *self); + /* Child setup, only available on UNIX */ GLIB_AVAILABLE_IN_2_40 void g_subprocess_launcher_set_child_setup (GSubprocessLauncher *self, diff --git a/gio/gtlscertificate.c b/gio/gtlscertificate.c index 72de5eb1f..0668d4905 100644 --- a/gio/gtlscertificate.c +++ b/gio/gtlscertificate.c @@ -60,7 +60,9 @@ enum PROP_CERTIFICATE_PEM, PROP_PRIVATE_KEY, PROP_PRIVATE_KEY_PEM, - PROP_ISSUER + PROP_ISSUER, + PROP_PKCS11_URI, + PROP_PRIVATE_KEY_PKCS11_URI, }; static void @@ -74,7 +76,16 @@ g_tls_certificate_get_property (GObject *object, GValue *value, GParamSpec *pspec) { - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + switch (prop_id) + { + case PROP_PKCS11_URI: + case PROP_PRIVATE_KEY_PKCS11_URI: + /* Subclasses must override this property but this allows older backends to not fatally error */ + g_value_set_static_string (value, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } } static void @@ -83,7 +94,15 @@ g_tls_certificate_set_property (GObject *object, const GValue *value, GParamSpec *pspec) { - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + switch (prop_id) + { + case PROP_PKCS11_URI: + case PROP_PRIVATE_KEY_PKCS11_URI: + /* Subclasses must override this property but this allows older backends to not fatally error */ + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } } static void @@ -193,6 +212,42 @@ g_tls_certificate_class_init (GTlsCertificateClass *class) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GTlsCertificate:pkcs11-uri: (nullable) + * + * A URI referencing the PKCS \#11 objects containing an X.509 certificate + * and optionally a private key. + * + * If %NULL the certificate is either not backed by PKCS \#11 or the + * #GTlsBackend does not support PKCS \#11. + * + * Since: 2.68 + */ + g_object_class_install_property (gobject_class, PROP_PKCS11_URI, + g_param_spec_string ("pkcs11-uri", + P_("PKCS #11 URI"), + P_("The PKCS #11 URI"), + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + /** + * GTlsCertificate:private-key-pkcs11-uri: (nullable) + * + * A URI referencing a PKCS \#11 object containing a private key. + * + * Since: 2.68 + */ + g_object_class_install_property (gobject_class, PROP_PRIVATE_KEY_PKCS11_URI, + g_param_spec_string ("private-key-pkcs11-uri", + P_("PKCS #11 URI"), + P_("The PKCS #11 URI for a private key"), + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); } static GTlsCertificate * @@ -592,6 +647,77 @@ g_tls_certificate_new_from_files (const gchar *cert_file, } /** + * g_tls_certificate_new_from_pkcs11_uris: + * @pkcs11_uri: A PKCS \#11 URI + * @private_key_pkcs11_uri: (nullable): A PKCS \#11 URI + * @error: #GError for error reporting, or %NULL to ignore. + * + * Creates a #GTlsCertificate from a PKCS \#11 URI. + * + * An example @pkcs11_uri would be `pkcs11:model=Model;manufacturer=Manufacture;serial=1;token=My%20Client%20Certificate;id=%01` + * + * Where the token’s layout is: + * + * ``` + * Object 0: + * URL: pkcs11:model=Model;manufacturer=Manufacture;serial=1;token=My%20Client%20Certificate;id=%01;object=private%20key;type=private + * Type: Private key (RSA-2048) + * ID: 01 + * + * Object 1: + * URL: pkcs11:model=Model;manufacturer=Manufacture;serial=1;token=My%20Client%20Certificate;id=%01;object=Certificate%20for%20Authentication;type=cert + * Type: X.509 Certificate (RSA-2048) + * ID: 01 + * ``` + * + * In this case the certificate and private key would both be detected and used as expected. + * @pkcs_uri may also just reference an X.509 certificate object and then optionally + * @private_key_pkcs11_uri allows using a private key exposed under a different URI. + * + * Note that the private key is not accessed until usage and may fail or require a PIN later. + * + * Returns: (transfer full): the new certificate, or %NULL on error + * + * Since: 2.68 + */ +GTlsCertificate * +g_tls_certificate_new_from_pkcs11_uris (const gchar *pkcs11_uri, + const gchar *private_key_pkcs11_uri, + GError **error) +{ + GObject *cert; + GTlsBackend *backend; + + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + g_return_val_if_fail (pkcs11_uri, NULL); + + backend = g_tls_backend_get_default (); + + cert = g_initable_new (g_tls_backend_get_certificate_type (backend), + NULL, error, + "pkcs11-uri", pkcs11_uri, + "private-key-pkcs11-uri", private_key_pkcs11_uri, + NULL); + + if (cert != NULL) + { + gchar *objects_uri; + + /* Old implementations might not override this property */ + g_object_get (cert, "pkcs11-uri", &objects_uri, NULL); + if (objects_uri == NULL) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("This GTlsBackend does not support creating PKCS #11 certificates")); + g_object_unref (cert); + return NULL; + } + g_free (objects_uri); + } + + return G_TLS_CERTIFICATE (cert); +} + +/** * g_tls_certificate_list_new_from_file: * @file: (type filename): file containing PEM-encoded certificates to import * @error: #GError for error reporting, or %NULL to ignore. diff --git a/gio/gtlscertificate.h b/gio/gtlscertificate.h index a064543c4..ead4f015e 100644 --- a/gio/gtlscertificate.h +++ b/gio/gtlscertificate.h @@ -71,6 +71,11 @@ GLIB_AVAILABLE_IN_ALL GTlsCertificate *g_tls_certificate_new_from_files (const gchar *cert_file, const gchar *key_file, GError **error); +GLIB_AVAILABLE_IN_2_68 +GTlsCertificate *g_tls_certificate_new_from_pkcs11_uris (const gchar *pkcs11_uri, + const gchar *private_key_pkcs11_uri, + GError **error); + GLIB_AVAILABLE_IN_ALL GList *g_tls_certificate_list_new_from_file (const gchar *file, GError **error); diff --git a/gio/gtlspassword.c b/gio/gtlspassword.c index bd86a6dfe..1e437a7b6 100644 --- a/gio/gtlspassword.c +++ b/gio/gtlspassword.c @@ -23,7 +23,6 @@ #include "glibintl.h" #include "gioenumtypes.h" -#include "gstrfuncsprivate.h" #include "gtlspassword.h" #include <string.h> @@ -288,14 +287,9 @@ g_tls_password_set_value (GTlsPassword *password, g_return_if_fail (G_IS_TLS_PASSWORD (password)); if (length < 0) - { - /* FIXME: g_tls_password_set_value_full() doesn’t support unsigned gsize */ - gsize length_unsigned = strlen ((gchar *) value); - g_return_if_fail (length_unsigned <= G_MAXSSIZE); - length = (gssize) length_unsigned; - } + length = strlen ((gchar *)value); - g_tls_password_set_value_full (password, g_memdup2 (value, (gsize) length), length, g_free); + g_tls_password_set_value_full (password, g_memdup (value, length), length, g_free); } /** diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index ec3e2b70c..612373a6a 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -35,6 +35,9 @@ #include <windows.h> +#include <glib/gstdioprivate.h> +#include "glib-private.h" + /* We need to watch 8 places: * 0) HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations * (anything below that key) @@ -55,15 +58,73 @@ * 7) HKEY_CLASSES_ROOT (only its subkeys) * On change: re-enumerate subkeys, try to filter out wrong names. * + * + * About verbs. A registry key (the name of that key is known as ProgID) + * can contain a "shell" subkey, which can then contain a number of verb + * subkeys (the most common being the "open" verb), and each of these + * contains a "command" subkey, which has a default string value that + * is the command to be run. + * Most ProgIDs are in HKEY_CLASSES_ROOT, but some are nested deeper in + * the registry (such as HKEY_CURRENT_USER\\Software\\<softwarename>). + * + * Verb selection works like this (according to https://docs.microsoft.com/en-us/windows/win32/shell/context ): + * 1) If "open" verb is available, that verb is used. + * 2) If the Shell subkey has a default string value, and if a verb subkey + * with that name exists, that verb is used. + * 3) The first subkey found in the list of verb subkeys is used. + * 4) The "openwith" verb is used + * + * Testing suggests that Windows never reaches the point 4 in any realistic + * circumstances. If a "command" subkey is missing for a verb, or if it has + * an empty string as its default value, the app launch fails + * (the "openwith" verb is not used, even if it's present). + * If the command is present, but is not valid (runs nonexisting executable, + * for example), then other verbs are not checked. + * It seems that when the documentation said "openwith verb", it meant + * that Windows invokes the default "Open with..." dialog (it does not + * look at the "openwith" verb subkey, even if it's there). + * If a verb subkey that is supposed to be used is present, but it lacks + * a command subkey, an error message is shown and nothing else happens. */ +#define _verb_idx(array,index) ((GWin32AppInfoShellVerb *) g_ptr_array_index (array, index)) + +#define _lookup_by_verb(array, verb, dst, itemtype) do { \ + gsize _index; \ + itemtype *_v; \ + for (_index = 0; array && _index < array->len; _index++) \ + { \ + _v = (itemtype *) g_ptr_array_index (array, _index); \ + if (_wcsicmp (_v->verb_name, (verb)) == 0) \ + { \ + *(dst) = _v; \ + break; \ + } \ + } \ + if (array == NULL || _index >= array->len) \ + *(dst) = NULL; \ +} while (0) + +#define _verb_lookup(array, verb, dst) _lookup_by_verb (array, verb, dst, GWin32AppInfoShellVerb) + +/* Because with subcommands a verb would have + * a name like "foo\\bar", but the key its command + * should be looked for is "shell\\foo\\shell\\bar\\command" + */ +typedef struct _reg_verb { + gunichar2 *name; + gunichar2 *shellpath; +} reg_verb; + typedef struct _GWin32AppInfoURLSchema GWin32AppInfoURLSchema; typedef struct _GWin32AppInfoFileExtension GWin32AppInfoFileExtension; +typedef struct _GWin32AppInfoShellVerb GWin32AppInfoShellVerb; typedef struct _GWin32AppInfoHandler GWin32AppInfoHandler; typedef struct _GWin32AppInfoApplication GWin32AppInfoApplication; typedef struct _GWin32AppInfoURLSchemaClass GWin32AppInfoURLSchemaClass; typedef struct _GWin32AppInfoFileExtensionClass GWin32AppInfoFileExtensionClass; +typedef struct _GWin32AppInfoShellVerbClass GWin32AppInfoShellVerbClass; typedef struct _GWin32AppInfoHandlerClass GWin32AppInfoHandlerClass; typedef struct _GWin32AppInfoApplicationClass GWin32AppInfoApplicationClass; @@ -87,6 +148,11 @@ struct _GWin32AppInfoApplicationClass GObjectClass parent_class; }; +struct _GWin32AppInfoShellVerbClass +{ + GObjectClass parent_class; +}; + struct _GWin32AppInfoURLSchema { GObject parent_instance; @@ -97,45 +163,57 @@ struct _GWin32AppInfoURLSchema { gchar *schema_u8; /* url schema (stuff before ':'), in UTF-8, folded */ - gchar *schema_folded; + gchar *schema_u8_folded; - /* Handler currently selected for this schema */ + /* Handler currently selected for this schema. Can be NULL. */ GWin32AppInfoHandler *chosen_handler; - /* Maps folded handler IDs -> to other handlers for this schema */ + /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this schema. + * Includes the chosen handler, if any. + */ GHashTable *handlers; }; struct _GWin32AppInfoHandler { GObject parent_instance; - /* Class name in HKCR */ + /* Usually a class name in HKCR */ gunichar2 *handler_id; - /* Handler registry key (HKCR\\handler_id). Can be used to watch this handler. */ + /* Registry object obtained by opening @handler_id. Can be used to watch this handler. */ GWin32RegistryKey *key; - /* Class name in HKCR, UTF-8, folded */ + /* @handler_id, in UTF-8, folded */ gchar *handler_id_folded; - /* shell/open/command default value for the class named by class_id */ - gunichar2 *handler_command; + /* Icon of the application for this handler */ + GIcon *icon; + + /* Verbs that this handler supports */ + GPtrArray *verbs; /* of GWin32AppInfoShellVerb */ +}; - /* If handler_id class has no command, it might point to another class */ - gunichar2 *proxy_id; +struct _GWin32AppInfoShellVerb { + GObject parent_instance; - /* Proxy registry key (HKCR\\proxy_id). Can be used to watch handler's proxy. */ - GWin32RegistryKey *proxy_key; + /* The verb that is used to invoke this handler. */ + gunichar2 *verb_name; - /* shell/open/command default value for the class named by proxy_id */ - gunichar2 *proxy_command; + /* User-friendly (localized) verb name. */ + gchar *verb_displayname; - /* Executable of the program (for matching, in folded form; UTF-8) */ - gchar *executable_folded; + /* shell/verb/command */ + gunichar2 *command; + + /* Same as @command, but in UTF-8 */ + gchar *command_utf8; /* Executable of the program (UTF-8) */ gchar *executable; + /* Executable of the program (for matching, in folded form; UTF-8) */ + gchar *executable_folded; + /* Pointer to a location within @executable */ gchar *executable_basename; @@ -146,10 +224,7 @@ struct _GWin32AppInfoHandler { */ gchar *dll_function; - /* Icon of the application for this handler */ - GIcon *icon; - - /* The application that is linked to this handler. */ + /* The application that is linked to this verb. */ GWin32AppInfoApplication *app; }; @@ -162,28 +237,34 @@ struct _GWin32AppInfoFileExtension { /* File extension (with leading '.'), in UTF-8 */ gchar *extension_u8; - /* handler currently selected for this extension */ + /* handler currently selected for this extension. Can be NULL. */ GWin32AppInfoHandler *chosen_handler; - /* Maps folded handler IDs -> to other handlers for this extension */ + /* Maps folded handler IDs -> to GWin32AppInfoHandlers for this extension. + * Includes the chosen handler, if any. + */ GHashTable *handlers; - - /* Maps folded app exename -> to apps that support this extension. - * ONLY for apps that are not reachable via handlers (i.e. apps from - * the HKCR/Applications, which have no handlers). */ - GHashTable *other_apps; }; struct _GWin32AppInfoApplication { GObject parent_instance; - /* Canonical name (used for key names). Can be NULL. */ + /* Canonical name (used for key names). + * For applications tracked by id this is the root registry + * key path for the application. + * For applications tracked by executable name this is the + * basename of the executable. + * For fake applications this is the full filename of the + * executable (as far as it can be inferred from a command line, + * meaning that it can also be a basename, if that's + * all that a commandline happen to give us). + */ gunichar2 *canonical_name; - /* Canonical name (used for key names), in UTF-8. Can be NULL. */ + /* @canonical_name, in UTF-8 */ gchar *canonical_name_u8; - /* Canonical name (used for key names), in UTF-8, folded. Can be NULL. */ + /* @canonical_name, in UTF-8, folded */ gchar *canonical_name_folded; /* Human-readable name in English. Can be NULL */ @@ -204,37 +285,17 @@ struct _GWin32AppInfoApplication { /* Description, could be in user's language, UTF-8. Can be NULL */ gchar *description_u8; - /* shell/open/command for the application. Can be NULL (see executable). */ - gunichar2 *command; - - /* shell/open/command for the application. Can be NULL (see executable). */ - gchar *command_u8; - - /* Executable of the program (for matching, in folded form; - * UTF-8). Never NULL. */ - gchar *executable_folded; - - /* Executable of the program (UTF-8). Never NULL. */ - gchar *executable; - - /* Pointer to a location within @executable */ - gchar *executable_basename; - - /* If not NULL, then @executable and its derived fields contain the name - * of a DLL file (without the name of the function that rundll32.exe should - * invoke), and this field contains the name of the function to be invoked. - * The application is then invoked as 'rundll32.exe "dll_path",dll_function other_arguments...'. - */ - gchar *dll_function; + /* Verbs that this application supports */ + GPtrArray *verbs; /* of GWin32AppInfoShellVerb */ /* Explicitly supported URLs, hashmap from map-owned gchar ptr (schema, - * UTF-8, folded) -> a GWin32AppInfoHandler + * UTF-8, folded) -> to a GWin32AppInfoHandler * Schema can be used as a key in the urls hashmap. */ GHashTable *supported_urls; /* Explicitly supported extensions, hashmap from map-owned gchar ptr - * (.extension, UTF-8, folded) -> a GWin32AppInfoHandler + * (.extension, UTF-8, folded) -> to a GWin32AppInfoHandler * Extension can be used as a key in the extensions hashmap. */ GHashTable *supported_exts; @@ -272,13 +333,18 @@ struct _GWin32AppInfoApplication { #define G_TYPE_WIN32_APPINFO_APPLICATION (g_win32_appinfo_application_get_type ()) #define G_WIN32_APPINFO_APPLICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_APPLICATION, GWin32AppInfoApplication)) +#define G_TYPE_WIN32_APPINFO_SHELL_VERB (g_win32_appinfo_shell_verb_get_type ()) +#define G_WIN32_APPINFO_SHELL_VERB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_WIN32_APPINFO_SHELL_VERB, GWin32AppInfoShellVerb)) + GType g_win32_appinfo_url_schema_get_type (void) G_GNUC_CONST; GType g_win32_appinfo_file_extension_get_type (void) G_GNUC_CONST; +GType g_win32_appinfo_shell_verb_get_type (void) G_GNUC_CONST; GType g_win32_appinfo_handler_get_type (void) G_GNUC_CONST; GType g_win32_appinfo_application_get_type (void) G_GNUC_CONST; G_DEFINE_TYPE (GWin32AppInfoURLSchema, g_win32_appinfo_url_schema, G_TYPE_OBJECT) G_DEFINE_TYPE (GWin32AppInfoFileExtension, g_win32_appinfo_file_extension, G_TYPE_OBJECT) +G_DEFINE_TYPE (GWin32AppInfoShellVerb, g_win32_appinfo_shell_verb, G_TYPE_OBJECT) G_DEFINE_TYPE (GWin32AppInfoHandler, g_win32_appinfo_handler, G_TYPE_OBJECT) G_DEFINE_TYPE (GWin32AppInfoApplication, g_win32_appinfo_application, G_TYPE_OBJECT) @@ -289,7 +355,7 @@ g_win32_appinfo_url_schema_dispose (GObject *object) g_clear_pointer (&url->schema, g_free); g_clear_pointer (&url->schema_u8, g_free); - g_clear_pointer (&url->schema_folded, g_free); + g_clear_pointer (&url->schema_u8_folded, g_free); g_clear_object (&url->chosen_handler); g_clear_pointer (&url->handlers, g_hash_table_destroy); G_OBJECT_CLASS (g_win32_appinfo_url_schema_parent_class)->dispose (object); @@ -303,16 +369,9 @@ g_win32_appinfo_handler_dispose (GObject *object) g_clear_pointer (&handler->handler_id, g_free); g_clear_pointer (&handler->handler_id_folded, g_free); - g_clear_pointer (&handler->handler_command, g_free); - g_clear_pointer (&handler->proxy_id, g_free); - g_clear_pointer (&handler->proxy_command, g_free); - g_clear_pointer (&handler->executable_folded, g_free); - g_clear_pointer (&handler->executable, g_free); - g_clear_pointer (&handler->dll_function, g_free); g_clear_object (&handler->key); - g_clear_object (&handler->proxy_key); g_clear_object (&handler->icon); - g_clear_object (&handler->app); + g_clear_pointer (&handler->verbs, g_ptr_array_unref); G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object); } @@ -325,11 +384,26 @@ g_win32_appinfo_file_extension_dispose (GObject *object) g_clear_pointer (&ext->extension_u8, g_free); g_clear_object (&ext->chosen_handler); g_clear_pointer (&ext->handlers, g_hash_table_destroy); - g_clear_pointer (&ext->other_apps, g_hash_table_destroy); G_OBJECT_CLASS (g_win32_appinfo_file_extension_parent_class)->dispose (object); } static void +g_win32_appinfo_shell_verb_dispose (GObject *object) +{ + GWin32AppInfoShellVerb *shverb = G_WIN32_APPINFO_SHELL_VERB (object); + + g_clear_pointer (&shverb->verb_name, g_free); + g_clear_pointer (&shverb->verb_displayname, g_free); + g_clear_pointer (&shverb->command, g_free); + g_clear_pointer (&shverb->command_utf8, g_free); + g_clear_pointer (&shverb->executable_folded, g_free); + g_clear_pointer (&shverb->executable, g_free); + g_clear_pointer (&shverb->dll_function, g_free); + g_clear_object (&shverb->app); + G_OBJECT_CLASS (g_win32_appinfo_shell_verb_parent_class)->dispose (object); +} + +static void g_win32_appinfo_application_dispose (GObject *object) { GWin32AppInfoApplication *app = G_WIN32_APPINFO_APPLICATION (object); @@ -340,20 +414,28 @@ g_win32_appinfo_application_dispose (GObject *object) g_clear_pointer (&app->pretty_name, g_free); g_clear_pointer (&app->localized_pretty_name, g_free); g_clear_pointer (&app->description, g_free); - g_clear_pointer (&app->command, g_free); g_clear_pointer (&app->pretty_name_u8, g_free); g_clear_pointer (&app->localized_pretty_name_u8, g_free); g_clear_pointer (&app->description_u8, g_free); - g_clear_pointer (&app->command_u8, g_free); - g_clear_pointer (&app->executable_folded, g_free); - g_clear_pointer (&app->executable, g_free); - g_clear_pointer (&app->dll_function, g_free); g_clear_pointer (&app->supported_urls, g_hash_table_destroy); g_clear_pointer (&app->supported_exts, g_hash_table_destroy); g_clear_object (&app->icon); + g_clear_pointer (&app->verbs, g_ptr_array_unref); G_OBJECT_CLASS (g_win32_appinfo_application_parent_class)->dispose (object); } +static const gchar * +g_win32_appinfo_application_get_some_name (GWin32AppInfoApplication *app) +{ + if (app->localized_pretty_name_u8) + return app->localized_pretty_name_u8; + + if (app->pretty_name_u8) + return app->pretty_name_u8; + + return app->canonical_name_u8; +} + static void g_win32_appinfo_url_schema_class_init (GWin32AppInfoURLSchemaClass *klass) { @@ -371,6 +453,14 @@ g_win32_appinfo_file_extension_class_init (GWin32AppInfoFileExtensionClass *klas } static void +g_win32_appinfo_shell_verb_class_init (GWin32AppInfoShellVerbClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = g_win32_appinfo_shell_verb_dispose; +} + +static void g_win32_appinfo_handler_class_init (GWin32AppInfoHandlerClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); @@ -396,21 +486,23 @@ g_win32_appinfo_url_schema_init (GWin32AppInfoURLSchema *self) } static void +g_win32_appinfo_shell_verb_init (GWin32AppInfoShellVerb *self) +{ +} + +static void g_win32_appinfo_file_extension_init (GWin32AppInfoFileExtension *self) { self->handlers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - self->other_apps = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); } static void g_win32_appinfo_handler_init (GWin32AppInfoHandler *self) { + self->verbs = g_ptr_array_new_with_free_func (g_object_unref); } static void @@ -424,6 +516,7 @@ g_win32_appinfo_application_init (GWin32AppInfoApplication *self) g_str_equal, g_free, g_object_unref); + self->verbs = g_ptr_array_new_with_free_func (g_object_unref); } G_LOCK_DEFINE_STATIC (gio_win32_appinfo); @@ -439,20 +532,27 @@ static GHashTable *extensions = NULL; static GHashTable *urls = NULL; /* Map of owned "appID" (UTF-8, folded) to - * GWin32AppInfoApplication ptr + * a GWin32AppInfoApplication */ static GHashTable *apps_by_id = NULL; /* Map of owned "app.exe" (UTF-8, folded) to - * GWin32AppInfoApplication ptr. + * a GWin32AppInfoApplication. * This map and its values are separate from apps_by_id. The fact that an app * with known ID has the same executable [base]name as an app in this map does * not mean that they are the same application. */ static GHashTable *apps_by_exe = NULL; +/* Map of owned "path:\to\app.exe" (UTF-8, folded) to + * a GWin32AppInfoApplication. + * The app objects in this map are fake - they are linked to + * handlers that do not have any apps associated with them. + */ +static GHashTable *fake_apps = NULL; + /* Map of owned "handler id" (UTF-8, folded) - * to GWin32AppInfoHandler ptr + * to a GWin32AppInfoHandler */ static GHashTable *handlers = NULL; @@ -487,7 +587,6 @@ static GWin32RegistryKey *classes_root_key; #define HKCR L"HKEY_CLASSES_ROOT\\" #define HKCU L"HKEY_CURRENT_USER\\" #define HKLM L"HKEY_LOCAL_MACHINE\\" -#define SHELL_OPEN_COMMAND L"\\shell\\open\\command" #define REG_PATH_MAX 256 #define REG_PATH_MAX_SIZE (REG_PATH_MAX * sizeof (gunichar2)) @@ -498,56 +597,92 @@ static GWin32RegistryKey *classes_root_key; #include "giowin32-private.c" static void -read_handler_icon (GWin32RegistryKey *proxy_key, - GWin32RegistryKey *program_key, +read_handler_icon (GWin32RegistryKey *key, GIcon **icon_out) { - gint counter; - GWin32RegistryKey *key; + GWin32RegistryKey *icon_key; + GWin32RegistryValueType default_type; + gchar *default_value; + + g_assert (icon_out); *icon_out = NULL; - for (counter = 0; counter < 2; counter++) - { - GWin32RegistryKey *icon_key; + icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL); - if (counter == 0) - key = proxy_key; - else - key = program_key; + if (icon_key == NULL) + return; - if (!key) - continue; + if (g_win32_registry_key_get_value (icon_key, + NULL, + TRUE, + "", + &default_type, + (gpointer *) &default_value, + NULL, + NULL)) + { + if (default_type == G_WIN32_REGISTRY_VALUE_STR && + default_value[0] != '\0') + *icon_out = g_themed_icon_new (default_value); - icon_key = g_win32_registry_key_get_child_w (key, L"DefaultIcon", NULL); + g_clear_pointer (&default_value, g_free); + } - if (icon_key != NULL) - { - GWin32RegistryValueType default_type; - gchar *default_value; + g_object_unref (icon_key); +} - if (g_win32_registry_key_get_value (icon_key, - NULL, - TRUE, - "", - &default_type, - (gpointer *) &default_value, - NULL, - NULL)) - { - if (default_type == G_WIN32_REGISTRY_VALUE_STR || - default_value[0] != '\0') - *icon_out = g_themed_icon_new (default_value); +static void +reg_verb_free (gpointer p) +{ + if (p == NULL) + return; - g_clear_pointer (&default_value, g_free); - } + g_free (((reg_verb *) p)->name); + g_free (((reg_verb *) p)->shellpath); + g_free (p); +} - g_object_unref (icon_key); - } +#define is_open(x) ( \ + ((x)[0] == L'o' || (x)[0] == L'O') && \ + ((x)[1] == L'p' || (x)[1] == L'P') && \ + ((x)[2] == L'e' || (x)[2] == L'E') && \ + ((x)[3] == L'n' || (x)[3] == L'N') && \ + ((x)[4] == L'\0') \ +) + +/* default verb (if any) comes first, + * then "open", then the rest of the verbs + * are sorted alphabetically + */ +static gint +compare_verbs (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + const reg_verb *ca = (const reg_verb *) a; + const reg_verb *cb = (const reg_verb *) b; + const gunichar2 *def = (const gunichar2 *) user_data; + gboolean is_open_ca; + gboolean is_open_cb; - if (*icon_out) - break; + if (def != NULL) + { + if (_wcsicmp (ca->name, def) == 0) + return 1; + else if (_wcsicmp (cb->name, def) == 0) + return -1; } + + is_open_ca = is_open (ca->name); + is_open_cb = is_open (cb->name); + + if (is_open_ca && !is_open_cb) + return 1; + else if (is_open_ca && !is_open_cb) + return -1; + + return _wcsicmp (ca->name, cb->name); } static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) G_GNUC_NULL_TERMINATED; @@ -555,6 +690,64 @@ static gboolean build_registry_pathv (gunichar2 *output, gsize output_size, va_l static GWin32RegistryKey *_g_win32_registry_key_build_and_new_w (GError **error, ...) G_GNUC_NULL_TERMINATED; +/* Called by process_verbs_commands. + * @verb is a verb name + * @command_line is the commandline of that verb + * @command_line_utf8 is the UTF-8 version of @command_line + * @verb_displayname is the prettier display name of the verb (might be NULL) + * @verb_is_preferred is TRUE if the verb is the preferred one + * @invent_new_verb_name is TRUE when the verb should be added + * even if a verb with such + * name already exists (in which case + * a new name is invented), unless + * the existing verb runs exactly the same + * commandline. + */ +typedef void (*verb_command_func) (gpointer handler_data1, + gpointer handler_data2, + const gunichar2 *verb, + const gunichar2 *command_line, + const gchar *command_line_utf8, + const gchar *verb_displayname, + gboolean verb_is_preferred, + gboolean invent_new_verb_name); + +static gunichar2 * decide_which_id_to_use (const gunichar2 *program_id, + GWin32RegistryKey **return_key, + gchar **return_handler_id_u8, + gchar **return_handler_id_u8_folded); + +static GWin32AppInfoURLSchema * get_schema_object (const gunichar2 *schema, + const gchar *schema_u8, + const gchar *schema_u8_folded); + +static GWin32AppInfoHandler * get_handler_object (const gchar *handler_id_u8_folded, + GWin32RegistryKey *handler_key, + const gunichar2 *handler_id); + +static GWin32AppInfoFileExtension *get_ext_object (const gunichar2 *ext, + const gchar *ext_u8, + const gchar *ext_u8_folded); + + +static void process_verbs_commands (GList *verbs, + const reg_verb *preferred_verb, + const gunichar2 *path_to_progid, + const gunichar2 *progid, + gboolean autoprefer_first_verb, + verb_command_func handler, + gpointer handler_data1, + gpointer handler_data2); + +static void handler_add_verb (gpointer handler_data1, + gpointer handler_data2, + const gunichar2 *verb, + const gunichar2 *command_line, + const gchar *command_line_utf8, + const gchar *verb_displayname, + gboolean verb_is_preferred, + gboolean invent_new_verb_name); + /* output_size is in *bytes*, not gunichar2s! */ static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) @@ -632,589 +825,830 @@ _g_win32_registry_key_build_and_new_w (GError **error, ...) return key; } -/* Slow and dirty validator for UTF-16 strings */ -static gboolean -g_utf16_validate (const gunichar2 *str, - glong len) -{ - gchar *tmp; - - if (str == NULL) - return FALSE; - - tmp = g_utf16_to_utf8 (str, len, NULL, NULL, NULL); - - if (tmp == NULL) - return FALSE; - - g_free (tmp); - - return TRUE; -} - -/* Does a UTF-16 validity check on *proxy_command and/or *program_command. - * Fails if that check doesn't pass. +/* Gets the list of shell verbs (a GList of reg_verb, put into @verbs) + * from the @program_id_key. + * If one of the verbs should be preferred, + * a pointer to this verb (in the GList) will be + * put into @preferred_verb. + * Does not automatically assume that the first verb + * is preferred (when no other preferences exist). + * @verbname_prefix is prefixed to the name of the verb + * (this is used for subcommands) and is initially an + * empty string. + * @verbshell_prefix is the subkey of @program_id_key + * that contains the verbs. It is "Shell" initially, + * but grows with recursive invocations (for subcommands). + * Returns TRUE on success, FALSE on failure. */ static gboolean -follow_class_chain_to_handler (const gunichar2 *program_id, - gsize program_id_size, - gunichar2 **program_command, - GWin32RegistryKey **program_key, - gunichar2 **proxy_id, - gunichar2 **proxy_command, - GWin32RegistryKey **proxy_key, - gchar **program_id_u8, - gchar **program_id_folded) +get_verbs (GWin32RegistryKey *program_id_key, + const reg_verb **preferred_verb, + GList **verbs, + const gunichar2 *verbname_prefix, + const gunichar2 *verbshell_prefix) { + GWin32RegistrySubkeyIter iter; GWin32RegistryKey *key; GWin32RegistryValueType val_type; - gsize proxy_id_size; - gboolean got_value; - - g_assert (program_id && program_command && proxy_id && proxy_command); + gunichar2 *default_verb; + gsize verbshell_prefix_len; + gsize verbname_prefix_len; + GList *i; - *program_command = NULL; - *proxy_id = NULL; - *proxy_command = NULL; + g_assert (program_id_key && verbs && preferred_verb); - if (program_key) - *program_key = NULL; - - if (proxy_key) - *proxy_key = NULL; + *verbs = NULL; + *preferred_verb = NULL; + key = g_win32_registry_key_get_child_w (program_id_key, + verbshell_prefix, + NULL); - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id, - SHELL_OPEN_COMMAND, NULL); + if (key == NULL) + return FALSE; - if (key != NULL) + if (!g_win32_registry_subkey_iter_init (&iter, key, NULL)) { - got_value = g_win32_registry_key_get_value_w (key, - NULL, - TRUE, - L"", - &val_type, - (void **) program_command, - NULL, - NULL); - if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR) - { - if (((program_id_u8 != NULL || program_id_folded != NULL) && - !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) || - !g_utf16_validate (*program_command, -1)) - { - g_object_unref (key); - g_free (program_command); - - return FALSE; - } - if (program_key == NULL) - g_object_unref (key); - else - *program_key = key; - - return TRUE; - } - else if (got_value) - g_clear_pointer (program_command, g_free); - g_object_unref (key); + + return FALSE; } - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, program_id, NULL); + verbshell_prefix_len = g_utf16_len (verbshell_prefix); + verbname_prefix_len = g_utf16_len (verbname_prefix); - if (key == NULL) - return FALSE; + while (g_win32_registry_subkey_iter_next (&iter, TRUE, NULL)) + { + const gunichar2 *name; + gsize name_len; + GWin32RegistryKey *subkey; + gboolean has_subcommands; + const reg_verb *tmp; + GWin32RegistryValueType subc_type; + reg_verb *rverb; + const gunichar2 *shell = L"Shell"; + const gsize shell_len = g_utf16_len (shell); + + if (!g_win32_registry_subkey_iter_get_name_w (&iter, &name, &name_len, NULL)) + continue; - got_value = g_win32_registry_key_get_value_w (key, - NULL, - TRUE, - L"", - &val_type, - (void **) proxy_id, - &proxy_id_size, - NULL); - g_object_unref (key); + subkey = g_win32_registry_key_get_child_w (key, + name, + NULL); + + g_assert (subkey != NULL); + /* The key we're looking at is "<some_root>/Shell/<this_key>", + * where "Shell" is verbshell_prefix. + * If it has a value named 'Subcommands' (doesn't matter what its data is), + * it means that this key has its own Shell subkey, the subkeys + * of which are shell commands (i.e. <some_root>/Shell/<this_key>/Shell/<some_other_keys>). + * To handle that, create new, extended nameprefix and shellprefix, + * and call the function recursively. + * name prefix "" -> "<this_key_name>\\" + * shell prefix "Shell" -> "Shell\\<this_key_name>\\Shell" + * The root, program_id_key, remains the same in all invocations. + * Essentially, we're flattening the command tree into a list. + */ + has_subcommands = FALSE; + if (g_win32_registry_key_get_value_w (subkey, + NULL, + TRUE, + L"Subcommands", + &subc_type, + NULL, + NULL, + NULL) && + subc_type == G_WIN32_REGISTRY_VALUE_STR) + { + gunichar2 *new_nameprefix = g_malloc ((verbname_prefix_len + name_len + 1 + 1) * sizeof (gunichar2)); + gunichar2 *new_shellprefix = g_malloc ((verbshell_prefix_len + 1 + name_len + 1 + shell_len + 1) * sizeof (gunichar2)); + memcpy (&new_shellprefix[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2)); + new_shellprefix[verbshell_prefix_len] = L'\\'; + memcpy (&new_shellprefix[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2)); + new_shellprefix[verbshell_prefix_len + 1 + name_len] = L'\\'; + memcpy (&new_shellprefix[verbshell_prefix_len + 1 + name_len + 1], shell, shell_len * sizeof (gunichar2)); + new_shellprefix[verbshell_prefix_len + 1 + name_len + 1 + shell_len] = 0; + + memcpy (&new_nameprefix[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2)); + memcpy (&new_nameprefix[verbname_prefix_len], name, (name_len) * sizeof (gunichar2)); + new_nameprefix[verbname_prefix_len + name_len] = L'\\'; + new_nameprefix[verbname_prefix_len + name_len + 1] = 0; + has_subcommands = get_verbs (program_id_key, &tmp, verbs, new_nameprefix, new_shellprefix); + g_free (new_shellprefix); + g_free (new_nameprefix); + } - if (!got_value || - (val_type != G_WIN32_REGISTRY_VALUE_STR)) - { - g_clear_pointer (proxy_id, g_free); + g_clear_object (&subkey); - return FALSE; + /* Presence of subcommands means that this key itself is not a command-key */ + if (has_subcommands) + continue; + + /* We don't look at the command sub-key and its value (the actual command line) here. + * We save the registry path instead, and use it later in process_verbs_commands(). + * The name of the verb is also saved. + * verbname_prefix is prefixed to the verb name (it's either an empty string + * or already ends with a '\\', so no extra separators needed). + * verbshell_prefix is prefixed to the verb key path (this one needs a separator, + * because it never has one - all verbshell prefixes end with "Shell", not "Shell\\") + */ + rverb = g_new0 (reg_verb, 1); + rverb->name = g_malloc ((verbname_prefix_len + name_len + 1) * sizeof (gunichar2)); + memcpy (&rverb->name[0], verbname_prefix, verbname_prefix_len * sizeof (gunichar2)); + memcpy (&rverb->name[verbname_prefix_len], name, name_len * sizeof (gunichar2)); + rverb->name[verbname_prefix_len + name_len] = 0; + rverb->shellpath = g_malloc ((verbshell_prefix_len + 1 + name_len + 1) * sizeof (gunichar2)); + memcpy (&rverb->shellpath[0], verbshell_prefix, verbshell_prefix_len * sizeof (gunichar2)); + memcpy (&rverb->shellpath[verbshell_prefix_len], L"\\", sizeof (gunichar2)); + memcpy (&rverb->shellpath[verbshell_prefix_len + 1], name, name_len * sizeof (gunichar2)); + rverb->shellpath[verbshell_prefix_len + 1 + name_len] = 0; + *verbs = g_list_append (*verbs, rverb); } - key = _g_win32_registry_key_build_and_new_w (NULL, HKCR, *proxy_id, - SHELL_OPEN_COMMAND, NULL); + g_win32_registry_subkey_iter_clear (&iter); - if (key == NULL) + if (*verbs == NULL) { - g_clear_pointer (proxy_id, g_free); + g_object_unref (key); return FALSE; } - got_value = g_win32_registry_key_get_value_w (key, - NULL, - TRUE, - L"", - &val_type, - (void **) proxy_command, - NULL, - NULL); + default_verb = NULL; - if (proxy_key) - *proxy_key = key; - else - g_object_unref (key); + if (g_win32_registry_key_get_value_w (key, + NULL, + TRUE, + L"", + &val_type, + (void **) &default_verb, + NULL, + NULL) && + (val_type != G_WIN32_REGISTRY_VALUE_STR || + g_utf16_len (default_verb) <= 0)) + g_clear_pointer (&default_verb, g_free); + + g_object_unref (key); - if (!got_value || - val_type != G_WIN32_REGISTRY_VALUE_STR || - ((program_id_u8 != NULL || program_id_folded != NULL) && - !g_utf16_to_utf8_and_fold (program_id, -1, program_id_u8, program_id_folded)) || - !g_utf16_validate (*proxy_command, -1)) + /* Only sort at the top level */ + if (verbname_prefix[0] == 0) { - g_clear_pointer (proxy_id, g_free); - g_clear_pointer (proxy_command, g_free); - if (proxy_key) - g_clear_object (proxy_key); - return FALSE; + *verbs = g_list_sort_with_data (*verbs, compare_verbs, default_verb); + + for (i = *verbs; default_verb && *preferred_verb == NULL && i; i = i->next) + if (_wcsicmp (default_verb, ((const reg_verb *) i->data)->name) == 0) + *preferred_verb = (const reg_verb *) i->data; } + g_clear_pointer (&default_verb, g_free); + return TRUE; } +/* Grabs a URL association (from HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\ + * or from an application with Capabilities, or just a schema subkey in HKCR). + * @program_id is a ProgID of the handler for the URL. + * @schema is the schema for the URL. + * @schema_u8 and @schema_u8_folded are UTF-8 and folded UTF-8 + * respectively. + * @app is the app to which the URL handler belongs (can be NULL). + * @is_user_choice is TRUE if this association is clearly preferred + */ static void -get_url_association (const gunichar2 *schema) +get_url_association (const gunichar2 *program_id, + const gunichar2 *schema, + const gchar *schema_u8, + const gchar *schema_u8_folded, + GWin32AppInfoApplication *app, + gboolean is_user_choice) { GWin32AppInfoURLSchema *schema_rec; GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_url; - gchar *schema_u8; - gchar *schema_folded; - GWin32RegistryKey *user_choice; - GWin32RegistryValueType val_type; - gunichar2 *program_id; - gsize program_id_size; - gunichar2 *program_command; - gunichar2 *proxy_id; - gunichar2 *proxy_command; - gchar *program_id_u8; - gchar *program_id_folded; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; - - user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS, - schema, USER_CHOICE, - NULL); - - if (user_choice == NULL) + gunichar2 *handler_id; + GList *verbs; + const reg_verb *preferred_verb; + gchar *handler_id_u8; + gchar *handler_id_u8_folded; + GWin32RegistryKey *handler_key; + + if ((handler_id = decide_which_id_to_use (program_id, + &handler_key, + &handler_id_u8, + &handler_id_u8_folded)) == NULL) return; - if (!g_utf16_to_utf8_and_fold (schema, -1, &schema_u8, &schema_folded)) + if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell")) { - g_object_unref (user_choice); + g_clear_pointer (&handler_id, g_free); + g_clear_pointer (&handler_id_u8, g_free); + g_clear_pointer (&handler_id_u8_folded, g_free); + g_clear_object (&handler_key); + return; } - schema_rec = g_hash_table_lookup (urls, schema_folded); + schema_rec = get_schema_object (schema, + schema_u8, + schema_u8_folded); - if (!g_win32_registry_key_get_value_w (user_choice, - NULL, - TRUE, - L"Progid", - &val_type, - (void **) &program_id, - &program_id_size, - NULL)) - { - g_free (schema_u8); - g_free (schema_folded); - g_object_unref (user_choice); - return; - } + handler_rec = get_handler_object (handler_id_u8_folded, + handler_key, + handler_id); + + if (is_user_choice || schema_rec->chosen_handler == NULL) + g_set_object (&schema_rec->chosen_handler, handler_rec); + + g_hash_table_insert (schema_rec->handlers, + g_strdup (handler_id_u8_folded), + g_object_ref (handler_rec)); + + g_clear_object (&handler_key); - if (val_type != G_WIN32_REGISTRY_VALUE_STR) + if (app) + g_hash_table_insert (app->supported_urls, + g_strdup (schema_rec->schema_u8_folded), + g_object_ref (handler_rec)); + + process_verbs_commands (g_steal_pointer (&verbs), + preferred_verb, + HKCR, + handler_id, + TRUE, + handler_add_verb, + handler_rec, + app); + + g_clear_pointer (&handler_id_u8, g_free); + g_clear_pointer (&handler_id_u8_folded, g_free); + g_clear_pointer (&handler_id, g_free); +} + +/* Grabs a file extension association (from HKCR\.ext or similar). + * @program_id is a ProgID of the handler for the extension. + * @file_extension is the extension (with the leading '.') + * @app is the app to which the extension handler belongs (can be NULL). + * @is_user_choice is TRUE if this is clearly the preferred association + */ +static void +get_file_ext (const gunichar2 *program_id, + const gunichar2 *file_extension, + GWin32AppInfoApplication *app, + gboolean is_user_choice) +{ + GWin32AppInfoHandler *handler_rec; + gunichar2 *handler_id; + const reg_verb *preferred_verb; + GList *verbs; + gchar *handler_id_u8; + gchar *handler_id_u8_folded; + GWin32RegistryKey *handler_key; + GWin32AppInfoFileExtension *file_extn; + gchar *file_extension_u8; + gchar *file_extension_u8_folded; + + if ((handler_id = decide_which_id_to_use (program_id, + &handler_key, + &handler_id_u8, + &handler_id_u8_folded)) == NULL) + return; + + if (!g_utf16_to_utf8_and_fold (file_extension, + -1, + &file_extension_u8, + &file_extension_u8_folded)) { - g_free (schema_u8); - g_free (schema_folded); - g_free (program_id); - g_object_unref (user_choice); + g_clear_pointer (&handler_id, g_free); + g_clear_pointer (&handler_id_u8, g_free); + g_clear_pointer (&handler_id_u8_folded, g_free); + g_clear_object (&handler_key); + return; } - program_key = proxy_key = NULL; - program_command = proxy_id = proxy_command = NULL; - - if (!follow_class_chain_to_handler (program_id, - program_id_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) + if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell")) { - g_free (schema_u8); - g_free (schema_folded); - g_free (program_id); - g_object_unref (user_choice); + g_clear_pointer (&handler_id, g_free); + g_clear_pointer (&handler_id_u8, g_free); + g_clear_pointer (&handler_id_u8_folded, g_free); + g_clear_object (&handler_key); + g_clear_pointer (&file_extension_u8, g_free); + g_clear_pointer (&file_extension_u8_folded, g_free); + return; } - handler_rec = g_hash_table_lookup (handlers, program_id_folded); + file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded); - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = g_wcsdup (program_id, program_id_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, program_key, &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } + handler_rec = get_handler_object (handler_id_u8_folded, + handler_key, + handler_id); - if (schema_rec == NULL) - { - schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); - schema_rec->schema = g_wcsdup (schema, -1); - schema_rec->schema_u8 = g_strdup (schema_u8); - schema_rec->schema_folded = g_strdup (schema_folded); - schema_rec->chosen_handler = g_object_ref (handler_rec); - g_hash_table_insert (urls, g_strdup (schema_folded), schema_rec); - } + if (is_user_choice || file_extn->chosen_handler == NULL) + g_set_object (&file_extn->chosen_handler, handler_rec); - if (schema_rec->chosen_handler == NULL) - schema_rec->chosen_handler = g_object_ref (handler_rec); + g_hash_table_insert (file_extn->handlers, + g_strdup (handler_id_u8_folded), + g_object_ref (handler_rec)); - handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers, - program_id_folded); - - if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec) - g_hash_table_insert (schema_rec->handlers, - g_strdup (program_id_folded), + if (app) + g_hash_table_insert (app->supported_exts, + g_strdup (file_extension_u8_folded), g_object_ref (handler_rec)); - g_free (schema_u8); - g_free (schema_folded); - g_free (program_id); - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - g_object_unref (user_choice); + g_clear_pointer (&file_extension_u8, g_free); + g_clear_pointer (&file_extension_u8_folded, g_free); + g_clear_object (&handler_key); + + process_verbs_commands (g_steal_pointer (&verbs), + preferred_verb, + HKCR, + handler_id, + TRUE, + handler_add_verb, + handler_rec, + app); + + g_clear_pointer (&handler_id, g_free); + g_clear_pointer (&handler_id_u8, g_free); + g_clear_pointer (&handler_id_u8_folded, g_free); } -static void -get_file_ext (const gunichar2 *ext) +/* Returns either a @program_id or the string from + * the default value of the program_id key (which is a name + * of a proxy class), or NULL. + * Does not check that proxy represents a valid + * record, just checks that it exists. + * Can return the class key (HKCR/program_id or HKCR/proxy_id). + * Can convert returned value to UTF-8 and fold it. + */ +static gunichar2 * +decide_which_id_to_use (const gunichar2 *program_id, + GWin32RegistryKey **return_key, + gchar **return_handler_id_u8, + gchar **return_handler_id_u8_folded) { - GWin32AppInfoFileExtension *file_extn; - gboolean file_ext_known; - GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_ext; - gchar *ext_u8; - gchar *ext_folded; - GWin32RegistryKey *user_choice; - GWin32RegistryKey *open_with_progids; + GWin32RegistryKey *key; GWin32RegistryValueType val_type; - gsize program_id_size; - gboolean found_handler; - gunichar2 *program_id; gunichar2 *proxy_id; - gchar *program_id_u8; - gchar *program_id_folded; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; - gunichar2 *program_command; - gunichar2 *proxy_command; - - open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, - ext, - OPEN_WITH_PROGIDS, - NULL); + gunichar2 *return_id; + gboolean got_value; + gchar *handler_id_u8; + gchar *handler_id_u8_folded; + g_assert (program_id); - user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, ext, - USER_CHOICE, NULL); + if (return_key) + *return_key = NULL; - if (user_choice == NULL && open_with_progids == NULL) - return; + /* Check the proxy first */ + key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL); - if (!g_utf16_to_utf8_and_fold (ext, -1, &ext_u8, &ext_folded)) + if (key == NULL) + return NULL; + + proxy_id = NULL; + got_value = g_win32_registry_key_get_value_w (key, + NULL, + TRUE, + L"", + &val_type, + (void **) &proxy_id, + NULL, + NULL); + if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR) + g_clear_pointer (&proxy_id, g_free); + + return_id = NULL; + + if (proxy_id) { - g_clear_object (&user_choice); - g_clear_object (&open_with_progids); - return; + GWin32RegistryKey *proxy_key; + proxy_key = g_win32_registry_key_get_child_w (classes_root_key, proxy_id, NULL); + + if (proxy_key) + { + if (return_key) + *return_key = g_steal_pointer (&proxy_key); + g_clear_object (&proxy_key); + + return_id = g_steal_pointer (&proxy_id); + } + + g_clear_pointer (&proxy_id, g_free); } - file_extn = NULL; - file_ext_known = g_hash_table_lookup_extended (extensions, - ext_folded, - NULL, - (void **) &file_extn); + if ((return_handler_id_u8 || + return_handler_id_u8_folded) && + !g_utf16_to_utf8_and_fold (return_id == NULL ? program_id : return_id, + -1, + &handler_id_u8, + &handler_id_u8_folded)) + { + g_clear_object (&key); + if (return_key) + g_clear_object (return_key); + g_clear_pointer (&return_id, g_free); - if (!file_ext_known) - file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); + return NULL; + } + + if (return_handler_id_u8) + *return_handler_id_u8 = g_steal_pointer (&handler_id_u8); + if (return_handler_id_u8_folded) + *return_handler_id_u8_folded = g_steal_pointer (&handler_id_u8_folded); - found_handler = FALSE; + if (return_id == NULL && return_key) + *return_key = g_steal_pointer (&key); + g_clear_object (&key); - if (user_choice != NULL) + if (return_id == NULL) + return g_wcsdup (program_id, -1); + + return return_id; +} + +/* Grabs the command for each verb from @verbs, + * and invokes @handler for it. Consumes @verbs. + * @path_to_progid and @progid are concatenated to + * produce a path to the key where Shell/verb/command + * subkeys are looked up. + * @preferred_verb, if not NULL, will be used to inform + * the @handler that a verb is preferred. + * @autoprefer_first_verb will automatically make the first + * verb to be preferred, if @preferred_verb is NULL. + * @handler_data1 and @handler_data2 are passed to @handler as-is. + */ +static void +process_verbs_commands (GList *verbs, + const reg_verb *preferred_verb, + const gunichar2 *path_to_progid, + const gunichar2 *progid, + gboolean autoprefer_first_verb, + verb_command_func handler, + gpointer handler_data1, + gpointer handler_data2) +{ + GList *i; + gboolean got_value; + + g_assert (handler != NULL); + g_assert (verbs != NULL); + g_assert (progid != NULL); + + for (i = verbs; i; i = i->next) { - if (g_win32_registry_key_get_value_w (user_choice, - NULL, - TRUE, - L"Progid", - &val_type, - (void **) &program_id, - &program_id_size, - NULL)) + const reg_verb *verb = (const reg_verb *) i->data; + GWin32RegistryKey *key; + GWin32RegistryKey *verb_key; + gunichar2 *command_value; + gchar *command_value_utf8; + GWin32RegistryValueType val_type; + gunichar2 *verb_displayname; + gchar *verb_displayname_u8; + + key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid, + L"\\", verb->shellpath, L"\\command", NULL); + + if (key == NULL) { - program_key = proxy_key = NULL; - - if (val_type == G_WIN32_REGISTRY_VALUE_STR && - follow_class_chain_to_handler (program_id, - program_id_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - { - handler_rec = g_hash_table_lookup (handlers, - program_id_folded); + g_debug ("%S%S\\shell\\%S does not have a \"command\" subkey", + path_to_progid, progid, verb->shellpath); + continue; + } - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, - NULL); - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (program_id, program_id_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } + command_value = NULL; + got_value = g_win32_registry_key_get_value_w (key, + NULL, + TRUE, + L"", + &val_type, + (void **) &command_value, + NULL, + NULL); + g_clear_object (&key); - found_handler = TRUE; + if (!got_value || + val_type != G_WIN32_REGISTRY_VALUE_STR || + (command_value_utf8 = g_utf16_to_utf8 (command_value, + -1, + NULL, + NULL, + NULL)) == NULL) + { + g_clear_pointer (&command_value, g_free); + continue; + } - handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers, - program_id_folded); + verb_displayname = NULL; + verb_displayname_u8 = NULL; + verb_key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid, + L"\\", verb->shellpath, NULL); - if (file_extn->chosen_handler == NULL) - { - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } - else if (handler_rec_in_ext == NULL) - { - if (file_extn->chosen_handler->handler_id_folded && - strcmp (file_extn->chosen_handler->handler_id_folded, - program_id_folded) != 0) - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } + if (verb_key) + { + gsize verb_displayname_len; + + got_value = g_win32_registry_key_get_value_w (verb_key, + g_win32_registry_get_os_dirs_w (), + TRUE, + L"MUIVerb", + &val_type, + (void **) &verb_displayname, + &verb_displayname_len, + NULL); + + if (got_value && + val_type == G_WIN32_REGISTRY_VALUE_STR && + verb_displayname_len > sizeof (gunichar2)) + verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL); + + g_clear_pointer (&verb_displayname, g_free); + + if (verb_displayname_u8 == NULL) + { + got_value = g_win32_registry_key_get_value_w (verb_key, + NULL, + TRUE, + L"", + &val_type, + (void **) &verb_displayname, + &verb_displayname_len, + NULL); - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); + if (got_value && + val_type == G_WIN32_REGISTRY_VALUE_STR && + verb_displayname_len > sizeof (gunichar2)) + verb_displayname_u8 = g_utf16_to_utf8 (verb_displayname, -1, NULL, NULL, NULL); } - g_free (program_id); + g_clear_pointer (&verb_displayname, g_free); + g_clear_object (&verb_key); } - g_object_unref (user_choice); + handler (handler_data1, handler_data2, verb->name, command_value, command_value_utf8, + verb_displayname_u8, + (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) || + (!preferred_verb && autoprefer_first_verb && i == verbs), + FALSE); + + g_clear_pointer (&command_value, g_free); + g_clear_pointer (&command_value_utf8, g_free); + g_clear_pointer (&verb_displayname_u8, g_free); } - if (open_with_progids != NULL) - { - GWin32RegistryValueIter iter; + g_list_free_full (verbs, reg_verb_free); +} - if (g_win32_registry_value_iter_init (&iter, open_with_progids, NULL)) - { - gunichar2 *value_name; - gunichar2 *value_data; - gsize value_name_len; - gsize value_data_size; - GWin32RegistryValueType value_type; +/* Looks up a schema object identified by + * @schema_u8_folded in the urls hash table. + * If such object doesn't exist, + * creates it and puts it into the urls hash table. + * Returns the object. + */ +static GWin32AppInfoURLSchema * +get_schema_object (const gunichar2 *schema, + const gchar *schema_u8, + const gchar *schema_u8_folded) +{ + GWin32AppInfoURLSchema *schema_rec; - while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) - { - gsize value_name_size; - program_key = proxy_key = NULL; + schema_rec = g_hash_table_lookup (urls, schema_u8_folded); - if ((!g_win32_registry_value_iter_get_value_type (&iter, - &value_type, - NULL)) || - ((val_type != G_WIN32_REGISTRY_VALUE_STR) && - (val_type != G_WIN32_REGISTRY_VALUE_EXPAND_STR)) || - (!g_win32_registry_value_iter_get_name_w (&iter, &value_name, - &value_name_len, - NULL)) || - (value_name_len <= 0) || - (!g_win32_registry_value_iter_get_data_w (&iter, TRUE, - (void **) &value_data, - &value_data_size, - NULL)) || - (value_data_size < sizeof (gunichar2)) || - (value_data[0] == L'\0')) - continue; + if (schema_rec != NULL) + return schema_rec; - value_name_size = sizeof (gunichar2) * (value_name_len + 1); - - if (!follow_class_chain_to_handler (value_name, - value_name_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - continue; + schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); + schema_rec->schema = g_wcsdup (schema, -1); + schema_rec->schema_u8 = g_strdup (schema_u8); + schema_rec->schema_u8_folded = g_strdup (schema_u8_folded); + g_hash_table_insert (urls, g_strdup (schema_rec->schema_u8_folded), schema_rec); - handler_rec = g_hash_table_lookup (handlers, - program_id_folded); + return schema_rec; +} - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (value_name, value_name_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } +/* Looks up a handler object identified by + * @handler_id_u8_folded in the handlers hash table. + * If such object doesn't exist, + * creates it and puts it into the handlers hash table. + * Returns the object. + */ +static GWin32AppInfoHandler * +get_handler_object (const gchar *handler_id_u8_folded, + GWin32RegistryKey *handler_key, + const gunichar2 *handler_id) +{ + GWin32AppInfoHandler *handler_rec; - found_handler = TRUE; + handler_rec = g_hash_table_lookup (handlers, handler_id_u8_folded); - handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers, - program_id_folded); + if (handler_rec != NULL) + return handler_rec; - if (handler_rec_in_ext == NULL) - { - if (file_extn->chosen_handler == NULL) - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - else if (file_extn->chosen_handler->handler_id_folded && - strcmp (file_extn->chosen_handler->handler_id_folded, - program_id_folded) != 0) - g_hash_table_insert (file_extn->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } + handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); + handler_rec->key = g_object_ref (handler_key); + handler_rec->handler_id = g_wcsdup (handler_id, -1); + handler_rec->handler_id_folded = g_strdup (handler_id_u8_folded); + read_handler_icon (handler_key, &handler_rec->icon); + g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec); - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - } + return handler_rec; +} - g_win32_registry_value_iter_clear (&iter); - } +static void +handler_add_verb (gpointer handler_data1, + gpointer handler_data2, + const gunichar2 *verb, + const gunichar2 *command_line, + const gchar *command_line_utf8, + const gchar *verb_displayname, + gboolean verb_is_preferred, + gboolean invent_new_verb_name) +{ + GWin32AppInfoHandler *handler_rec = (GWin32AppInfoHandler *) handler_data1; + GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2; + GWin32AppInfoShellVerb *shverb; - g_object_unref (open_with_progids); - } + _verb_lookup (handler_rec->verbs, verb, &shverb); - if (!found_handler) - { - if (!file_ext_known) - g_object_unref (file_extn); - } - else if (!file_ext_known) + if (shverb != NULL) + return; + + shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL); + shverb->verb_name = g_wcsdup (verb, -1); + shverb->verb_displayname = g_strdup (verb_displayname); + shverb->command = g_wcsdup (command_line, -1); + shverb->command_utf8 = g_strdup (command_line_utf8); + if (app_rec) + shverb->app = g_object_ref (app_rec); + + _g_win32_extract_executable (shverb->command, + &shverb->executable, + &shverb->executable_basename, + &shverb->executable_folded, + NULL, + &shverb->dll_function); + + if (shverb->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command); + + if (!verb_is_preferred) + g_ptr_array_add (handler_rec->verbs, shverb); + else + g_ptr_array_insert (handler_rec->verbs, 0, shverb); +} + +/* Tries to generate a new name for a verb that looks + * like "verb (%x)", where %x is an integer in range of [0;255). + * On success puts new verb (and new verb displayname) into + * @new_verb and @new_displayname and return TRUE. + * On failure puts NULL into both and returns FALSE. + */ +static gboolean +generate_new_verb_name (GPtrArray *verbs, + const gunichar2 *verb, + const gchar *verb_displayname, + gunichar2 **new_verb, + gchar **new_displayname) +{ + gsize counter; + GWin32AppInfoShellVerb *shverb; + gsize orig_len = g_utf16_len (verb); + gsize new_verb_name_len = orig_len + strlen (" ()") + 2 + 1; + gunichar2 *new_verb_name = g_malloc (new_verb_name_len * sizeof (gunichar2)); + + *new_verb = NULL; + *new_displayname = NULL; + + memcpy (new_verb_name, verb, orig_len * sizeof (gunichar2)); + for (counter = 0; counter < 255; counter++) + { + _snwprintf (&new_verb_name[orig_len], new_verb_name_len, L" (%x)", counter); + _verb_lookup (verbs, new_verb_name, &shverb); + + if (shverb == NULL) + { + *new_verb = new_verb_name; + if (verb_displayname != NULL) + *new_displayname = g_strdup_printf ("%s (%zx)", verb_displayname, counter); + + return TRUE; + } + } + + return FALSE; +} + +static void +app_add_verb (gpointer handler_data1, + gpointer handler_data2, + const gunichar2 *verb, + const gunichar2 *command_line, + const gchar *command_line_utf8, + const gchar *verb_displayname, + gboolean verb_is_preferred, + gboolean invent_new_verb_name) +{ + gunichar2 *new_verb = NULL; + gchar *new_displayname = NULL; + GWin32AppInfoApplication *app_rec = (GWin32AppInfoApplication *) handler_data2; + GWin32AppInfoShellVerb *shverb; + + _verb_lookup (app_rec->verbs, verb, &shverb); + + /* Special logic for fake apps - do our best to + * collate all possible verbs in the app, + * including the verbs that have the same name but + * different commandlines, in which case a new + * verb name has to be invented. + */ + if (shverb != NULL) { - file_extn->extension = g_wcsdup (ext, -1); - file_extn->extension_u8 = g_strdup (ext_u8); - g_hash_table_insert (extensions, g_strdup (ext_folded), file_extn); + gsize vi; + + if (!invent_new_verb_name) + return; + + for (vi = 0; vi < app_rec->verbs->len; vi++) + { + GWin32AppInfoShellVerb *app_verb; + + app_verb = _verb_idx (app_rec->verbs, vi); + + if (_wcsicmp (command_line, app_verb->command) == 0) + break; + } + + if (vi < app_rec->verbs->len || + !generate_new_verb_name (app_rec->verbs, + verb, + verb_displayname, + &new_verb, + &new_displayname)) + return; } - g_free (ext_u8); - g_free (ext_folded); + shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL); + if (new_verb == NULL) + shverb->verb_name = g_wcsdup (verb, -1); + else + shverb->verb_name = g_steal_pointer (&new_verb); + if (new_displayname == NULL) + shverb->verb_displayname = g_strdup (verb_displayname); + else + shverb->verb_displayname = g_steal_pointer (&new_displayname); + + shverb->command = g_wcsdup (command_line, -1); + shverb->command_utf8 = g_strdup (command_line_utf8); + shverb->app = g_object_ref (app_rec); + + _g_win32_extract_executable (shverb->command, + &shverb->executable, + &shverb->executable_basename, + &shverb->executable_folded, + NULL, + &shverb->dll_function); + + if (shverb->dll_function != NULL) + _g_win32_fixup_broken_microsoft_rundll_commandline (shverb->command); + + if (!verb_is_preferred) + g_ptr_array_add (app_rec->verbs, shverb); + else + g_ptr_array_insert (app_rec->verbs, 0, shverb); +} + +/* Looks up a file extension object identified by + * @ext_u8_folded in the extensions hash table. + * If such object doesn't exist, + * creates it and puts it into the extensions hash table. + * Returns the object. + */ +static GWin32AppInfoFileExtension * +get_ext_object (const gunichar2 *ext, + const gchar *ext_u8, + const gchar *ext_u8_folded) +{ + GWin32AppInfoFileExtension *file_extn; + + if (g_hash_table_lookup_extended (extensions, + ext_u8_folded, + NULL, + (void **) &file_extn)) + return file_extn; + + file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); + file_extn->extension = g_wcsdup (ext, -1); + file_extn->extension_u8 = g_strdup (ext_u8); + g_hash_table_insert (extensions, g_strdup (ext_u8_folded), file_extn); + + return file_extn; } +/* Iterates over HKCU\\Software\\Clients or HKLM\\Software\\Clients, + * (depending on @user_registry being TRUE or FALSE), + * collecting applications listed there. + * Puts the path to the client key for each client into @priority_capable_apps + * (only for clients with file or URL associations). + */ static void collect_capable_apps_from_clients (GPtrArray *capable_apps, GPtrArray *priority_capable_apps, @@ -1223,7 +1657,7 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps, GWin32RegistryKey *clients; GWin32RegistrySubkeyIter clients_iter; - gunichar2 *client_type_name; + const gunichar2 *client_type_name; gsize client_type_name_len; @@ -1251,7 +1685,7 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps, GWin32RegistryKey *system_client_type; GWin32RegistryValueType default_type; gunichar2 *default_value = NULL; - gunichar2 *client_name; + const gunichar2 *client_name; gsize client_name_len; if (!g_win32_registry_subkey_iter_get_name_w (&clients_iter, @@ -1356,11 +1790,17 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps, g_object_unref (clients); } +/* Iterates over HKCU\\Software\\RegisteredApplications or HKLM\\Software\\RegisteredApplications, + * (depending on @user_registry being TRUE or FALSE), + * collecting applications listed there. + * Puts the path to the app key for each app into @capable_apps. + */ static void collect_capable_apps_from_registered_apps (GPtrArray *capable_apps, gboolean user_registry) { GWin32RegistryValueIter iter; + const gunichar2 *reg_path; gunichar2 *value_data; gsize value_data_size; @@ -1368,13 +1808,12 @@ collect_capable_apps_from_registered_apps (GPtrArray *capable_apps, GWin32RegistryKey *registered_apps; if (user_registry) - registered_apps = - g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\RegisteredApplications", - NULL); + reg_path = L"HKEY_CURRENT_USER\\Software\\RegisteredApplications"; else - registered_apps = - g_win32_registry_key_new_w (L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications", - NULL); + reg_path = L"HKEY_LOCAL_MACHINE\\Software\\RegisteredApplications"; + + registered_apps = + g_win32_registry_key_new_w (reg_path, NULL); if (!registered_apps) return; @@ -1382,13 +1821,15 @@ collect_capable_apps_from_registered_apps (GPtrArray *capable_apps, if (!g_win32_registry_value_iter_init (&iter, registered_apps, NULL)) { g_object_unref (registered_apps); + return; } while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) { gunichar2 possible_location[REG_PATH_MAX_SIZE + 1]; - GWin32RegistryKey *location = NULL; + GWin32RegistryKey *location; + gunichar2 *p; if ((!g_win32_registry_value_iter_get_value_type (&iter, &value_type, @@ -1402,51 +1843,81 @@ collect_capable_apps_from_registered_apps (GPtrArray *capable_apps, (value_data[0] == L'\0')) continue; - if (build_registry_path (possible_location, sizeof (possible_location), - HKCU, value_data, NULL)) - location = g_win32_registry_key_new_w (possible_location, NULL); - - if (location) - { - gunichar2 *p = wcsrchr (possible_location, L'\\'); - - if (p) - *p = L'\0'; - - g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1)); - g_object_unref (location); - continue; - } - if (!build_registry_path (possible_location, sizeof (possible_location), user_registry ? HKCU : HKLM, value_data, NULL)) continue; location = g_win32_registry_key_new_w (possible_location, NULL); - if (location) + if (location == NULL) + continue; + + p = wcsrchr (possible_location, L'\\'); + + if (p) { - gunichar2 *p = wcsrchr (possible_location, L'\\'); - if (p) - *p = L'\0'; + *p = L'\0'; g_ptr_array_add (capable_apps, g_wcsdup (possible_location, -1)); - g_object_unref (location); } + + g_object_unref (location); } g_win32_registry_value_iter_clear (&iter); g_object_unref (registered_apps); } +/* Looks up an app object identified by + * @canonical_name_folded in the @app_hashmap. + * If such object doesn't exist, + * creates it and puts it into the @app_hashmap. + * Returns the object. + */ +static GWin32AppInfoApplication * +get_app_object (GHashTable *app_hashmap, + const gunichar2 *canonical_name, + const gchar *canonical_name_u8, + const gchar *canonical_name_folded, + gboolean user_specific, + gboolean default_app) +{ + GWin32AppInfoApplication *app; + + app = g_hash_table_lookup (app_hashmap, canonical_name_folded); + + if (app != NULL) + return app; + + app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); + app->canonical_name = g_wcsdup (canonical_name, -1); + app->canonical_name_u8 = g_strdup (canonical_name_u8); + app->canonical_name_folded = g_strdup (canonical_name_folded); + app->no_open_with = FALSE; + app->user_specific = user_specific; + app->default_app = default_app; + g_hash_table_insert (app_hashmap, + g_strdup (canonical_name_folded), + app); + + return app; +} + +/* Grabs an application that has Capabilities. + * @app_key_path is the path to the application key + * (which must have a "Capabilities" subkey). + * @default_app is TRUE if the app has priority + */ static void -read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolean default_app) +read_capable_app (const gunichar2 *app_key_path, + gboolean user_specific, + gboolean default_app) { GWin32AppInfoApplication *app; - gunichar2 *app_key_path; - gunichar2 *canonical_name; - gchar *canonical_name_u8; - gchar *canonical_name_folded; - GWin32RegistryKey *appkey; + gchar *canonical_name_u8 = NULL; + gchar *canonical_name_folded = NULL; + gchar *app_key_path_u8 = NULL; + gchar *app_key_path_u8_folded = NULL; + GWin32RegistryKey *appkey = NULL; gunichar2 *fallback_friendly_name; GWin32RegistryValueType vtype; gboolean success; @@ -1456,136 +1927,49 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea gunichar2 *icon_source; GWin32RegistryKey *capabilities; GWin32RegistryKey *default_icon_key; - GWin32RegistryKey *shell_open_command_key; - gunichar2 *shell_open_command; - gchar *app_executable; - gchar *app_executable_basename; - gchar *app_executable_folded; - gchar *app_executable_folded_basename; - gchar *app_dll_function; GWin32RegistryKey *associations; - - app_key_path = g_wcsdup (input_app_key_path, -1); - - canonical_name = wcsrchr (app_key_path, L'\\'); - - if (canonical_name == NULL) - { - /* The key must have at least one '\\' */ - g_free (app_key_path); - return; - } - - canonical_name += 1; - - if (!g_utf16_to_utf8_and_fold (canonical_name, -1, &canonical_name_u8, &canonical_name_folded)) + const reg_verb *preferred_verb; + GList *verbs = NULL; + + appkey = NULL; + capabilities = NULL; + + if (!g_utf16_to_utf8_and_fold (app_key_path, + -1, + &canonical_name_u8, + &canonical_name_folded) || + !g_utf16_to_utf8_and_fold (app_key_path, + -1, + &app_key_path_u8, + &app_key_path_u8_folded) || + (appkey = g_win32_registry_key_new_w (app_key_path, NULL)) == NULL || + (capabilities = g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL)) == NULL || + !get_verbs (capabilities, &preferred_verb, &verbs, L"", L"Shell")) { - g_free (app_key_path); - return; - } - - appkey = g_win32_registry_key_new_w (app_key_path, NULL); + g_clear_pointer (&canonical_name_u8, g_free); + g_clear_pointer (&canonical_name_folded, g_free); + g_clear_object (&appkey); + g_clear_pointer (&app_key_path_u8, g_free); + g_clear_pointer (&app_key_path_u8_folded, g_free); - if (appkey == NULL) - { - g_free (canonical_name_u8); - g_free (canonical_name_folded); - g_free (app_key_path); return; } - capabilities = - g_win32_registry_key_get_child_w (appkey, L"Capabilities", NULL); - - if (capabilities == NULL) - { - /* Must have capabilities */ - g_free (canonical_name_u8); - g_free (canonical_name_folded); - g_free (app_key_path); - return; - } - - shell_open_command_key = - g_win32_registry_key_get_child_w (appkey, - L"shell\\open\\command", - NULL); - - if (shell_open_command_key == NULL) - { - g_object_unref (capabilities); - g_free (canonical_name_u8); - g_free (canonical_name_folded); - g_free (app_key_path); - g_object_unref (appkey); - return ; - } - - shell_open_command = NULL; - - success = g_win32_registry_key_get_value_w (shell_open_command_key, - NULL, - TRUE, - L"", - &vtype, - (gpointer *) &shell_open_command, - NULL, - NULL); - - if (success && - (vtype != G_WIN32_REGISTRY_VALUE_STR || - !g_utf16_validate (shell_open_command, -1))) - { - /* Must have a command */ - g_clear_pointer (&shell_open_command, g_free); - g_object_unref (capabilities); - g_free (canonical_name_u8); - g_free (canonical_name_folded); - g_free (app_key_path); - g_object_unref (appkey); - return; - } - - _g_win32_extract_executable (shell_open_command, - &app_executable, - &app_executable_basename, - &app_executable_folded, - &app_executable_folded_basename, - &app_dll_function); - if (app_dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command); - - app = g_hash_table_lookup (apps_by_id, canonical_name_folded); - - if (app == NULL) - { - app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); - - app->canonical_name = g_wcsdup (canonical_name, -1); - app->canonical_name_u8 = g_strdup (canonical_name_u8); - app->canonical_name_folded = - g_strdup (canonical_name_folded); - - app->command = g_wcsdup (shell_open_command, -1); - app->command_u8 = - g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL); - app->executable = g_strdup (app_executable); - app->executable_basename = - &app->executable[app_executable_basename - app_executable]; - app->executable_folded = - g_strdup (app_executable_folded); - - app->no_open_with = FALSE; - - app->user_specific = user_specific; - app->default_app = default_app; - - app->dll_function = g_strdup (app_dll_function); - - g_hash_table_insert (apps_by_id, - g_strdup (canonical_name_folded), - app); - } + app = get_app_object (apps_by_id, + app_key_path, + canonical_name_u8, + canonical_name_folded, + user_specific, + default_app); + + process_verbs_commands (g_steal_pointer (&verbs), + preferred_verb, + L"", /* [ab]use the fact that two strings are simply concatenated */ + g_win32_registry_key_get_path_w (capabilities), + FALSE, + app_add_verb, + app, + app); fallback_friendly_name = NULL; success = g_win32_registry_key_get_value_w (appkey, @@ -1600,7 +1984,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) g_clear_pointer (&fallback_friendly_name, g_free); - if (fallback_friendly_name && app->pretty_name == NULL) + if (fallback_friendly_name && + app->pretty_name == NULL) { app->pretty_name = g_wcsdup (fallback_friendly_name, -1); g_clear_pointer (&app->pretty_name_u8, g_free); @@ -1621,10 +2006,12 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea NULL, NULL); - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + if (success && + vtype != G_WIN32_REGISTRY_VALUE_STR) g_clear_pointer (&friendly_name, g_free); - if (friendly_name && app->localized_pretty_name == NULL) + if (friendly_name && + app->localized_pretty_name == NULL) { app->localized_pretty_name = g_wcsdup (friendly_name, -1); g_clear_pointer (&app->localized_pretty_name_u8, g_free); @@ -1672,7 +2059,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea NULL, NULL); - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + if (success && + vtype != G_WIN32_REGISTRY_VALUE_STR) g_clear_pointer (&icon_source, g_free); g_object_unref (default_icon_key); @@ -1689,11 +2077,13 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea NULL, NULL); - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + if (success && + vtype != G_WIN32_REGISTRY_VALUE_STR) g_clear_pointer (&icon_source, g_free); } - if (icon_source && app->icon == NULL) + if (icon_source && + app->icon == NULL) { gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL); app->icon = g_themed_icon_new (name); @@ -1713,13 +2103,17 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) g_clear_pointer (&narrow_application_name, g_free); - /* TODO: do something with the narrow name. Maybe make a kind of sub-app? - * Narrow name is a more precise name of the application in given context. - * I.e. Thunderbird's name is "Thunderbird", whereas its narrow name is - * "Thunderbird (news)" when registering it as a news client. - * Maybe we should consider applications with different narrow names as - * different applications altogether? - */ + if (narrow_application_name && + app->localized_pretty_name == NULL) + { + app->localized_pretty_name = g_wcsdup (narrow_application_name, -1); + g_clear_pointer (&app->localized_pretty_name_u8, g_free); + app->localized_pretty_name_u8 = g_utf16_to_utf8 (narrow_application_name, + -1, + NULL, + NULL, + NULL); + } associations = g_win32_registry_key_get_child_w (capabilities, L"FileAssociations", @@ -1739,19 +2133,6 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) { - GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_ext; - GWin32AppInfoFileExtension *ext; - gunichar2 *program_command; - gunichar2 *proxy_id; - gunichar2 *proxy_command; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; - gchar *program_id_u8; - gchar *program_id_folded; - gchar *file_extension_u8; - gchar *file_extension_folded; - if ((!g_win32_registry_value_iter_get_value_type (&iter, &value_type, NULL)) || @@ -1770,110 +2151,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea (extension_handler[0] == L'\0')) continue; - if (!follow_class_chain_to_handler (extension_handler, - extension_handler_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - continue; - - handler_rec = g_hash_table_lookup (handlers, - program_id_folded); - - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (extension_handler,extension_handler_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } - - if (g_utf16_to_utf8_and_fold (file_extension, - -1, - &file_extension_u8, - &file_extension_folded)) - { - ext = g_hash_table_lookup (extensions, - file_extension_folded); - - if (ext == NULL) - { - ext = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); - - ext->extension = g_wcsdup (file_extension, -1); - ext->extension_u8 = g_strdup (file_extension_u8); - g_hash_table_insert (extensions, g_strdup (file_extension_folded), ext); - } - - handler_rec_in_ext = - g_hash_table_lookup (ext->handlers, - program_id_folded); - - if (handler_rec_in_ext == NULL) - { - if (ext->chosen_handler == NULL) - g_hash_table_insert (ext->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - else if (ext->chosen_handler->handler_id_folded && - strcmp (ext->chosen_handler->handler_id_folded, - program_id_folded) != 0) - g_hash_table_insert (ext->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - } - - handler_rec_in_ext = - g_hash_table_lookup (app->supported_exts, - file_extension_folded); - - if (handler_rec_in_ext == NULL) - g_hash_table_insert (app->supported_exts, - g_strdup (file_extension_folded), - g_object_ref (handler_rec)); - - g_free (file_extension_u8); - g_free (file_extension_folded); - } - - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); + get_file_ext (extension_handler, file_extension, app, FALSE); } g_win32_registry_value_iter_clear (&iter); @@ -1898,18 +2176,8 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) { - GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_url; - GWin32AppInfoURLSchema *schema; - gunichar2 *program_command; - gunichar2 *proxy_id; - gunichar2 *proxy_command; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; - gchar *program_id_u8; - gchar *program_id_folded; gchar *schema_u8; - gchar *schema_folded; + gchar *schema_u8_folded; if ((!g_win32_registry_value_iter_get_value_type (&iter, &value_type, @@ -1930,105 +2198,16 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea (schema_handler[0] == L'\0')) continue; - if (!follow_class_chain_to_handler (schema_handler, - schema_handler_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - continue; - - handler_rec = g_hash_table_lookup (handlers, program_id_folded); - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = - g_wcsdup (schema_handler, schema_handler_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = program_command ? - g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = - proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, - program_key, - &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } + if (g_utf16_to_utf8_and_fold (url_schema, + url_schema_len, + &schema_u8, + &schema_u8_folded)) + get_url_association (schema_handler, url_schema, schema_u8, schema_u8_folded, app, FALSE); - if (g_utf16_to_utf8_and_fold (url_schema, - -1, - &schema_u8, - &schema_folded)) - { - schema = g_hash_table_lookup (urls, - schema_folded); - - if (schema == NULL) - { - schema = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); - - schema->schema = g_wcsdup (url_schema, -1); - schema->schema_u8 = g_strdup (schema_u8); - schema->schema_folded = - g_strdup (schema_folded); - g_hash_table_insert (urls, - g_strdup (schema_folded), - schema); - } - - handler_rec_in_url = - g_hash_table_lookup (schema->handlers, - program_id_folded); - - if (handler_rec_in_url == NULL) - g_hash_table_insert (schema->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - - handler_rec_in_url = - g_hash_table_lookup (app->supported_urls, - schema_folded); - - if (handler_rec_in_url == NULL) - g_hash_table_insert (app->supported_urls, - g_strdup (schema_folded), - g_object_ref (handler_rec)); - - g_free (schema_u8); - g_free (schema_folded); - } - - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); + g_clear_pointer (&schema_u8, g_free); + g_clear_pointer (&schema_u8_folded, g_free); } g_win32_registry_value_iter_clear (&iter); @@ -2037,29 +2216,26 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea g_object_unref (associations); } - g_clear_pointer (&app_executable, g_free); - g_clear_pointer (&app_executable_folded, g_free); - g_clear_pointer (&app_dll_function, g_free); g_clear_pointer (&fallback_friendly_name, g_free); g_clear_pointer (&description, g_free); g_clear_pointer (&icon_source, g_free); g_clear_pointer (&narrow_application_name, g_free); - g_clear_pointer (&shell_open_command, g_free); g_object_unref (appkey); - g_object_unref (shell_open_command_key); g_object_unref (capabilities); - g_free (canonical_name_u8); - g_free (canonical_name_folded); - g_free (app_key_path); + g_clear_pointer (&app_key_path_u8, g_free); + g_clear_pointer (&app_key_path_u8_folded, g_free); + g_clear_pointer (&canonical_name_u8, g_free); + g_clear_pointer (&canonical_name_folded, g_free); } +/* Iterates over subkeys in HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\ + * and calls get_url_association() for each one that has a user-chosen handler. + */ static void read_urls (GWin32RegistryKey *url_associations) { GWin32RegistrySubkeyIter url_iter; - gunichar2 *url_schema; - gsize url_schema_len; if (url_associations == NULL) return; @@ -2069,302 +2245,259 @@ read_urls (GWin32RegistryKey *url_associations) while (g_win32_registry_subkey_iter_next (&url_iter, TRUE, NULL)) { - if (!g_win32_registry_subkey_iter_get_name_w (&url_iter, - &url_schema, - &url_schema_len, - NULL)) - continue; - - get_url_association (url_schema); + gchar *schema_u8 = NULL; + gchar *schema_u8_folded = NULL; + const gunichar2 *url_schema = NULL; + gunichar2 *program_id = NULL; + GWin32RegistryKey *user_choice = NULL; + gsize url_schema_len; + GWin32RegistryValueType val_type; + + if (g_win32_registry_subkey_iter_get_name_w (&url_iter, + &url_schema, + &url_schema_len, + NULL) && + g_utf16_to_utf8_and_fold (url_schema, + url_schema_len, + &schema_u8, + &schema_u8_folded) && + (user_choice = _g_win32_registry_key_build_and_new_w (NULL, URL_ASSOCIATIONS, + url_schema, USER_CHOICE, + NULL)) != NULL && + g_win32_registry_key_get_value_w (user_choice, + NULL, + TRUE, + L"Progid", + &val_type, + (void **) &program_id, + NULL, + NULL) && + val_type == G_WIN32_REGISTRY_VALUE_STR) + get_url_association (program_id, url_schema, schema_u8, schema_u8_folded, NULL, TRUE); + + g_clear_pointer (&program_id, g_free); + g_clear_pointer (&user_choice, g_object_unref); + g_clear_pointer (&schema_u8, g_free); + g_clear_pointer (&schema_u8_folded, g_free); } g_win32_registry_subkey_iter_clear (&url_iter); } +/* Reads an application that is only registered by the basename of its + * executable (and doesn't have Capabilities subkey). + * @incapable_app is the registry key for the app. + * @app_exe_basename is the basename of its executable. + */ static void -read_exeapps (void) +read_incapable_app (GWin32RegistryKey *incapable_app, + const gunichar2 *app_exe_basename, + const gchar *app_exe_basename_u8, + const gchar *app_exe_basename_u8_folded) { - GWin32RegistryKey *applications_key; - GWin32RegistrySubkeyIter app_iter; - - applications_key = - g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL); + GWin32RegistryValueIter sup_iter; + GWin32AppInfoApplication *app; + GList *verbs; + const reg_verb *preferred_verb; + gunichar2 *friendly_app_name; + gboolean success; + GWin32RegistryValueType vtype; + gboolean no_open_with; + GWin32RegistryKey *default_icon_key; + gunichar2 *icon_source; + GIcon *icon = NULL; + GWin32RegistryKey *supported_key; - if (applications_key == NULL) + if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell")) return; - if (!g_win32_registry_subkey_iter_init (&app_iter, applications_key, NULL)) - { - g_object_unref (applications_key); - return; - } - - while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL)) - { - gunichar2 *app_exe_basename; - gsize app_exe_basename_len; - GWin32RegistryKey *incapable_app; - gunichar2 *friendly_app_name; - gboolean success; - gboolean no_open_with; - GWin32RegistryValueType vtype; - GWin32RegistryKey *default_icon_key; - gunichar2 *icon_source; - GIcon *icon = NULL; - gchar *appexe; - gchar *appexe_basename; - gchar *appexe_folded; - gchar *appexe_folded_basename; - GWin32AppInfoApplication *app; - GWin32RegistryKey *shell_open_command_key; - gunichar2 *shell_open_command; - GWin32RegistryKey *supported_key; - - if (!g_win32_registry_subkey_iter_get_name_w (&app_iter, - &app_exe_basename, - &app_exe_basename_len, - NULL) || - !g_utf16_validate (app_exe_basename, app_exe_basename_len)) - continue; - - incapable_app = - g_win32_registry_key_get_child_w (applications_key, - app_exe_basename, - NULL); - - if (incapable_app == NULL) - continue; - - _g_win32_extract_executable (app_exe_basename, - &appexe, - &appexe_basename, - &appexe_folded, - &appexe_folded_basename, - NULL); - - shell_open_command_key = - g_win32_registry_key_get_child_w (incapable_app, - L"shell\\open\\command", - NULL); - - shell_open_command = NULL; - - if (shell_open_command_key != NULL) - { - success = g_win32_registry_key_get_value_w (shell_open_command_key, - NULL, - TRUE, - L"", - &vtype, - (gpointer *) &shell_open_command, - NULL, - NULL); - - if (success && - (vtype != G_WIN32_REGISTRY_VALUE_STR || - !g_utf16_validate (shell_open_command, -1))) - { - g_clear_pointer (&shell_open_command, g_free); - } - - g_object_unref (shell_open_command_key); - } - - friendly_app_name = NULL; - success = g_win32_registry_key_get_value_w (incapable_app, - g_win32_registry_get_os_dirs_w (), - TRUE, - L"FriendlyAppName", - &vtype, - (void **) &friendly_app_name, - NULL, - NULL); - - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) - g_clear_pointer (&friendly_app_name, g_free); - - no_open_with = FALSE; - success = g_win32_registry_key_get_value_w (incapable_app, - NULL, - TRUE, - L"NoOpenWith", - &vtype, - NULL, - NULL, - NULL); - - if (success) - no_open_with = TRUE; - - default_icon_key = - g_win32_registry_key_get_child_w (incapable_app, - L"DefaultIcon", - NULL); - - icon_source = NULL; - - if (default_icon_key != NULL) - { - success = - g_win32_registry_key_get_value_w (default_icon_key, - NULL, + app = get_app_object (apps_by_exe, + app_exe_basename, + app_exe_basename_u8, + app_exe_basename_u8_folded, + FALSE, + FALSE); + + process_verbs_commands (g_steal_pointer (&verbs), + preferred_verb, + L"HKEY_CLASSES_ROOT\\Applications\\", + app_exe_basename, + TRUE, + app_add_verb, + app, + app); + + friendly_app_name = NULL; + success = g_win32_registry_key_get_value_w (incapable_app, + g_win32_registry_get_os_dirs_w (), TRUE, - L"", + L"FriendlyAppName", &vtype, - (void **) &icon_source, + (void **) &friendly_app_name, NULL, NULL); - if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) - g_clear_pointer (&icon_source, g_free); + if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + g_clear_pointer (&friendly_app_name, g_free); + + no_open_with = g_win32_registry_key_get_value_w (incapable_app, + NULL, + TRUE, + L"NoOpenWith", + &vtype, + NULL, + NULL, + NULL); - g_object_unref (default_icon_key); - } + default_icon_key = + g_win32_registry_key_get_child_w (incapable_app, + L"DefaultIcon", + NULL); - if (icon_source) - { - gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL); - icon = g_themed_icon_new (name); - g_free (name); - } + icon_source = NULL; - app = g_hash_table_lookup (apps_by_exe, appexe_folded_basename); + if (default_icon_key != NULL) + { + success = + g_win32_registry_key_get_value_w (default_icon_key, + NULL, + TRUE, + L"", + &vtype, + (void **) &icon_source, + NULL, + NULL); - if (app == NULL) - { - app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); + if (success && vtype != G_WIN32_REGISTRY_VALUE_STR) + g_clear_pointer (&icon_source, g_free); - if (shell_open_command) - { - gchar *dll_function; - - _g_win32_extract_executable (shell_open_command, - NULL, - NULL, - NULL, - NULL, - &dll_function); - if (dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (shell_open_command); - g_clear_pointer (&dll_function, g_free); - } + g_object_unref (default_icon_key); + } - app->command = - shell_open_command ? g_wcsdup (shell_open_command, -1) : NULL; + if (icon_source) + { + gchar *name = g_utf16_to_utf8 (icon_source, -1, NULL, NULL, NULL); + if (name != NULL) + icon = g_themed_icon_new (name); + g_free (name); + } - if (shell_open_command) - app->command_u8 = g_utf16_to_utf8 (shell_open_command, -1, NULL, NULL, NULL); + app->no_open_with = no_open_with; - app->executable = g_strdup (appexe); - app->executable_basename = &app->executable[appexe_basename - appexe]; - app->executable_folded = g_strdup (appexe_folded); + if (friendly_app_name && + app->localized_pretty_name == NULL) + { + app->localized_pretty_name = g_wcsdup (friendly_app_name, -1); + g_clear_pointer (&app->localized_pretty_name_u8, g_free); + app->localized_pretty_name_u8 = + g_utf16_to_utf8 (friendly_app_name, -1, NULL, NULL, NULL); + } - app->no_open_with = no_open_with; + if (icon && app->icon == NULL) + app->icon = g_object_ref (icon); - if (friendly_app_name) - { - app->localized_pretty_name = g_wcsdup (friendly_app_name, -1); - g_clear_pointer (&app->localized_pretty_name_u8, g_free); - app->localized_pretty_name_u8 = - g_utf16_to_utf8 (friendly_app_name, -1, NULL, NULL, NULL); - } + supported_key = + g_win32_registry_key_get_child_w (incapable_app, + L"SupportedTypes", + NULL); - if (icon) - app->icon = g_object_ref (icon); + if (supported_key && + g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL)) + { + gunichar2 *ext_name; + gsize ext_name_len; - app->user_specific = FALSE; - app->default_app = FALSE; + while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL)) + { + if ((!g_win32_registry_value_iter_get_name_w (&sup_iter, + &ext_name, + &ext_name_len, + NULL)) || + (ext_name_len <= 0) || + (ext_name[0] != L'.')) + continue; - g_hash_table_insert (apps_by_exe, - g_strdup (appexe_folded_basename), - app); + get_file_ext (ext_name, ext_name, app, FALSE); } - supported_key = - g_win32_registry_key_get_child_w (incapable_app, - L"SupportedTypes", - NULL); + g_win32_registry_value_iter_clear (&sup_iter); + } - if (supported_key) - { - GWin32RegistryValueIter sup_iter; - if (g_win32_registry_value_iter_init (&sup_iter, supported_key, NULL)) - { - gunichar2 *ext_name; - gsize ext_name_len; + g_clear_object (&supported_key); + g_free (friendly_app_name); + g_free (icon_source); - while (g_win32_registry_value_iter_next (&sup_iter, TRUE, NULL)) - { - gchar *ext_u8; - gchar *ext_folded; - GWin32AppInfoFileExtension *file_extn; - gboolean file_ext_known; - - if ((!g_win32_registry_value_iter_get_name_w (&sup_iter, - &ext_name, - &ext_name_len, - NULL)) || - (ext_name_len <= 0) || - (ext_name[0] != L'.') || - (!g_utf16_to_utf8_and_fold (ext_name, - -1, - &ext_u8, - &ext_folded))) - continue; - - file_extn = NULL; - file_ext_known = - g_hash_table_lookup_extended (extensions, - ext_folded, - NULL, - (void **) &file_extn); + g_clear_object (&icon); +} - if (!file_ext_known) - { - file_extn = - g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); - file_extn->extension = g_wcsdup (ext_name, -1); - file_extn->extension_u8 = g_strdup (ext_u8); - g_hash_table_insert (extensions, - g_strdup (ext_folded), - file_extn); - } +/* Iterates over subkeys of HKEY_CLASSES_ROOT\\Applications + * and calls read_incapable_app() for each one. + */ +static void +read_exeapps (void) +{ + GWin32RegistryKey *applications_key; + GWin32RegistrySubkeyIter app_iter; - g_hash_table_insert (file_extn->other_apps, - g_strdup (appexe_folded), - g_object_ref (app)); + applications_key = + g_win32_registry_key_new_w (L"HKEY_CLASSES_ROOT\\Applications", NULL); - g_free (ext_u8); - g_free (ext_folded); - } + if (applications_key == NULL) + return; - g_win32_registry_value_iter_clear (&sup_iter); - } + if (!g_win32_registry_subkey_iter_init (&app_iter, applications_key, NULL)) + { + g_object_unref (applications_key); + return; + } - g_object_unref (supported_key); - } + while (g_win32_registry_subkey_iter_next (&app_iter, TRUE, NULL)) + { + const gunichar2 *app_exe_basename; + gsize app_exe_basename_len; + GWin32RegistryKey *incapable_app; + gchar *app_exe_basename_u8; + gchar *app_exe_basename_u8_folded; + + if (!g_win32_registry_subkey_iter_get_name_w (&app_iter, + &app_exe_basename, + &app_exe_basename_len, + NULL) || + !g_utf16_to_utf8_and_fold (app_exe_basename, + app_exe_basename_len, + &app_exe_basename_u8, + &app_exe_basename_u8_folded)) + continue; + incapable_app = + g_win32_registry_key_get_child_w (applications_key, + app_exe_basename, + NULL); - g_free (appexe); - g_free (appexe_folded); - g_free (shell_open_command); - g_free (friendly_app_name); - g_free (icon_source); + if (incapable_app != NULL) + read_incapable_app (incapable_app, + app_exe_basename, + app_exe_basename_u8, + app_exe_basename_u8_folded); - g_clear_object (&icon); g_clear_object (&incapable_app); + g_clear_pointer (&app_exe_basename_u8, g_free); + g_clear_pointer (&app_exe_basename_u8_folded, g_free); } g_win32_registry_subkey_iter_clear (&app_iter); g_object_unref (applications_key); } - +/* Iterates over subkeys of HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\ + * and calls get_file_ext() for each associated handler + * (starting with user-chosen handler, if any) + */ static void read_exts (GWin32RegistryKey *file_exts) { GWin32RegistrySubkeyIter ext_iter; - gunichar2 *file_extension; + const gunichar2 *file_extension; gsize file_extension_len; if (file_exts == NULL) @@ -2375,263 +2508,89 @@ read_exts (GWin32RegistryKey *file_exts) while (g_win32_registry_subkey_iter_next (&ext_iter, TRUE, NULL)) { + GWin32RegistryKey *open_with_progids; + gunichar2 *program_id; + GWin32RegistryValueIter iter; + gunichar2 *value_name; + gsize value_name_len; + GWin32RegistryValueType value_type; + GWin32RegistryKey *user_choice; + if (!g_win32_registry_subkey_iter_get_name_w (&ext_iter, &file_extension, &file_extension_len, NULL)) continue; - get_file_ext (file_extension); - } - - g_win32_registry_subkey_iter_clear (&ext_iter); -} - -static void -read_class_extension (GWin32RegistryKey *classes_root, - gunichar2 *class_name, - gsize class_name_len) -{ - gchar *ext_u8; - gchar *ext_folded; - GWin32AppInfoFileExtension *file_extn; - GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_ext; - GWin32RegistryKey *class_key; - gsize program_id_size; - gunichar2 *program_id; - gunichar2 *proxy_id; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; - gunichar2 *program_command; - gunichar2 *proxy_command; - - class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL); - - if (class_key == NULL) - return; - - program_id = class_name; - program_id_size = (class_name_len + 1) * sizeof (gunichar2); - program_key = proxy_key = NULL; - program_command = proxy_command = NULL; - - if (!follow_class_chain_to_handler (program_id, - program_id_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &ext_u8, - &ext_folded)) - { - g_object_unref (class_key); - return; - } - - - file_extn = g_hash_table_lookup (extensions, ext_folded); - handler_rec = g_hash_table_lookup (handlers, ext_folded); - - if (file_extn == NULL) - { - file_extn = g_object_new (G_TYPE_WIN32_APPINFO_FILE_EXTENSION, NULL); - file_extn->extension = g_wcsdup (class_name, -1); - file_extn->extension_u8 = g_strdup (ext_u8); - g_hash_table_insert (extensions, g_strdup (ext_folded), file_extn); - } - - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = g_wcsdup (program_id, program_id_size); - handler_rec->handler_id_folded = g_strdup (ext_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, program_key, &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (ext_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } - - handler_rec_in_ext = g_hash_table_lookup (file_extn->handlers, - ext_folded); - - if (file_extn->chosen_handler == NULL) - g_hash_table_insert (file_extn->handlers, - g_strdup (ext_folded), - g_object_ref (handler_rec)); - else if (handler_rec_in_ext == NULL) - { - if (file_extn->chosen_handler->handler_id_folded && - strcmp (file_extn->chosen_handler->handler_id_folded, - ext_folded) != 0) - g_hash_table_insert (file_extn->handlers, - g_strdup (ext_folded), - g_object_ref (handler_rec)); - } - - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - g_free (ext_u8); - g_free (ext_folded); - g_object_unref (class_key); -} - -static void -read_class_url (GWin32RegistryKey *classes_root, - gunichar2 *class_name, - gsize class_name_len) -{ - GWin32RegistryKey *class_key; - gboolean success; - GWin32RegistryValueType vtype; - GWin32AppInfoURLSchema *schema_rec; - GWin32AppInfoHandler *handler_rec; - GWin32AppInfoHandler *handler_rec_in_url; - gunichar2 *program_id; - gsize program_id_size; - gunichar2 *program_command; - gunichar2 *proxy_id; - gunichar2 *proxy_command; - gchar *program_id_u8; - gchar *program_id_folded; - GWin32RegistryKey *program_key; - GWin32RegistryKey *proxy_key; + program_id = NULL; + user_choice = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, file_extension, + USER_CHOICE, NULL); + if (user_choice && + g_win32_registry_key_get_value_w (user_choice, + NULL, + TRUE, + L"Progid", + &value_type, + (void **) &program_id, + NULL, + NULL) && + value_type == G_WIN32_REGISTRY_VALUE_STR) + { + /* Note: program_id could be "ProgramID" or "Applications\\program.exe". + * The code still works, but handler_id might have a backslash + * in it - that might trip us up later on. + * Even though in that case this is logically an "application" + * registry entry, we don't treat it in any special way. + * We do scan that registry branch anyway, just not here. + */ + get_file_ext (program_id, file_extension, NULL, TRUE); + } - class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL); + g_clear_object (&user_choice); + g_clear_pointer (&program_id, g_free); - if (class_key == NULL) - return; + open_with_progids = _g_win32_registry_key_build_and_new_w (NULL, FILE_EXTS, + file_extension, + OPEN_WITH_PROGIDS, + NULL); - success = g_win32_registry_key_get_value_w (class_key, - NULL, - TRUE, - L"URL Protocol", - &vtype, - NULL, - NULL, - NULL); - - if (!success || - vtype != G_WIN32_REGISTRY_VALUE_STR) - { - g_object_unref (class_key); - return; - } + if (open_with_progids == NULL) + continue; - program_id = class_name; - program_id_size = (class_name_len + 1) * sizeof (gunichar2); - proxy_key = program_key = NULL; - program_command = proxy_id = proxy_command = NULL; - - if (!follow_class_chain_to_handler (program_id, - program_id_size, - &program_command, - &program_key, - &proxy_id, - &proxy_command, - &proxy_key, - &program_id_u8, - &program_id_folded)) - { - g_object_unref (class_key); - return; - } + if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL)) + { + g_clear_object (&open_with_progids); + continue; + } - schema_rec = g_hash_table_lookup (urls, program_id_folded); - handler_rec = g_hash_table_lookup (handlers, program_id_folded); + while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) + { + if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name, + &value_name_len, + NULL) || + (value_name_len == 0)) + continue; - if (handler_rec == NULL) - { - handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - - handler_rec->proxy_key = proxy_key; - handler_rec->key = program_key; - handler_rec->handler_id = g_wcsdup (program_id, program_id_size); - handler_rec->handler_id_folded = - g_strdup (program_id_folded); - handler_rec->handler_command = - program_command ? g_wcsdup (program_command, -1) : NULL; - handler_rec->proxy_id = proxy_id ? g_wcsdup (proxy_id, -1) : NULL; - handler_rec->proxy_command = - proxy_command ? g_wcsdup (proxy_command, -1) : NULL; - _g_win32_extract_executable (proxy_command ? proxy_command : program_command, - &handler_rec->executable, - &handler_rec->executable_basename, - &handler_rec->executable_folded, - NULL, - &handler_rec->dll_function); - if (handler_rec->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (handler_rec->handler_command ? handler_rec->handler_command : handler_rec->proxy_command); - read_handler_icon (proxy_key, program_key, &handler_rec->icon); - g_hash_table_insert (handlers, - g_strdup (program_id_folded), - handler_rec); - } - else - { - g_clear_object (&program_key); - g_clear_object (&proxy_key); - } + get_file_ext (value_name, file_extension, NULL, FALSE); + } - if (schema_rec == NULL) - { - schema_rec = g_object_new (G_TYPE_WIN32_APPINFO_URL_SCHEMA, NULL); - schema_rec->schema = g_wcsdup (class_name, -1); - schema_rec->schema_u8 = g_strdup (program_id_u8); - schema_rec->schema_folded = g_strdup (program_id_folded); - schema_rec->chosen_handler = g_object_ref (handler_rec); - g_hash_table_insert (urls, - g_strdup (program_id_folded), - schema_rec); + g_win32_registry_value_iter_clear (&iter); + g_clear_object (&open_with_progids); } - if (schema_rec->chosen_handler == NULL) - schema_rec->chosen_handler = g_object_ref (handler_rec); - - handler_rec_in_url = g_hash_table_lookup (schema_rec->handlers, - program_id_folded); - - if (handler_rec_in_url == NULL && schema_rec->chosen_handler != handler_rec) - g_hash_table_insert (schema_rec->handlers, - g_strdup (program_id_folded), - g_object_ref (handler_rec)); - - g_free (program_id_u8); - g_free (program_id_folded); - g_free (program_command); - g_free (proxy_id); - g_free (proxy_command); - g_object_unref (class_key); + g_win32_registry_subkey_iter_clear (&ext_iter); } +/* Iterates over subkeys in HKCR, calls + * get_file_ext() for any subkey that starts with ".", + * or get_url_association() for any subkey that could + * be a URL schema and has a "URL Protocol" value. + */ static void read_classes (GWin32RegistryKey *classes_root) { GWin32RegistrySubkeyIter class_iter; - gunichar2 *class_name; + const gunichar2 *class_name; gsize class_name_len; if (classes_root == NULL) @@ -2650,409 +2609,326 @@ read_classes (GWin32RegistryKey *classes_root) continue; if (class_name[0] == L'.') - read_class_extension (classes_root, class_name, class_name_len); - else { - gsize i; - - for (i = 0; i < class_name_len; i++) - if (!iswalpha (class_name[i])) - break; - - if (i == class_name_len) - read_class_url (classes_root, class_name, class_name_len); - } - } - - g_win32_registry_subkey_iter_clear (&class_iter); -} - -static void -link_chosen_handlers (void) -{ - GHashTableIter iter; - GHashTableIter handler_iter; - gchar *schema_folded; - GWin32AppInfoURLSchema *schema; - gchar *handler_id_folded; - GWin32AppInfoHandler *handler; - gchar *ext_folded; - GWin32AppInfoFileExtension *ext; - - g_hash_table_iter_init (&iter, urls); + GWin32RegistryKey *class_key; + GWin32RegistryValueIter iter; + GWin32RegistryKey *open_with_progids; + gunichar2 *value_name; + gsize value_name_len; - while (g_hash_table_iter_next (&iter, - (gpointer *) &schema_folded, - (gpointer *) &schema)) - { - if (schema->chosen_handler != NULL) - continue; + /* Read the data from the HKCR\\.ext (usually proxied + * to another HKCR subkey) + */ + get_file_ext (class_name, class_name, NULL, FALSE); - g_hash_table_iter_init (&handler_iter, schema->handlers); + class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL); - while (g_hash_table_iter_next (&handler_iter, - (gpointer *) &handler_id_folded, - (gpointer *) &handler)) - { - gchar *proxy_id_folded; + if (class_key == NULL) + continue; - if (schema->chosen_handler != NULL) - break; + open_with_progids = g_win32_registry_key_get_child_w (class_key, L"OpenWithProgids", NULL); + g_clear_object (&class_key); - if (strcmp (handler_id_folded, schema_folded) != 0) + if (open_with_progids == NULL) continue; - if (handler->proxy_command && - handler->proxy_id && - g_utf16_to_utf8_and_fold (handler->proxy_id, - -1, - NULL, - &proxy_id_folded)) + if (!g_win32_registry_value_iter_init (&iter, open_with_progids, NULL)) { - GWin32AppInfoHandler *proxy; - - proxy = g_hash_table_lookup (handlers, proxy_id_folded); - - if (proxy) - { - schema->chosen_handler = g_object_ref (proxy); - g_debug ("Linking schema %s to proxy handler %c ? \"%S\" : %S\n", - schema->schema_u8, - schema->chosen_handler->proxy_id ? 'P' : 'T', - schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id, - schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command); - } - - g_free (proxy_id_folded); + g_clear_object (&open_with_progids); + continue; } - if (schema->chosen_handler == NULL) + /* Read the data for other handlers for this extension */ + while (g_win32_registry_value_iter_next (&iter, TRUE, NULL)) { - schema->chosen_handler = g_object_ref (handler); - g_debug ("Linking schema %s to handler %c ? \"%S\" : %S\n", - schema->schema_u8, - schema->chosen_handler->proxy_id ? 'P' : 'T', - schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id, - schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command); - } - } - } + if (!g_win32_registry_value_iter_get_name_w (&iter, &value_name, + &value_name_len, + NULL) || + (value_name_len == 0)) + continue; - g_hash_table_iter_init (&iter, extensions); + get_file_ext (value_name, class_name, NULL, FALSE); + } - while (g_hash_table_iter_next (&iter, - (gpointer *) &ext_folded, - (gpointer *) &ext)) - { - if (ext->chosen_handler != NULL) - continue; + g_win32_registry_value_iter_clear (&iter); + g_clear_object (&open_with_progids); + } + else + { + gsize i; + GWin32RegistryKey *class_key; + gboolean success; + GWin32RegistryValueType vtype; + gchar *schema_u8; + gchar *schema_u8_folded; - g_hash_table_iter_init (&handler_iter, ext->handlers); + for (i = 0; i < class_name_len; i++) + if (!iswalpha (class_name[i])) + break; - while (g_hash_table_iter_next (&handler_iter, - (gpointer *) &handler_id_folded, - (gpointer *) &handler)) - { - gchar *proxy_id_folded; + if (i != class_name_len) + continue; - if (ext->chosen_handler != NULL) - break; + class_key = g_win32_registry_key_get_child_w (classes_root, class_name, NULL); - if (strcmp (handler_id_folded, ext_folded) != 0) + if (class_key == NULL) continue; - if (handler->proxy_command && - handler->proxy_id && - g_utf16_to_utf8_and_fold (handler->proxy_id, - -1, - NULL, - &proxy_id_folded)) - { - GWin32AppInfoHandler *proxy; + success = g_win32_registry_key_get_value_w (class_key, + NULL, + TRUE, + L"URL Protocol", + &vtype, + NULL, + NULL, + NULL); + g_clear_object (&class_key); - proxy = g_hash_table_lookup (handlers, proxy_id_folded); + if (!success || + vtype != G_WIN32_REGISTRY_VALUE_STR) + continue; - if (proxy) - { - ext->chosen_handler = g_object_ref (proxy); - g_debug ("Linking ext %s to proxy handler %c ? \"%S\" : %S\n", - ext->extension_u8, - ext->chosen_handler->proxy_id ? 'P' : 'T', - ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id, - ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command); - } + if (!g_utf16_to_utf8_and_fold (class_name, -1, &schema_u8, &schema_u8_folded)) + continue; - g_free (proxy_id_folded); - } + get_url_association (class_name, class_name, schema_u8, schema_u8_folded, NULL, FALSE); - if (ext->chosen_handler == NULL) - { - ext->chosen_handler = g_object_ref (handler); - g_debug ("Linking ext %s to handler %c ? \"%S\" : %S\n", - ext->extension_u8, - ext->chosen_handler->proxy_id ? 'P' : 'T', - ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id, - ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command); - } + g_clear_pointer (&schema_u8, g_free); + g_clear_pointer (&schema_u8_folded, g_free); } } + + g_win32_registry_subkey_iter_clear (&class_iter); } +/* Iterates over all handlers and over all apps, + * and links handler verbs to apps if a handler + * runs the same executable as one of the app verbs. + */ static void -link_handlers_to_registered_apps (void) +link_handlers_to_unregistered_apps (void) { GHashTableIter iter; - GHashTableIter sup_iter; - gchar *app_id_folded; + GHashTableIter app_iter; + GWin32AppInfoHandler *handler; + gchar *handler_id_fld; GWin32AppInfoApplication *app; - gchar *schema_folded; - GWin32AppInfoURLSchema *schema; - gchar *ext_folded; - GWin32AppInfoFileExtension *ext; - gsize unhandled_exts; - - g_hash_table_iter_init (&sup_iter, urls); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &schema_folded, - (gpointer *) &schema)) - { - if (schema->chosen_handler == NULL) - g_debug ("WARNING: schema %s has no chosen handler\n", schema->schema_u8); - } - unhandled_exts= 0; - g_hash_table_iter_init (&sup_iter, extensions); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &ext)) - { - if (ext->chosen_handler == NULL) - { - g_debug ("WARNING: extension %s has no chosen handler\n", - ext->extension_u8); - unhandled_exts += 1; - } - } + gchar *canonical_name_fld; + gchar *appexe_fld_basename; - g_hash_table_iter_init (&iter, apps_by_id); + g_hash_table_iter_init (&iter, handlers); while (g_hash_table_iter_next (&iter, - (gpointer *) &app_id_folded, - (gpointer *) &app)) + (gpointer *) &handler_id_fld, + (gpointer *) &handler)) { - if (app->supported_urls) - { - GWin32AppInfoHandler *handler; + gsize vi; - g_hash_table_iter_init (&sup_iter, app->supported_urls); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &schema_folded, - (gpointer *) &handler)) + for (vi = 0; vi < handler->verbs->len; vi++) + { + GWin32AppInfoShellVerb *handler_verb; + const gchar *handler_exe_basename; + enum { - schema = g_hash_table_lookup (urls, schema_folded); + SH_UNKNOWN, + GOT_SH_INFO, + ERROR_GETTING_SH_INFO, + } have_stat_handler = SH_UNKNOWN; + GWin32PrivateStat handler_verb_exec_info; - g_assert (schema != NULL); + handler_verb = _verb_idx (handler->verbs, vi); - if (schema->chosen_handler != NULL && - schema->chosen_handler->app == NULL) - { - schema->chosen_handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n to schema %s handler %c ? \"%S\" : %S\n", - schema->schema_u8, - schema->chosen_handler->proxy_id ? 'P' : 'T', - schema->chosen_handler->proxy_id ? schema->chosen_handler->proxy_id : schema->chosen_handler->handler_id, - schema->chosen_handler->proxy_command ? schema->chosen_handler->proxy_command : schema->chosen_handler->handler_command); - } - } + if (handler_verb->app != NULL) + continue; - g_hash_table_iter_init (&sup_iter, app->supported_urls); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &schema_folded, - (gpointer *) &handler)) + handler_exe_basename = g_utf8_find_basename (handler_verb->executable_folded, -1); + g_hash_table_iter_init (&app_iter, apps_by_id); + + while (g_hash_table_iter_next (&app_iter, + (gpointer *) &canonical_name_fld, + (gpointer *) &app)) { - if (handler->app == NULL) - { - handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n directly to schema handler to %c ? \"%S\" : %S\n", - handler->proxy_id ? 'P' : 'T', - handler->proxy_id ? handler->proxy_id : handler->handler_id, - handler->proxy_command ? handler->proxy_command : handler->handler_command); - } - } - } + GWin32AppInfoShellVerb *app_verb; + gsize ai; - if (app->supported_exts) - { - GWin32AppInfoHandler *handler; + for (ai = 0; ai < app->verbs->len; ai++) + { + GWin32PrivateStat app_verb_exec_info; + const gchar *app_exe_basename; + app_verb = _verb_idx (app->verbs, ai); - g_hash_table_iter_init (&sup_iter, app->supported_exts); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &handler)) - { - ext = g_hash_table_lookup (extensions, ext_folded); + app_exe_basename = g_utf8_find_basename (app_verb->executable_folded, -1); - g_assert (ext != NULL); + /* First check that the executable paths are identical */ + if (g_strcmp0 (app_verb->executable_folded, handler_verb->executable_folded) != 0) + { + /* If not, check the basenames. If they are different, don't bother + * with further checks. + */ + if (g_strcmp0 (app_exe_basename, handler_exe_basename) != 0) + continue; + + /* Get filesystem IDs for both files. + * For the handler that is attempted only once. + */ + if (have_stat_handler == SH_UNKNOWN) + { + if (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (handler_verb->executable_folded, + &handler_verb_exec_info) == 0) + have_stat_handler = GOT_SH_INFO; + else + have_stat_handler = ERROR_GETTING_SH_INFO; + } + + if (have_stat_handler != GOT_SH_INFO || + (GLIB_PRIVATE_CALL (g_win32_stat_utf8) (app_verb->executable_folded, + &app_verb_exec_info) != 0) || + app_verb_exec_info.file_index != handler_verb_exec_info.file_index) + continue; + } - if (ext->chosen_handler != NULL && - ext->chosen_handler->app == NULL) - { - ext->chosen_handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n to ext %s handler %c ? \"%S\" : %S\n", - ext->extension_u8, - ext->chosen_handler->proxy_id ? 'P' : 'T', - ext->chosen_handler->proxy_id ? ext->chosen_handler->proxy_id : ext->chosen_handler->handler_id, - ext->chosen_handler->proxy_command ? ext->chosen_handler->proxy_command : ext->chosen_handler->handler_command); + handler_verb->app = g_object_ref (app); + break; } } - g_hash_table_iter_init (&sup_iter, app->supported_exts); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &handler)) + if (handler_verb->app != NULL) + continue; + + g_hash_table_iter_init (&app_iter, apps_by_exe); + + while (g_hash_table_iter_next (&app_iter, + (gpointer *) &appexe_fld_basename, + (gpointer *) &app)) { - if (handler->app == NULL) - { - handler->app = g_object_ref (app); - g_debug ("Linking %S", app->canonical_name); - - if (app->localized_pretty_name) - g_debug (" '%S'", app->localized_pretty_name); - else if (app->pretty_name) - g_debug (" '%S'", app->pretty_name); - else - g_debug (" '%s'", app->executable); - - if (app->command) - g_debug (" %S", app->command); - - g_debug ("\n directly to ext handler %c ? \"%S\" : %S\n", - handler->proxy_id ? 'P' : 'T', - handler->proxy_id ? handler->proxy_id : handler->handler_id, - handler->proxy_command ? handler->proxy_command : handler->handler_command); - } - } - } - } + /* Use basename because apps_by_exe only has basenames */ + if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0) + continue; - g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts); - unhandled_exts= 0; - g_hash_table_iter_init (&sup_iter, extensions); - while (g_hash_table_iter_next (&sup_iter, - (gpointer *) &ext_folded, - (gpointer *) &ext)) - { - if (ext->chosen_handler == NULL) - { - g_debug ("WARNING: extension %s has no chosen handler\n", - ext->extension_u8); - unhandled_exts += 1; + handler_verb->app = g_object_ref (app); + break; + } } } - g_debug ("%" G_GSIZE_FORMAT "undefhandled extensions\n", unhandled_exts); } +/* Finds all .ext and schema: handler verbs that have no app linked to them, + * creates a "fake app" object and links these verbs to these + * objects. Objects are identified by the full path to + * the executable being run, thus multiple different invocations + * get grouped in a more-or-less natural way. + * The iteration goes separately over .ext and schema: handlers + * (instead of the global handlers hashmap) to allow us to + * put the handlers into supported_urls or supported_exts as + * needed (handler objects themselves have no knowledge of extensions + * and/or URLs they are associated with). + */ static void -link_handlers_to_unregistered_apps (void) +link_handlers_to_fake_apps (void) { GHashTableIter iter; - GHashTableIter app_iter; + GHashTableIter handler_iter; + gchar *extension_utf8_folded; + GWin32AppInfoFileExtension *file_extn; + gchar *handler_id_fld; GWin32AppInfoHandler *handler; - gchar *handler_id_fc; - GWin32AppInfoApplication *app; - gchar *canonical_name_fc; - gchar *appexe_fc_basename; + gchar *url_utf8_folded; + GWin32AppInfoURLSchema *schema; - g_hash_table_iter_init (&iter, handlers); + g_hash_table_iter_init (&iter, extensions); while (g_hash_table_iter_next (&iter, - (gpointer *) &handler_id_fc, - (gpointer *) &handler)) + (gpointer *) &extension_utf8_folded, + (gpointer *) &file_extn)) { - gchar *hndexe_fc_basename; - - if ((handler->app != NULL) || - (handler->executable_folded == NULL)) - continue; - - g_hash_table_iter_init (&app_iter, apps_by_id); - - while (g_hash_table_iter_next (&app_iter, - (gpointer *) &canonical_name_fc, - (gpointer *) &app)) + g_hash_table_iter_init (&handler_iter, file_extn->handlers); + while (g_hash_table_iter_next (&handler_iter, + (gpointer *) &handler_id_fld, + (gpointer *) &handler)) { - if (app->executable_folded == NULL) - continue; - - if (strcmp (app->executable_folded, - handler->executable_folded) != 0) - continue; - - handler->app = app; - break; - } + gsize vi; - if (handler->app != NULL) - continue; + for (vi = 0; vi < handler->verbs->len; vi++) + { + GWin32AppInfoShellVerb *handler_verb; + GWin32AppInfoApplication *app; + gunichar2 *exename_utf16; + handler_verb = _verb_idx (handler->verbs, vi); - hndexe_fc_basename = g_utf8_casefold (handler->executable_basename, -1); + if (handler_verb->app != NULL) + continue; - if (hndexe_fc_basename == NULL) - continue; + exename_utf16 = g_utf8_to_utf16 (handler_verb->executable, -1, NULL, NULL, NULL); + if (exename_utf16 == NULL) + continue; - g_hash_table_iter_init (&app_iter, apps_by_exe); + app = get_app_object (fake_apps, + exename_utf16, + handler_verb->executable, + handler_verb->executable_folded, + FALSE, + FALSE); + g_clear_pointer (&exename_utf16, g_free); + handler_verb->app = g_object_ref (app); + + app_add_verb (app, + app, + handler_verb->verb_name, + handler_verb->command, + handler_verb->command_utf8, + handler_verb->verb_displayname, + TRUE, + TRUE); + g_hash_table_insert (app->supported_exts, + g_strdup (extension_utf8_folded), + g_object_ref (handler)); + } + } + } - while ((hndexe_fc_basename != NULL) && - (g_hash_table_iter_next (&app_iter, - (gpointer *) &appexe_fc_basename, - (gpointer *) &app))) + g_hash_table_iter_init (&iter, urls); + while (g_hash_table_iter_next (&iter, + (gpointer *) &url_utf8_folded, + (gpointer *) &schema)) + { + g_hash_table_iter_init (&handler_iter, schema->handlers); + while (g_hash_table_iter_next (&handler_iter, + (gpointer *) &handler_id_fld, + (gpointer *) &handler)) { - /* Use basename because apps_by_exe only has basenames */ - if (strcmp (hndexe_fc_basename, appexe_fc_basename) != 0) - continue; + gsize vi; - handler->app = app; - break; - } + for (vi = 0; vi < handler->verbs->len; vi++) + { + GWin32AppInfoShellVerb *handler_verb; + GWin32AppInfoApplication *app; + gchar *command_utf8_folded; + handler_verb = _verb_idx (handler->verbs, vi); - g_free (hndexe_fc_basename); + if (handler_verb->app != NULL) + continue; - if (handler->app == NULL) - g_debug ("WARNING: handler that runs %s has no corresponding app\n", - handler->executable); + command_utf8_folded = g_utf8_casefold (handler_verb->command_utf8, -1); + app = get_app_object (fake_apps, + handler_verb->command, + handler_verb->command_utf8, + command_utf8_folded, + FALSE, + FALSE); + g_clear_pointer (&command_utf8_folded, g_free); + handler_verb->app = g_object_ref (app); + + app_add_verb (app, + app, + handler_verb->verb_name, + handler_verb->command, + handler_verb->command_utf8, + handler_verb->verb_displayname, + TRUE, + TRUE); + g_hash_table_insert (app->supported_urls, + g_strdup (url_utf8_folded), + g_object_ref (handler)); + } + } } } @@ -3083,6 +2959,7 @@ update_registry_data (void) g_clear_pointer (&apps_by_id, g_hash_table_destroy); g_clear_pointer (&apps_by_exe, g_hash_table_destroy); + g_clear_pointer (&fake_apps, g_hash_table_destroy); g_clear_pointer (&urls, g_hash_table_destroy); g_clear_pointer (&extensions, g_hash_table_destroy); g_clear_pointer (&handlers, g_hash_table_destroy); @@ -3104,6 +2981,8 @@ update_registry_data (void) g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); apps_by_exe = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + fake_apps = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); urls = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); extensions = @@ -3134,9 +3013,8 @@ update_registry_data (void) exeapp_end = GetTickCount (); read_classes (classes_root); classes_end = GetTickCount (); - link_chosen_handlers (); - link_handlers_to_registered_apps (); link_handlers_to_unregistered_apps (); + link_handlers_to_fake_apps (); postproc_end = GetTickCount (); g_debug ("Collecting capable appnames: %lums\n" @@ -3147,7 +3025,7 @@ update_registry_data (void) "Reading exe-only apps:...... %lums\n" "Reading classes: %lums\n" "Postprocessing:..............%lums\n" - "TOTAL: %lums\n", + "TOTAL: %lums", collect_end - collect_start, alloc_end - collect_end, capable_end - alloc_end, @@ -3337,7 +3215,7 @@ g_win32_app_info_finalize (GObject *object) info = G_WIN32_APP_INFO (object); - g_clear_pointer (&info->supported_types, g_free); + g_clear_pointer (&info->supported_types, g_strfreev); g_clear_object (&info->app); g_clear_object (&info->handler); @@ -3392,7 +3270,7 @@ g_win32_app_info_new_from_app (GWin32AppInfoApplication *app, if (!ext) continue; - new_info->supported_types[i] = (gchar *) ext; + new_info->supported_types[i] = g_strdup ((gchar *) ext); i += 1; } @@ -3444,30 +3322,41 @@ static gboolean g_win32_app_info_equal (GAppInfo *appinfo1, GAppInfo *appinfo2) { + GWin32AppInfoShellVerb *shverb1 = NULL; + GWin32AppInfoShellVerb *shverb2 = NULL; GWin32AppInfo *info1 = G_WIN32_APP_INFO (appinfo1); GWin32AppInfo *info2 = G_WIN32_APP_INFO (appinfo2); + GWin32AppInfoApplication *app1 = info1->app; + GWin32AppInfoApplication *app2 = info2->app; - if (info1->app == NULL || - info2->app == NULL) + if (app1 == NULL || + app2 == NULL) return info1 == info2; - if (info1->app->canonical_name_folded != NULL && - info2->app->canonical_name_folded != NULL) - return (strcmp (info1->app->canonical_name_folded, - info2->app->canonical_name_folded)) == 0; + if (app1->canonical_name_folded != NULL && + app2->canonical_name_folded != NULL) + return (g_strcmp0 (app1->canonical_name_folded, + app2->canonical_name_folded)) == 0; - if (info1->app->executable_folded != NULL && - info2->app->executable_folded != NULL) - return (strcmp (info1->app->executable_folded, - info2->app->executable_folded)) == 0; + if (app1->verbs->len > 0 && + app2->verbs->len > 0) + { + shverb1 = _verb_idx (app1->verbs, 0); + shverb2 = _verb_idx (app2->verbs, 0); + if (shverb1->executable_folded != NULL && + shverb2->executable_folded != NULL) + return (g_strcmp0 (shverb1->executable_folded, + shverb2->executable_folded)) == 0; + } - return info1->app == info2->app; + return app1 == app2; } static const char * g_win32_app_info_get_id (GAppInfo *appinfo) { GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + GWin32AppInfoShellVerb *shverb; if (info->app == NULL) return NULL; @@ -3475,8 +3364,9 @@ g_win32_app_info_get_id (GAppInfo *appinfo) if (info->app->canonical_name_u8) return info->app->canonical_name_u8; - if (info->app->executable_basename) - return info->app->executable_basename; + if (info->app->verbs->len > 0 && + (shverb = _verb_idx (info->app->verbs, 0))->executable_basename != NULL) + return shverb->executable_basename; return NULL; } @@ -3527,7 +3417,10 @@ g_win32_app_info_get_executable (GAppInfo *appinfo) if (info->app == NULL) return NULL; - return info->app->executable; + if (info->app->verbs->len > 0) + return _verb_idx (info->app->verbs, 0)->executable; + + return NULL; } static const char * @@ -3538,7 +3431,10 @@ g_win32_app_info_get_commandline (GAppInfo *appinfo) if (info->app == NULL) return NULL; - return info->app->command_u8; + if (info->app->verbs->len > 0) + return _verb_idx (info->app->verbs, 0)->command_utf8; + + return NULL; } static GIcon * @@ -3842,14 +3738,6 @@ expand_application_parameters (GWin32AppInfo *info, gboolean res; gchar *a_char; - if (exec_line == NULL) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - P_("Application registry did not specify" - " a shell\\open\\command")); - return FALSE; - } - expanded_exec = g_string_new (NULL); res = FALSE; @@ -3897,42 +3785,44 @@ expand_application_parameters (GWin32AppInfo *info, static gchar * -get_appath_for_exe (gunichar2 *exe_basename) +get_appath_for_exe (const gchar *exe_basename) { GWin32RegistryKey *apppath_key = NULL; GWin32RegistryValueType val_type; - gunichar2 *appath = NULL; + gchar *appath = NULL; gboolean got_value; - gchar *result = NULL; + gchar *key_path = g_strdup_printf ("HKEY_LOCAL_MACHINE\\" + "SOFTWARE\\" + "Microsoft\\" + "Windows\\" + "CurrentVersion\\" + "App Paths\\" + "%s", exe_basename); - apppath_key = _g_win32_registry_key_build_and_new_w (NULL, L"HKEY_LOCAL_MACHINE\\" - L"\\SOFTWARE" - L"\\Microsoft" - L"\\Windows" - L"\\CurrentVersion" - L"\\App Paths\\", - exe_basename, NULL); + apppath_key = g_win32_registry_key_new (key_path, NULL); + g_clear_pointer (&key_path, g_free); if (apppath_key == NULL) return NULL; - got_value = g_win32_registry_key_get_value_w (apppath_key, - NULL, - TRUE, - L"Path", - &val_type, - (void **) &appath, - NULL, - NULL); + got_value = g_win32_registry_key_get_value (apppath_key, + NULL, + TRUE, + "Path", + &val_type, + (void **) &appath, + NULL, + NULL); g_object_unref (apppath_key); - if (got_value && val_type == G_WIN32_REGISTRY_VALUE_STR) - result = g_utf16_to_utf8 (appath, -1, NULL,NULL, NULL); + if (got_value && + val_type == G_WIN32_REGISTRY_VALUE_STR) + return appath; g_clear_pointer (&appath, g_free); - return result; + return appath; } @@ -3946,9 +3836,9 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, gboolean completed = FALSE; char **argv, **envp; int argc; - gchar *command; + const gchar *command; gchar *apppath; - gunichar2 *exe_basename; + GWin32AppInfoShellVerb *shverb; g_return_val_if_fail (info != NULL, FALSE); g_return_val_if_fail (info->app != NULL, FALSE); @@ -3960,52 +3850,32 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, else envp = g_get_environ (); - command = NULL; - exe_basename = NULL; + shverb = NULL; - if (info->handler) - { - if (info->handler->handler_command) - { - command = g_utf16_to_utf8 (info->handler->handler_command, - -1, - NULL, - NULL, - NULL); - exe_basename = g_utf8_to_utf16 (info->handler->executable_basename, - -1, - NULL, - NULL, - NULL); - } - else if (info->handler->proxy_command) - { - command = g_utf16_to_utf8 (info->handler->proxy_command, - -1, - NULL, - NULL, - NULL); - exe_basename = g_utf8_to_utf16 (info->handler->executable_basename, - -1, - NULL, - NULL, - NULL); - } - } + if (info->handler != NULL && + info->handler->verbs->len > 0) + shverb = _verb_idx (info->handler->verbs, 0); + else if (info->app->verbs->len > 0) + shverb = _verb_idx (info->app->verbs, 0); - if (command == NULL) + if (shverb == NULL) { - command = g_strdup (info->app->command_u8); - exe_basename = g_utf8_to_utf16 (info->app->executable_basename, - -1, - NULL, - NULL, - NULL); - } + if (info->handler == NULL) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + P_("The app ‘%s’ in the application object has no verbs"), + g_win32_appinfo_application_get_some_name (info->app)); + else if (info->handler->verbs->len == 0) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + P_("The app ‘%s’ and the handler ‘%s’ in the application object have no verbs"), + g_win32_appinfo_application_get_some_name (info->app), + info->handler->handler_id_folded); - apppath = get_appath_for_exe (exe_basename); + return FALSE; + } - g_free (exe_basename); + g_assert (shverb->command_utf8 != NULL); + command = shverb->command_utf8; + apppath = get_appath_for_exe (shverb->executable_basename); if (apppath) { @@ -4095,7 +3965,6 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, out: g_strfreev (argv); g_strfreev (envp); - g_free (command); return completed; } @@ -4266,7 +4135,9 @@ g_app_info_create_from_commandline (const char *commandline, info = g_object_new (G_TYPE_WIN32_APP_INFO, NULL); app = g_object_new (G_TYPE_WIN32_APPINFO_APPLICATION, NULL); - app->command = g_steal_pointer (&app_command); + app->no_open_with = FALSE; + app->user_specific = FALSE; + app->default_app = FALSE; if (application_name) { @@ -4279,21 +4150,16 @@ g_app_info_create_from_commandline (const char *commandline, app->canonical_name_folded = g_utf8_casefold (application_name, -1); } - _g_win32_extract_executable (app->command, - &app->executable, - &app->executable_basename, - &app->executable_folded, - NULL, - &app->dll_function); - if (app->dll_function != NULL) - _g_win32_fixup_broken_microsoft_rundll_commandline (app->command); - - app->command_u8 = g_utf16_to_utf8 (app->command, -1, NULL, NULL, NULL); - - app->no_open_with = FALSE; - app->user_specific = FALSE; - app->default_app = FALSE; + app_add_verb (app, + app, + L"open", + app_command, + commandline, + "open", + TRUE, + FALSE); + g_clear_pointer (&app_command, g_free); info->app = app; info->handler = NULL; @@ -4333,9 +4199,10 @@ g_win32_app_info_iface_init (GAppInfoIface *iface) GAppInfo * g_app_info_get_default_for_uri_scheme (const char *uri_scheme) { - GWin32AppInfoURLSchema *scheme; + GWin32AppInfoURLSchema *scheme = NULL; char *scheme_down; GAppInfo *result; + GWin32AppInfoShellVerb *shverb; scheme_down = g_utf8_casefold (uri_scheme, -1); @@ -4345,30 +4212,28 @@ g_app_info_get_default_for_uri_scheme (const char *uri_scheme) if (strcmp (scheme_down, "file") == 0) { g_free (scheme_down); + return NULL; } g_win32_appinfo_init (); G_LOCK (gio_win32_appinfo); - scheme = g_hash_table_lookup (urls, scheme_down); + g_set_object (&scheme, g_hash_table_lookup (urls, scheme_down)); g_free (scheme_down); - if (scheme) - g_object_ref (scheme); - G_UNLOCK (gio_win32_appinfo); result = NULL; if (scheme != NULL && scheme->chosen_handler != NULL && - scheme->chosen_handler->app != NULL) - result = g_win32_app_info_new_from_app (scheme->chosen_handler->app, + scheme->chosen_handler->verbs->len > 0 && + (shverb = _verb_idx (scheme->chosen_handler->verbs, 0))->app != NULL) + result = g_win32_app_info_new_from_app (shverb->app, scheme->chosen_handler); - if (scheme) - g_object_unref (scheme); + g_clear_object (&scheme); return result; } @@ -4377,12 +4242,10 @@ GAppInfo * g_app_info_get_default_for_type (const char *content_type, gboolean must_support_uris) { - GWin32AppInfoFileExtension *ext; + GWin32AppInfoFileExtension *ext = NULL; char *ext_down; - GWin32AppInfoHandler *handler; GAppInfo *result; - GWin32AppInfoApplication *app; - GHashTableIter iter; + GWin32AppInfoShellVerb *shverb; ext_down = g_utf8_casefold (content_type, -1); @@ -4393,56 +4256,47 @@ g_app_info_get_default_for_type (const char *content_type, G_LOCK (gio_win32_appinfo); /* Assuming that "content_type" is a file extension, not a MIME type */ - ext = g_hash_table_lookup (extensions, ext_down); + g_set_object (&ext, g_hash_table_lookup (extensions, ext_down)); g_free (ext_down); - result = NULL; + G_UNLOCK (gio_win32_appinfo); - if (ext != NULL) - g_object_ref (ext); + if (ext == NULL) + return NULL; - G_UNLOCK (gio_win32_appinfo); + result = NULL; - if (ext != NULL) + if (ext->chosen_handler != NULL && + ext->chosen_handler->verbs->len > 0 && + (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL && + (!must_support_uris || + g_win32_app_supports_uris (shverb->app))) + result = g_win32_app_info_new_from_app (shverb->app, + ext->chosen_handler); + else { - if (ext->chosen_handler != NULL && - ext->chosen_handler->app != NULL && - (!must_support_uris || - g_win32_app_supports_uris (ext->chosen_handler->app))) - result = g_win32_app_info_new_from_app (ext->chosen_handler->app, - ext->chosen_handler); - else + GHashTableIter iter; + GWin32AppInfoHandler *handler; + + g_hash_table_iter_init (&iter, ext->handlers); + + while (result == NULL && + g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler)) { - g_hash_table_iter_init (&iter, ext->handlers); + if (handler->verbs->len == 0) + continue; - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler)) - { - if (handler->app && - (!must_support_uris || - g_win32_app_supports_uris (ext->chosen_handler->app))) - { - result = g_win32_app_info_new_from_app (handler->app, handler); - break; - } - } + shverb = _verb_idx (handler->verbs, 0); - if (result == NULL) - { - g_hash_table_iter_init (&iter, ext->other_apps); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app)) - { - if (!must_support_uris || - g_win32_app_supports_uris (ext->chosen_handler->app)) - { - result = g_win32_app_info_new_from_app (app, NULL); - break; - } - } - } + if (shverb->app && + (!must_support_uris || + g_win32_app_supports_uris (shverb->app))) + result = g_win32_app_info_new_from_app (shverb->app, handler); } - g_object_unref (ext); } + g_clear_object (&ext); + return result; } @@ -4478,12 +4332,13 @@ g_app_info_get_all (void) GList * g_app_info_get_all_for_type (const char *content_type) { - GWin32AppInfoFileExtension *ext; + GWin32AppInfoFileExtension *ext = NULL; char *ext_down; GWin32AppInfoHandler *handler; - GWin32AppInfoApplication *app; GHashTableIter iter; + GHashTable *apps = NULL; GList *result; + GWin32AppInfoShellVerb *shverb; ext_down = g_utf8_casefold (content_type, -1); @@ -4494,46 +4349,52 @@ g_app_info_get_all_for_type (const char *content_type) G_LOCK (gio_win32_appinfo); /* Assuming that "content_type" is a file extension, not a MIME type */ - ext = g_hash_table_lookup (extensions, ext_down); + g_set_object (&ext, g_hash_table_lookup (extensions, ext_down)); g_free (ext_down); - result = NULL; - - if (ext != NULL) - g_object_ref (ext); - G_UNLOCK (gio_win32_appinfo); if (ext == NULL) return NULL; + result = NULL; + /* Used as a set to ensure uniqueness */ + apps = g_hash_table_new (g_direct_hash, g_direct_equal); + if (ext->chosen_handler != NULL && - ext->chosen_handler->app != NULL) - result = g_list_prepend (result, - g_win32_app_info_new_from_app (ext->chosen_handler->app, - ext->chosen_handler)); + ext->chosen_handler->verbs->len > 0 && + (shverb = _verb_idx (ext->chosen_handler->verbs, 0))->app != NULL) + { + g_hash_table_add (apps, shverb->app); + result = g_list_prepend (result, + g_win32_app_info_new_from_app (shverb->app, + ext->chosen_handler)); + } g_hash_table_iter_init (&iter, ext->handlers); while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &handler)) { - if (handler->app && - (ext->chosen_handler == NULL || ext->chosen_handler->app != handler->app)) - result = g_list_prepend (result, - g_win32_app_info_new_from_app (handler->app, - handler)); - } + gsize vi; - g_hash_table_iter_init (&iter, ext->other_apps); + for (vi = 0; vi < handler->verbs->len; vi++) + { + shverb = _verb_idx (handler->verbs, vi); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &app)) - { - result = g_list_prepend (result, g_win32_app_info_new_from_app (app, NULL)); - } + if (shverb->app == NULL || + g_hash_table_contains (apps, shverb->app)) + continue; - g_object_unref (ext); + g_hash_table_add (apps, shverb->app); + result = g_list_prepend (result, + g_win32_app_info_new_from_app (shverb->app, + handler)); + } + } + g_clear_object (&ext); result = g_list_reverse (result); + g_hash_table_unref (apps); return result; } diff --git a/gio/gwin32registrykey.c b/gio/gwin32registrykey.c index 2eb67daf8..6b24fdd90 100644 --- a/gio/gwin32registrykey.c +++ b/gio/gwin32registrykey.c @@ -28,8 +28,6 @@ #include <ntstatus.h> #include <winternl.h> -#include "gstrfuncsprivate.h" - #ifndef _WDMDDK_ typedef enum _KEY_INFORMATION_CLASS { KeyBasicInformation, @@ -127,34 +125,16 @@ typedef enum G_WIN32_REGISTRY_UPDATED_PATH = 1, } GWin32RegistryKeyUpdateFlag; -static gsize -g_utf16_len (const gunichar2 *str) -{ - gsize result; - - for (result = 0; str[0] != 0; str++, result++) - ; - - return result; -} - static gunichar2 * -g_wcsdup (const gunichar2 *str, gssize str_len) +g_wcsdup (const gunichar2 *str, + gssize str_size) { - gsize str_len_unsigned; - gsize str_size; - - g_return_val_if_fail (str != NULL, NULL); - - if (str_len < 0) - str_len_unsigned = g_utf16_len (str); - else - str_len_unsigned = (gsize) str_len; - - g_assert (str_len_unsigned <= G_MAXSIZE / sizeof (gunichar2) - 1); - str_size = (str_len_unsigned + 1) * sizeof (gunichar2); - - return g_memdup2 (str, str_size); + if (str_size == -1) + { + str_size = wcslen (str) + 1; + str_size *= sizeof (gunichar2); + } + return g_memdup (str, str_size); } /** @@ -267,7 +247,7 @@ g_win32_registry_value_iter_copy (const GWin32RegistryValueIter *iter) new_iter->value_name_size = iter->value_name_size; if (iter->value_data != NULL) - new_iter->value_data = g_memdup2 (iter->value_data, iter->value_data_size); + new_iter->value_data = g_memdup (iter->value_data, iter->value_data_size); new_iter->value_data_size = iter->value_data_size; @@ -288,8 +268,8 @@ g_win32_registry_value_iter_copy (const GWin32RegistryValueIter *iter) new_iter->value_data_expanded_charsize = iter->value_data_expanded_charsize; if (iter->value_data_expanded_u8 != NULL) - new_iter->value_data_expanded_u8 = g_memdup2 (iter->value_data_expanded_u8, - iter->value_data_expanded_charsize); + new_iter->value_data_expanded_u8 = g_memdup (iter->value_data_expanded_u8, + iter->value_data_expanded_charsize); new_iter->value_data_expanded_u8_size = iter->value_data_expanded_charsize; @@ -966,7 +946,7 @@ g_win32_registry_subkey_iter_next (GWin32RegistrySubkeyIter *iter, **/ gboolean g_win32_registry_subkey_iter_get_name_w (GWin32RegistrySubkeyIter *iter, - gunichar2 **subkey_name, + const gunichar2 **subkey_name, gsize *subkey_name_len, GError **error) { @@ -1008,7 +988,7 @@ g_win32_registry_subkey_iter_get_name_w (GWin32RegistrySubkeyIter *iter, **/ gboolean g_win32_registry_subkey_iter_get_name (GWin32RegistrySubkeyIter *iter, - gchar **subkey_name, + const gchar **subkey_name, gsize *subkey_name_len, GError **error) { @@ -1033,13 +1013,15 @@ g_win32_registry_subkey_iter_get_name (GWin32RegistrySubkeyIter *iter, &subkey_name_len_glong, error); - if (iter->subkey_name_u8 != NULL) - { - *subkey_name_len = subkey_name_len_glong; - return TRUE; - } + if (iter->subkey_name_u8 == NULL) + return FALSE; - return FALSE; + *subkey_name = iter->subkey_name_u8; + + if (subkey_name_len) + *subkey_name_len = subkey_name_len_glong; + + return TRUE; } /** diff --git a/gio/gwin32registrykey.h b/gio/gwin32registrykey.h index 28b57a843..f92a10caf 100644 --- a/gio/gwin32registrykey.h +++ b/gio/gwin32registrykey.h @@ -191,12 +191,12 @@ gboolean g_win32_registry_subkey_iter_next (GWin32RegistrySubk GError **error); GLIB_AVAILABLE_IN_2_46 gboolean g_win32_registry_subkey_iter_get_name (GWin32RegistrySubkeyIter *iter, - gchar **subkey_name, + const gchar **subkey_name, gsize *subkey_name_len, GError **error); GLIB_AVAILABLE_IN_2_46 gboolean g_win32_registry_subkey_iter_get_name_w (GWin32RegistrySubkeyIter *iter, - gunichar2 **subkey_name, + const gunichar2 **subkey_name, gsize *subkey_name_len, GError **error); diff --git a/gio/meson.build b/gio/meson.build index 40a9ca6d0..37af9e438 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -874,14 +874,14 @@ endif # Dependencies used by executables below have_libelf = false -libelf = dependency('libelf', version : '>= 0.8.12', required : false) +libelf = dependency('libelf', version : '>= 0.8.12', required : get_option ('libelf')) if libelf.found() have_libelf = true else # This fallback is necessary on *BSD. elfutils isn't the only libelf # implementation, and *BSD usually includes their own libelf as a system # library which doesn't have a corresponding .pc file. - libelf = cc.find_library('elf', required : false) + libelf = cc.find_library('elf', required : get_option ('libelf')) have_libelf = libelf.found() have_libelf = have_libelf and cc.has_function('elf_begin', dependencies : libelf) have_libelf = have_libelf and cc.has_function('elf_getshdrstrndx', dependencies : libelf) diff --git a/gio/tests/async-close-output-stream.c b/gio/tests/async-close-output-stream.c index d3f97a119..5f6620275 100644 --- a/gio/tests/async-close-output-stream.c +++ b/gio/tests/async-close-output-stream.c @@ -24,8 +24,6 @@ #include <stdlib.h> #include <string.h> -#include "gstrfuncsprivate.h" - #define DATA_TO_WRITE "Hello world\n" typedef struct @@ -149,9 +147,9 @@ prepare_data (SetupData *data, data->expected_size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (data->data_stream)); - g_assert_cmpuint (data->expected_size, >, 0); + g_assert_cmpint (data->expected_size, >, 0); - data->expected_output = g_memdup2 (written, data->expected_size); + data->expected_output = g_memdup (written, (guint)data->expected_size); /* then recreate the streams and prepare them for the asynchronous close */ destroy_streams (data); diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c index bc50ff491..f4e509a59 100644 --- a/gio/tests/desktop-app-info.c +++ b/gio/tests/desktop-app-info.c @@ -571,8 +571,7 @@ assert_implementations (const gchar *interface, "gnome-terminal.desktop nautilus-autorun-software.desktop gcr-viewer.desktop " \ "nautilus-connect-server.desktop kde4-dolphin.desktop gnome-music.desktop " \ "kde4-konqbrowser.desktop gucharmap.desktop kde4-okular.desktop nautilus.desktop " \ - "gedit.desktop evince.desktop file-roller.desktop dconf-editor.desktop glade.desktop " \ - "invalid-desktop.desktop" + "gedit.desktop evince.desktop file-roller.desktop dconf-editor.desktop glade.desktop" #define HOME_APPS "epiphany-weather-for-toronto-island-9c6a4e022b17686306243dada811d550d25eb1fb.desktop" #define ALL_HOME_APPS HOME_APPS " eog.desktop" @@ -727,9 +726,6 @@ test_show_in (void) assert_shown ("gcr-prompter.desktop", TRUE, "GNOME-Classic"); assert_shown ("gcr-prompter.desktop", TRUE, "GNOME-Classic:KDE"); assert_shown ("gcr-prompter.desktop", TRUE, "KDE:GNOME-Classic"); - assert_shown ("invalid-desktop.desktop", TRUE, "GNOME"); - assert_shown ("invalid-desktop.desktop", FALSE, "../invalid/desktop"); - assert_shown ("invalid-desktop.desktop", FALSE, "../invalid/desktop:../invalid/desktop"); } /* Test g_desktop_app_info_launch_uris_as_manager() and diff --git a/gio/tests/desktop-files/usr/applications/invalid-desktop.desktop b/gio/tests/desktop-files/usr/applications/invalid-desktop.desktop deleted file mode 100644 index dffaa2469..000000000 --- a/gio/tests/desktop-files/usr/applications/invalid-desktop.desktop +++ /dev/null @@ -1,5 +0,0 @@ -[Desktop Entry] -Type=Application -Name=appinfo-test -OnlyShowIn=../invalid/desktop;GNOME -NotShowIn=ROX; diff --git a/gio/tests/file.c b/gio/tests/file.c index ddd1ffcba..e951e1fcd 100644 --- a/gio/tests/file.c +++ b/gio/tests/file.c @@ -686,7 +686,7 @@ test_replace_cancel (void) guint count; GError *error = NULL; - g_test_bug ("https://bugzilla.gnome.org/629301"); + g_test_bug ("629301"); path = g_dir_make_tmp ("g_file_replace_cancel_XXXXXX", &error); g_assert_no_error (error); @@ -806,113 +806,6 @@ test_replace_cancel (void) } static void -test_replace_symlink (void) -{ -#ifdef G_OS_UNIX - gchar *tmpdir_path = NULL; - GFile *tmpdir = NULL, *source_file = NULL, *target_file = NULL; - GFileOutputStream *stream = NULL; - const gchar *new_contents = "this is a test message which should be written to source and not target"; - gsize n_written; - GFileEnumerator *enumerator = NULL; - GFileInfo *info = NULL; - gchar *contents = NULL; - gsize length = 0; - GError *local_error = NULL; - - g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2325"); - g_test_summary ("Test that G_FILE_CREATE_REPLACE_DESTINATION doesn’t follow symlinks"); - - /* Create a fresh, empty working directory. */ - tmpdir_path = g_dir_make_tmp ("g_file_replace_symlink_XXXXXX", &local_error); - g_assert_no_error (local_error); - tmpdir = g_file_new_for_path (tmpdir_path); - - g_test_message ("Using temporary directory %s", tmpdir_path); - g_free (tmpdir_path); - - /* Create symlink `source` which points to `target`. */ - source_file = g_file_get_child (tmpdir, "source"); - target_file = g_file_get_child (tmpdir, "target"); - g_file_make_symbolic_link (source_file, "target", NULL, &local_error); - g_assert_no_error (local_error); - - /* Ensure that `target` doesn’t exist */ - g_assert_false (g_file_query_exists (target_file, NULL)); - - /* Replace the `source` symlink with a regular file using - * %G_FILE_CREATE_REPLACE_DESTINATION, which should replace it *without* - * following the symlink */ - stream = g_file_replace (source_file, NULL, FALSE /* no backup */, - G_FILE_CREATE_REPLACE_DESTINATION, NULL, &local_error); - g_assert_no_error (local_error); - - g_output_stream_write_all (G_OUTPUT_STREAM (stream), new_contents, strlen (new_contents), - &n_written, NULL, &local_error); - g_assert_no_error (local_error); - g_assert_cmpint (n_written, ==, strlen (new_contents)); - - g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &local_error); - g_assert_no_error (local_error); - - g_clear_object (&stream); - - /* At this point, there should still only be one file: `source`. It should - * now be a regular file. `target` should not exist. */ - enumerator = g_file_enumerate_children (tmpdir, - G_FILE_ATTRIBUTE_STANDARD_NAME "," - G_FILE_ATTRIBUTE_STANDARD_TYPE, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &local_error); - g_assert_no_error (local_error); - - info = g_file_enumerator_next_file (enumerator, NULL, &local_error); - g_assert_no_error (local_error); - g_assert_nonnull (info); - - g_assert_cmpstr (g_file_info_get_name (info), ==, "source"); - g_assert_cmpint (g_file_info_get_file_type (info), ==, G_FILE_TYPE_REGULAR); - - g_clear_object (&info); - - info = g_file_enumerator_next_file (enumerator, NULL, &local_error); - g_assert_no_error (local_error); - g_assert_null (info); - - g_file_enumerator_close (enumerator, NULL, &local_error); - g_assert_no_error (local_error); - g_clear_object (&enumerator); - - /* Double-check that `target` doesn’t exist */ - g_assert_false (g_file_query_exists (target_file, NULL)); - - /* Check the content of `source`. */ - g_file_load_contents (source_file, - NULL, - &contents, - &length, - NULL, - &local_error); - g_assert_no_error (local_error); - g_assert_cmpstr (contents, ==, new_contents); - g_assert_cmpuint (length, ==, strlen (new_contents)); - g_free (contents); - - /* Tidy up. */ - g_file_delete (source_file, NULL, &local_error); - g_assert_no_error (local_error); - - g_file_delete (tmpdir, NULL, &local_error); - g_assert_no_error (local_error); - - g_clear_object (&target_file); - g_clear_object (&source_file); - g_clear_object (&tmpdir); -#else /* if !G_OS_UNIX */ - g_test_skip ("Symlink replacement tests can only be run on Unix") -#endif -} - -static void on_file_deleted (GObject *object, GAsyncResult *result, gpointer user_data) @@ -1148,7 +1041,7 @@ test_measure (void) if (size > 0) g_assert_cmpuint (num_bytes, ==, size); g_assert_cmpuint (num_dirs, ==, 6); - g_assert_cmpuint (num_files, ==, 32); + g_assert_cmpuint (num_files, ==, 31); g_object_unref (file); g_free (path); @@ -1238,7 +1131,7 @@ test_measure_async (void) g_free (path); data->expected_dirs = 6; - data->expected_files = 32; + data->expected_files = 31; g_file_measure_disk_usage_async (file, G_FILE_MEASURE_APPARENT_SIZE, @@ -1887,11 +1780,86 @@ test_writev_async_all_too_big_vectors (void) g_object_unref (file); } +static void +test_build_attribute_list_for_copy (void) +{ + GFile *tmpfile; + GFileIOStream *iostream; + GError *error = NULL; + const GFileCopyFlags test_flags[] = + { + G_FILE_COPY_NONE, + G_FILE_COPY_TARGET_DEFAULT_PERMS, + G_FILE_COPY_ALL_METADATA, + G_FILE_COPY_ALL_METADATA | G_FILE_COPY_TARGET_DEFAULT_PERMS, + }; + gsize i; + char *attrs; + gchar *attrs_with_commas; + + tmpfile = g_file_new_tmp ("tmp-build-attribute-list-for-copyXXXXXX", + &iostream, &error); + g_assert_no_error (error); + g_io_stream_close ((GIOStream*)iostream, NULL, &error); + g_assert_no_error (error); + g_clear_object (&iostream); + + for (i = 0; i < G_N_ELEMENTS (test_flags); i++) + { + GFileCopyFlags flags = test_flags[i]; + + attrs = g_file_build_attribute_list_for_copy (tmpfile, flags, NULL, &error); + g_test_message ("Attributes for copy: %s", attrs); + g_assert_no_error (error); + g_assert_nonnull (attrs); + attrs_with_commas = g_strconcat (",", attrs, ",", NULL); + g_free (attrs); + + /* See g_local_file_class_init for reference. */ + if (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_MODE ",")); + else + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_MODE ",")); +#ifdef G_OS_UNIX + if (flags & G_FILE_COPY_ALL_METADATA) + { + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_UID ",")); + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_GID ",")); + } + else + { + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_UID ",")); + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_GID ",")); + } +#endif +#ifdef HAVE_UTIMES + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_MODIFIED ",")); + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC ",")); + if (flags & G_FILE_COPY_ALL_METADATA) + { + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS ",")); + g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS_USEC ",")); + } + else + { + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS ",")); + g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS_USEC ",")); + } +#endif + g_free (attrs_with_commas); + } + + (void) g_file_delete (tmpfile, NULL, NULL); + g_clear_object (&tmpfile); +} + int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); + g_test_bug_base ("http://bugzilla.gnome.org/"); + g_test_add_func ("/file/basic", test_basic); g_test_add_func ("/file/build-filename", test_build_filename); g_test_add_func ("/file/parent", test_parent); @@ -1905,7 +1873,6 @@ main (int argc, char *argv[]) g_test_add_data_func ("/file/async-create-delete/4096", GINT_TO_POINTER (4096), test_create_delete); g_test_add_func ("/file/replace-load", test_replace_load); g_test_add_func ("/file/replace-cancel", test_replace_cancel); - g_test_add_func ("/file/replace-symlink", test_replace_symlink); g_test_add_func ("/file/async-delete", test_async_delete); g_test_add_func ("/file/copy-preserve-mode", test_copy_preserve_mode); g_test_add_func ("/file/measure", test_measure); @@ -1923,6 +1890,7 @@ main (int argc, char *argv[]) g_test_add_func ("/file/writev/async_all-no-vectors", test_writev_async_all_no_vectors); g_test_add_func ("/file/writev/async_all-to-big-vectors", test_writev_async_all_too_big_vectors); g_test_add_func ("/file/writev/async_all-cancellation", test_writev_async_all_cancellation); + g_test_add_func ("/file/build-attribute-list-for-copy", test_build_attribute_list_for_copy); return g_test_run (); } diff --git a/gio/tests/gdbus-auth.c b/gio/tests/gdbus-auth.c index 8be4473c7..18288f36d 100644 --- a/gio/tests/gdbus-auth.c +++ b/gio/tests/gdbus-auth.c @@ -121,7 +121,7 @@ test_auth_on_timeout (gpointer user_data) { g_error ("Timeout waiting for client"); g_assert_not_reached (); - return FALSE; + return G_SOURCE_REMOVE; } diff --git a/gio/tests/gdbus-close-pending.c b/gio/tests/gdbus-close-pending.c index d2c5b62f5..bd8fda5a7 100644 --- a/gio/tests/gdbus-close-pending.c +++ b/gio/tests/gdbus-close-pending.c @@ -188,7 +188,7 @@ delayed_close_cb (gpointer data) close_async (df->stream, df->io_priority, df->cancellable, df->callback, df->user_data); - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/gio/tests/gdbus-connection-loss.c b/gio/tests/gdbus-connection-loss.c index a34a9923c..cc88cb0cc 100644 --- a/gio/tests/gdbus-connection-loss.c +++ b/gio/tests/gdbus-connection-loss.c @@ -53,7 +53,7 @@ on_timeout (gpointer user_data) { /* tear down bus */ session_bus_stop (); - return FALSE; /* remove source */ + return G_SOURCE_REMOVE; } static void diff --git a/gio/tests/gdbus-connection-slow.c b/gio/tests/gdbus-connection-slow.c index dd1ababd4..27a6af3c4 100644 --- a/gio/tests/gdbus-connection-slow.c +++ b/gio/tests/gdbus-connection-slow.c @@ -49,7 +49,7 @@ test_connection_flush_on_timeout (gpointer user_data) guint iteration = GPOINTER_TO_UINT (user_data); g_printerr ("Timeout waiting 1000 msec on iteration %d\n", iteration); g_assert_not_reached (); - return FALSE; + return G_SOURCE_REMOVE; } static void @@ -125,7 +125,7 @@ large_message_timeout_cb (gpointer data) g_error ("Error: timeout waiting for dbus name to appear"); - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/gio/tests/gdbus-connection.c b/gio/tests/gdbus-connection.c index 7bd7a02f4..278570609 100644 --- a/gio/tests/gdbus-connection.c +++ b/gio/tests/gdbus-connection.c @@ -65,7 +65,7 @@ test_connection_quit_mainloop (gpointer user_data) _log ("quit_mainloop_fired"); *quit_mainloop_fired = TRUE; g_main_loop_quit (loop); - return TRUE; + return G_SOURCE_CONTINUE; } /* ---------------------------------------------------------------------------------------------------- */ @@ -934,7 +934,7 @@ test_connection_filter_on_timeout (gpointer user_data) { g_printerr ("Timeout waiting 30 sec on service\n"); g_assert_not_reached (); - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/gio/tests/gdbus-example-objectmanager-server.c b/gio/tests/gdbus-example-objectmanager-server.c index c460e66e9..dcedba2d1 100644 --- a/gio/tests/gdbus-example-objectmanager-server.c +++ b/gio/tests/gdbus-example-objectmanager-server.c @@ -53,7 +53,7 @@ on_animal_poke (ExampleAnimal *animal, g_assert_not_reached (); out: - return TRUE; /* to indicate that the method was handled */ + return G_DBUS_METHOD_INVOCATION_HANDLED; } diff --git a/gio/tests/gdbus-example-server.c b/gio/tests/gdbus-example-server.c index 6f1123d85..00e482724 100644 --- a/gio/tests/gdbus-example-server.c +++ b/gio/tests/gdbus-example-server.c @@ -315,7 +315,7 @@ on_timeout_cb (gpointer user_data) g_assert_no_error (error); - return TRUE; + return G_SOURCE_CONTINUE; } /* ---------------------------------------------------------------------------------------------------- */ diff --git a/gio/tests/gdbus-exit-on-close.c b/gio/tests/gdbus-exit-on-close.c index 3c4e5ecd6..4241fc7a4 100644 --- a/gio/tests/gdbus-exit-on-close.c +++ b/gio/tests/gdbus-exit-on-close.c @@ -57,7 +57,7 @@ quit_later_cb (gpointer data G_GNUC_UNUSED) { g_main_loop_quit (loop); - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c index ad2489dab..ba5388600 100644 --- a/gio/tests/gdbus-export.c +++ b/gio/tests/gdbus-export.c @@ -23,7 +23,6 @@ #include <string.h> #include "gdbus-tests.h" -#include "gstrfuncsprivate.h" /* all tests rely on a shared mainloop */ static GMainLoop *loop = NULL; @@ -672,7 +671,7 @@ subtree_introspect (GDBusConnection *connection, g_assert_not_reached (); } - return g_memdup2 (interfaces, 2 * sizeof (void *)); + return g_memdup (interfaces, 2 * sizeof (void *)); } static const GDBusInterfaceVTable * @@ -728,7 +727,7 @@ dynamic_subtree_introspect (GDBusConnection *connection, { const GDBusInterfaceInfo *interfaces[2] = { &dyna_interface_info, NULL }; - return g_memdup2 (interfaces, 2 * sizeof (void *)); + return g_memdup (interfaces, 2 * sizeof (void *)); } static const GDBusInterfaceVTable * diff --git a/gio/tests/gdbus-overflow.c b/gio/tests/gdbus-overflow.c index 53ec9c0b6..ca3d5d0e7 100644 --- a/gio/tests/gdbus-overflow.c +++ b/gio/tests/gdbus-overflow.c @@ -95,7 +95,7 @@ static gboolean overflow_on_500ms_later_func (gpointer user_data) { g_main_loop_quit (loop); - return FALSE; /* don't keep the idle */ + return G_SOURCE_REMOVE; } static void diff --git a/gio/tests/gdbus-peer.c b/gio/tests/gdbus-peer.c index a03ebbbbe..ca53528d2 100644 --- a/gio/tests/gdbus-peer.c +++ b/gio/tests/gdbus-peer.c @@ -76,12 +76,6 @@ typedef struct gboolean signal_received; } PeerData; -/* This needs to be enough to usually take more than one write(), - * to reproduce - * <https://gitlab.gnome.org/GNOME/glib/-/issues/2074>. - * 1 MiB ought to be enough. */ -#define BIG_MESSAGE_ARRAY_SIZE (1024 * 1024) - static const gchar *test_interface_introspection_xml = "<node>" " <interface name='org.gtk.GDBus.PeerTestInterface'>" @@ -94,11 +88,6 @@ static const gchar *test_interface_introspection_xml = " <method name='OpenFile'>" " <arg type='s' name='path' direction='in'/>" " </method>" - " <method name='OpenFileWithBigMessage'>" - " <arg type='s' name='path' direction='in'/>" - " <arg type='h' name='handle' direction='out'/>" - " <arg type='ay' name='junk' direction='out'/>" - " </method>" " <signal name='PeerSignal'>" " <arg type='s' name='a_string'/>" " </signal>" @@ -175,8 +164,7 @@ test_interface_method_call (GDBusConnection *connection, g_dbus_method_invocation_return_value (invocation, NULL); } - else if (g_strcmp0 (method_name, "OpenFile") == 0 || - g_strcmp0 (method_name, "OpenFileWithBigMessage") == 0) + else if (g_strcmp0 (method_name, "OpenFile") == 0) { #ifdef G_OS_UNIX const gchar *path; @@ -202,21 +190,6 @@ test_interface_method_call (GDBusConnection *connection, g_object_unref (fd_list); g_object_unref (invocation); - if (g_strcmp0 (method_name, "OpenFileWithBigMessage") == 0) - { - char *junk; - - junk = g_new0 (char, BIG_MESSAGE_ARRAY_SIZE); - g_dbus_message_set_body (reply, - g_variant_new ("(h@ay)", - 0, - g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, - junk, - BIG_MESSAGE_ARRAY_SIZE, - 1))); - g_free (junk); - } - error = NULL; g_dbus_connection_send_message (connection, reply, @@ -671,7 +644,7 @@ check_connection (gpointer user_data) } } - return FALSE; + return G_SOURCE_REMOVE; } static gboolean @@ -681,7 +654,7 @@ on_do_disconnect_in_idle (gpointer data) g_debug ("GDC %p has ref_count %d", c, G_OBJECT (c)->ref_count); g_dbus_connection_disconnect (c); g_object_unref (c); - return FALSE; + return G_SOURCE_REMOVE; } #endif @@ -750,7 +723,6 @@ do_test_peer (void) const gchar *s; GThread *service_thread; gulong signal_handler_id; - gsize i; memset (&data, '\0', sizeof (PeerData)); data.current_connections = g_ptr_array_new_with_free_func (g_object_unref); @@ -871,116 +843,73 @@ do_test_peer (void) g_assert_cmpint (data.num_method_calls, ==, 3); g_signal_handler_disconnect (proxy, signal_handler_id); - /* - * Check for UNIX fd passing. - * - * The first time through, we use a very simple method call. Note that - * because this does not have a G_VARIANT_TYPE_HANDLE in the message body - * to refer to the fd, it is a GDBus-specific idiom that would not - * interoperate with libdbus or sd-bus - * (see <https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1726>). - * - * The second time, we call a method that returns a fd attached to a - * large message, to reproduce - * <https://gitlab.gnome.org/GNOME/glib/-/issues/2074>. It also happens - * to follow the more usual pattern for D-Bus messages containing a - * G_VARIANT_TYPE_HANDLE to refer to attached fds. - */ - for (i = 0; i < 2; i++) - { + /* check for UNIX fd passing */ #ifdef G_OS_UNIX - GDBusMessage *method_call_message; - GDBusMessage *method_reply_message; - GUnixFDList *fd_list; - gint fd; - gchar *buf; - gsize len; - gchar *buf2; - gsize len2; - const char *testfile = g_test_get_filename (G_TEST_DIST, "file.c", NULL); - const char *method = "OpenFile"; - GVariant *body; - - if (i == 1) - method = "OpenFileWithBigMessage"; - - method_call_message = g_dbus_message_new_method_call (NULL, /* name */ - "/org/gtk/GDBus/PeerTestObject", - "org.gtk.GDBus.PeerTestInterface", - method); - g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", testfile)); - error = NULL; - method_reply_message = g_dbus_connection_send_message_with_reply_sync (c, - method_call_message, - G_DBUS_SEND_MESSAGE_FLAGS_NONE, - -1, - NULL, /* out_serial */ - NULL, /* cancellable */ - &error); - g_assert_no_error (error); - g_assert (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN); - - body = g_dbus_message_get_body (method_reply_message); - - if (i == 1) - { - gint32 handle = -1; - GVariant *junk = NULL; - - g_assert_cmpstr (g_variant_get_type_string (body), ==, "(hay)"); - g_variant_get (body, "(h@ay)", &handle, &junk); - g_assert_cmpint (handle, ==, 0); - g_assert_cmpuint (g_variant_n_children (junk), ==, BIG_MESSAGE_ARRAY_SIZE); - g_variant_unref (junk); - } - else - { - g_assert_null (body); - } - - fd_list = g_dbus_message_get_unix_fd_list (method_reply_message); - g_assert (fd_list != NULL); - g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1); - error = NULL; - fd = g_unix_fd_list_get (fd_list, 0, &error); - g_assert_no_error (error); - g_object_unref (method_call_message); - g_object_unref (method_reply_message); + { + GDBusMessage *method_call_message; + GDBusMessage *method_reply_message; + GUnixFDList *fd_list; + gint fd; + gchar *buf; + gsize len; + gchar *buf2; + gsize len2; + const char *testfile = g_test_get_filename (G_TEST_DIST, "file.c", NULL); + + method_call_message = g_dbus_message_new_method_call (NULL, /* name */ + "/org/gtk/GDBus/PeerTestObject", + "org.gtk.GDBus.PeerTestInterface", + "OpenFile"); + g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", testfile)); + error = NULL; + method_reply_message = g_dbus_connection_send_message_with_reply_sync (c, + method_call_message, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + -1, + NULL, /* out_serial */ + NULL, /* cancellable */ + &error); + g_assert_no_error (error); + g_assert (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN); + fd_list = g_dbus_message_get_unix_fd_list (method_reply_message); + g_assert (fd_list != NULL); + g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1); + error = NULL; + fd = g_unix_fd_list_get (fd_list, 0, &error); + g_assert_no_error (error); + g_object_unref (method_call_message); + g_object_unref (method_reply_message); - error = NULL; - len = 0; - buf = read_all_from_fd (fd, &len, &error); - g_assert_no_error (error); - g_assert (buf != NULL); - close (fd); + error = NULL; + len = 0; + buf = read_all_from_fd (fd, &len, &error); + g_assert_no_error (error); + g_assert (buf != NULL); + close (fd); - error = NULL; - g_file_get_contents (testfile, - &buf2, - &len2, - &error); - g_assert_no_error (error); - g_assert_cmpmem (buf, len, buf2, len2); - g_free (buf2); - g_free (buf); + error = NULL; + g_file_get_contents (testfile, + &buf2, + &len2, + &error); + g_assert_no_error (error); + g_assert_cmpmem (buf, len, buf2, len2); + g_free (buf2); + g_free (buf); + } #else - /* We do the same number of iterations on non-Unix, so that - * the method call count will match. In this case we use - * OpenFile both times, because the difference between this - * and OpenFileWithBigMessage is only relevant on Unix. */ - error = NULL; - result = g_dbus_proxy_call_sync (proxy, - "OpenFile", - g_variant_new ("(s)", "boo"), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, /* GCancellable */ - &error); - g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); - g_assert (result == NULL); - g_error_free (error); + error = NULL; + result = g_dbus_proxy_call_sync (proxy, + "OpenFile", + g_variant_new ("(s)", "boo"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* GCancellable */ + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR); + g_assert (result == NULL); + g_error_free (error); #endif /* G_OS_UNIX */ - } /* Check that g_socket_get_credentials() work - (though this really * should be in socket.c) @@ -1088,7 +1017,7 @@ do_test_peer (void) g_variant_get (result, "(&s)", &s); g_assert_cmpstr (s, ==, "You greeted me with 'Hey Again Peer!'."); g_variant_unref (result); - g_assert_cmpint (data.num_method_calls, ==, 6); + g_assert_cmpint (data.num_method_calls, ==, 5); #if 0 /* TODO: THIS TEST DOESN'T WORK YET */ @@ -1139,183 +1068,6 @@ test_peer (void) /* ---------------------------------------------------------------------------------------------------- */ -#define VALID_GUID "0123456789abcdef0123456789abcdef" - -static void -test_peer_invalid_server (void) -{ - GDBusServer *server; - - if (!g_test_undefined ()) - { - g_test_skip ("Not exercising programming errors"); - return; - } - - if (g_test_subprocess ()) - { - /* This assumes we are not going to run out of GDBusServerFlags - * any time soon */ - server = g_dbus_server_new_sync ("tcp:", (GDBusServerFlags) (1 << 30), - VALID_GUID, - NULL, NULL, NULL); - g_assert_null (server); - } - else - { - g_test_trap_subprocess (NULL, 0, 0); - g_test_trap_assert_failed (); - g_test_trap_assert_stderr ("*CRITICAL*G_DBUS_SERVER_FLAGS_ALL*"); - } -} - -static void -test_peer_invalid_conn_stream_sync (void) -{ - GSocket *sock; - GSocketConnection *socket_conn; - GIOStream *iostream; - GDBusConnection *conn; - - if (!g_test_undefined ()) - { - g_test_skip ("Not exercising programming errors"); - return; - } - - sock = g_socket_new (G_SOCKET_FAMILY_IPV4, - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_TCP, - NULL); - - if (sock == NULL) - { - g_test_skip ("TCP not available?"); - return; - } - - socket_conn = g_socket_connection_factory_create_connection (sock); - g_assert_nonnull (socket_conn); - iostream = G_IO_STREAM (socket_conn); - g_assert_nonnull (iostream); - - if (g_test_subprocess ()) - { - /* This assumes we are not going to run out of GDBusConnectionFlags - * any time soon */ - conn = g_dbus_connection_new_sync (iostream, VALID_GUID, - (GDBusConnectionFlags) (1 << 30), - NULL, NULL, NULL); - g_assert_null (conn); - } - else - { - g_test_trap_subprocess (NULL, 0, 0); - g_test_trap_assert_failed (); - g_test_trap_assert_stderr ("*CRITICAL*G_DBUS_CONNECTION_FLAGS_ALL*"); - } - - g_clear_object (&sock); - g_clear_object (&socket_conn); -} - -static void -test_peer_invalid_conn_stream_async (void) -{ - GSocket *sock; - GSocketConnection *socket_conn; - GIOStream *iostream; - - if (!g_test_undefined ()) - { - g_test_skip ("Not exercising programming errors"); - return; - } - - sock = g_socket_new (G_SOCKET_FAMILY_IPV4, - G_SOCKET_TYPE_STREAM, - G_SOCKET_PROTOCOL_TCP, - NULL); - - if (sock == NULL) - { - g_test_skip ("TCP not available?"); - return; - } - - socket_conn = g_socket_connection_factory_create_connection (sock); - g_assert_nonnull (socket_conn); - iostream = G_IO_STREAM (socket_conn); - g_assert_nonnull (iostream); - - if (g_test_subprocess ()) - { - g_dbus_connection_new (iostream, VALID_GUID, - (GDBusConnectionFlags) (1 << 30), - NULL, NULL, NULL, NULL); - } - else - { - g_test_trap_subprocess (NULL, 0, 0); - g_test_trap_assert_failed (); - g_test_trap_assert_stderr ("*CRITICAL*G_DBUS_CONNECTION_FLAGS_ALL*"); - } - - g_clear_object (&sock); - g_clear_object (&socket_conn); -} - -static void -test_peer_invalid_conn_addr_sync (void) -{ - GDBusConnection *conn; - - if (!g_test_undefined ()) - { - g_test_skip ("Not exercising programming errors"); - return; - } - - if (g_test_subprocess ()) - { - conn = g_dbus_connection_new_for_address_sync ("tcp:", - (GDBusConnectionFlags) (1 << 30), - NULL, NULL, NULL); - g_assert_null (conn); - } - else - { - g_test_trap_subprocess (NULL, 0, 0); - g_test_trap_assert_failed (); - g_test_trap_assert_stderr ("*CRITICAL*G_DBUS_CONNECTION_FLAGS_ALL*"); - } -} - -static void -test_peer_invalid_conn_addr_async (void) -{ - if (!g_test_undefined ()) - { - g_test_skip ("Not exercising programming errors"); - return; - } - - if (g_test_subprocess ()) - { - g_dbus_connection_new_for_address ("tcp:", - (GDBusConnectionFlags) (1 << 30), - NULL, NULL, NULL, NULL); - } - else - { - g_test_trap_subprocess (NULL, 0, 0); - g_test_trap_assert_failed (); - g_test_trap_assert_stderr ("*CRITICAL*G_DBUS_CONNECTION_FLAGS_ALL*"); - } -} - -/* ---------------------------------------------------------------------------------------------------- */ - static void test_peer_signals (void) { @@ -1967,7 +1719,7 @@ codegen_on_animal_poke (ExampleAnimal *animal, g_assert_not_reached (); out: - return TRUE; /* to indicate that the method was handled */ + return G_DBUS_METHOD_INVOCATION_HANDLED; } /* Runs in thread we created GDBusServer in (since we didn't pass G_DBUS_SERVER_FLAGS_RUN_IN_THREAD) */ @@ -2042,7 +1794,7 @@ static gboolean codegen_quit_mainloop_timeout (gpointer data) { g_main_loop_quit (loop); - return FALSE; + return G_SOURCE_REMOVE; } static void @@ -2187,16 +1939,6 @@ main (int argc, test_interface_introspection_data = introspection_data->interfaces[0]; g_test_add_func ("/gdbus/peer-to-peer", test_peer); - g_test_add_func ("/gdbus/peer-to-peer/invalid/server", - test_peer_invalid_server); - g_test_add_func ("/gdbus/peer-to-peer/invalid/conn/stream/async", - test_peer_invalid_conn_stream_async); - g_test_add_func ("/gdbus/peer-to-peer/invalid/conn/stream/sync", - test_peer_invalid_conn_stream_sync); - g_test_add_func ("/gdbus/peer-to-peer/invalid/conn/addr/async", - test_peer_invalid_conn_addr_async); - g_test_add_func ("/gdbus/peer-to-peer/invalid/conn/addr/sync", - test_peer_invalid_conn_addr_sync); g_test_add_func ("/gdbus/peer-to-peer/signals", test_peer_signals); g_test_add_func ("/gdbus/delayed-message-processing", delayed_message_processing); g_test_add_func ("/gdbus/nonce-tcp", test_nonce_tcp); diff --git a/gio/tests/gdbus-test-codegen.c b/gio/tests/gdbus-test-codegen.c index bb1d059ca..9a3ffa927 100644 --- a/gio/tests/gdbus-test-codegen.c +++ b/gio/tests/gdbus-test-codegen.c @@ -33,6 +33,11 @@ #include "gdbus-test-codegen-generated-interface-info.h" +#if GLIB_VERSION_MIN_REQUIRED < GLIB_VERSION_2_68 +# undef G_DBUS_METHOD_INVOCATION_HANDLED +# define G_DBUS_METHOD_INVOCATION_HANDLED TRUE +#endif + /* ---------------------------------------------------------------------------------------------------- */ static guint @@ -100,7 +105,7 @@ on_handle_hello_world (FooiGenBar *object, response = g_strdup_printf ("Word! You said '%s'. I'm Skeleton, btw!", greeting); foo_igen_bar_complete_hello_world (object, invocation, response); g_free (response); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -145,7 +150,7 @@ on_handle_test_primitive_types (FooiGenBar *object, g_free (s1); g_free (s2); g_free (s3); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -185,7 +190,7 @@ on_handle_test_non_primitive_types (FooiGenBar *object, array_of_bytestrings, str->str); g_string_free (str, TRUE); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -202,7 +207,7 @@ on_handle_request_signal_emission (FooiGenBar *object, foo_igen_bar_emit_test_signal (object, 43, a_strv, a_bytestring_array, a_variant); /* consumes a_variant */ foo_igen_bar_complete_request_signal_emission (object, invocation); } - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -218,7 +223,7 @@ on_handle_request_multi_property_mods (FooiGenBar *object, foo_igen_bar_set_y (object, foo_igen_bar_get_y (object) + 1); foo_igen_bar_set_i (object, foo_igen_bar_get_i (object) + 1); foo_igen_bar_complete_request_multi_property_mods (object, invocation); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -238,7 +243,7 @@ on_handle_property_cancellation (FooiGenBar *object, g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (object)); /* this makes us return the reply D-Bus method */ foo_igen_bar_complete_property_cancellation (object, invocation); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } /* ---------------------------------------------------------------------------------------------------- */ @@ -293,7 +298,7 @@ on_handle_force_method (FooiGenBat *object, g_variant_unref (ret_ay); g_variant_unref (ret_struct); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -382,7 +387,7 @@ on_handle_check_not_authorized (FooiGenAuthorize *object, gpointer user_data) { foo_igen_authorize_complete_check_not_authorized (object, invocation); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -391,7 +396,7 @@ on_handle_check_authorized (FooiGenAuthorize *object, gpointer user_data) { foo_igen_authorize_complete_check_authorized (object, invocation); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -400,7 +405,7 @@ on_handle_check_not_authorized_from_object (FooiGenAuthorize *object, gpointer user_data) { foo_igen_authorize_complete_check_not_authorized_from_object (object, invocation); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } /* ---------------------------------------------------------------------------------------------------- */ @@ -414,7 +419,7 @@ on_handle_get_self (FooiGenMethodThreads *object, s = g_strdup_printf ("%p", (void *)g_thread_self ()); foo_igen_method_threads_complete_get_self (object, invocation, s); g_free (s); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } /* ---------------------------------------------------------------------------------------------------- */ @@ -2645,7 +2650,7 @@ handle_hello_fd (FooiGenFDPassing *object, const gchar *arg_greeting) { foo_igen_fdpassing_complete_hello_fd (object, invocation, fd_list, arg_greeting); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } #if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_64 @@ -2657,7 +2662,7 @@ handle_no_annotation (FooiGenFDPassing *object, const gchar *arg_greeting_locale) { foo_igen_fdpassing_complete_no_annotation (object, invocation, fd_list, arg_greeting, arg_greeting_locale); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -2667,7 +2672,7 @@ handle_no_annotation_nested (FooiGenFDPassing *object, GVariant *arg_files) { foo_igen_fdpassing_complete_no_annotation_nested (object, invocation, fd_list); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } #else static gboolean @@ -2677,7 +2682,7 @@ handle_no_annotation (FooiGenFDPassing *object, const gchar *arg_greeting_locale) { foo_igen_fdpassing_complete_no_annotation (object, invocation, arg_greeting, arg_greeting_locale); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean @@ -2686,7 +2691,7 @@ handle_no_annotation_nested (FooiGenFDPassing *object, GVariant *arg_files) { foo_igen_fdpassing_complete_no_annotation_nested (object, invocation); - return TRUE; + return G_DBUS_METHOD_INVOCATION_HANDLED; } #endif diff --git a/gio/tests/gdbus-tests.c b/gio/tests/gdbus-tests.c index bed0d24f7..1003d07e0 100644 --- a/gio/tests/gdbus-tests.c +++ b/gio/tests/gdbus-tests.c @@ -48,7 +48,7 @@ on_property_notify_timeout (gpointer user_data) PropertyNotifyData *data = user_data; data->timed_out = TRUE; g_main_loop_quit (data->loop); - return TRUE; + return G_SOURCE_CONTINUE; } gboolean @@ -83,7 +83,7 @@ static gboolean _give_up (gpointer data) { g_error ("%s", (const gchar *) data); - g_return_val_if_reached (TRUE); + g_return_val_if_reached (G_SOURCE_CONTINUE); } typedef struct @@ -175,7 +175,7 @@ on_signal_received_timeout (gpointer user_data) SignalReceivedData *data = user_data; data->timed_out = TRUE; g_main_loop_quit (data->loop); - return TRUE; + return G_SOURCE_CONTINUE; } gboolean diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index 6e5a6d367..baadca8f5 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -1740,14 +1740,6 @@ key_changed_cb (GSettings *settings, const gchar *key, gpointer data) (*b) = TRUE; } -typedef struct -{ - const gchar *path; - const gchar *root_group; - const gchar *keyfile_group; - const gchar *root_path; -} KeyfileTestData; - /* * Test that using a keyfile works */ @@ -1842,11 +1834,7 @@ test_keyfile (Fixture *fixture, g_free (str); g_settings_set (settings, "farewell", "s", "cheerio"); - - /* Check that empty keys/groups are not allowed. */ - g_assert_false (g_settings_is_writable (settings, "")); - g_assert_false (g_settings_is_writable (settings, "/")); - + /* When executing as root, changing the mode of the keyfile will have * no effect on the writability of the settings. */ @@ -1878,149 +1866,6 @@ test_keyfile (Fixture *fixture, g_free (keyfile_path); } -/* - * Test that using a keyfile works with a schema with no path set. - */ -static void -test_keyfile_no_path (Fixture *fixture, - gconstpointer user_data) -{ - const KeyfileTestData *test_data = user_data; - GSettingsBackend *kf_backend; - GSettings *settings; - GKeyFile *keyfile; - gboolean writable; - gchar *key = NULL; - GError *error = NULL; - gchar *keyfile_path = NULL, *store_path = NULL; - - keyfile_path = g_build_filename (fixture->tmp_dir, "keyfile", NULL); - store_path = g_build_filename (keyfile_path, "gsettings.store", NULL); - kf_backend = g_keyfile_settings_backend_new (store_path, test_data->root_path, test_data->root_group); - settings = g_settings_new_with_backend_and_path ("org.gtk.test.no-path", kf_backend, test_data->path); - g_object_unref (kf_backend); - - g_settings_reset (settings, "test-boolean"); - g_assert_true (g_settings_get_boolean (settings, "test-boolean")); - - writable = g_settings_is_writable (settings, "test-boolean"); - g_assert_true (writable); - g_settings_set (settings, "test-boolean", "b", FALSE); - - g_assert_false (g_settings_get_boolean (settings, "test-boolean")); - - g_settings_delay (settings); - g_settings_set (settings, "test-boolean", "b", TRUE); - g_settings_apply (settings); - - keyfile = g_key_file_new (); - g_assert_true (g_key_file_load_from_file (keyfile, store_path, 0, NULL)); - - g_assert_true (g_key_file_get_boolean (keyfile, test_data->keyfile_group, "test-boolean", NULL)); - - g_key_file_free (keyfile); - - g_settings_reset (settings, "test-boolean"); - g_settings_apply (settings); - keyfile = g_key_file_new (); - g_assert_true (g_key_file_load_from_file (keyfile, store_path, 0, NULL)); - - g_assert_false (g_key_file_get_string (keyfile, test_data->keyfile_group, "test-boolean", &error)); - g_assert_error (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND); - g_clear_error (&error); - - /* Check that empty keys/groups are not allowed. */ - g_assert_false (g_settings_is_writable (settings, "")); - g_assert_false (g_settings_is_writable (settings, "/")); - - /* Keys which ghost the root group name are not allowed. This can only be - * tested when the path is `/` as otherwise it acts as a prefix and prevents - * any ghosting. */ - if (g_str_equal (test_data->path, "/")) - { - key = g_strdup_printf ("%s/%s", test_data->root_group, ""); - g_assert_false (g_settings_is_writable (settings, key)); - g_free (key); - - key = g_strdup_printf ("%s/%s", test_data->root_group, "/"); - g_assert_false (g_settings_is_writable (settings, key)); - g_free (key); - - key = g_strdup_printf ("%s/%s", test_data->root_group, "test-boolean"); - g_assert_false (g_settings_is_writable (settings, key)); - g_free (key); - } - - g_key_file_free (keyfile); - g_object_unref (settings); - - /* Clean up the temporary directory. */ - g_assert_no_errno (g_chmod (keyfile_path, 0777)); - g_assert_no_errno (g_remove (store_path)); - g_assert_no_errno (g_rmdir (keyfile_path)); - g_free (store_path); - g_free (keyfile_path); -} - -/* - * Test that a keyfile rejects writes to keys outside its root path. - */ -static void -test_keyfile_outside_root_path (Fixture *fixture, - gconstpointer user_data) -{ - GSettingsBackend *kf_backend; - GSettings *settings; - gchar *keyfile_path = NULL, *store_path = NULL; - - keyfile_path = g_build_filename (fixture->tmp_dir, "keyfile", NULL); - store_path = g_build_filename (keyfile_path, "gsettings.store", NULL); - kf_backend = g_keyfile_settings_backend_new (store_path, "/tests/basic-types/", "root"); - settings = g_settings_new_with_backend_and_path ("org.gtk.test.no-path", kf_backend, "/tests/"); - g_object_unref (kf_backend); - - g_assert_false (g_settings_is_writable (settings, "test-boolean")); - - g_object_unref (settings); - - /* Clean up the temporary directory. The keyfile probably doesn’t exist, so - * don’t error on failure. */ - g_remove (store_path); - g_assert_no_errno (g_rmdir (keyfile_path)); - g_free (store_path); - g_free (keyfile_path); -} - -/* - * Test that a keyfile rejects writes to keys in the root if no root group is set. - */ -static void -test_keyfile_no_root_group (Fixture *fixture, - gconstpointer user_data) -{ - GSettingsBackend *kf_backend; - GSettings *settings; - gchar *keyfile_path = NULL, *store_path = NULL; - - keyfile_path = g_build_filename (fixture->tmp_dir, "keyfile", NULL); - store_path = g_build_filename (keyfile_path, "gsettings.store", NULL); - kf_backend = g_keyfile_settings_backend_new (store_path, "/", NULL); - settings = g_settings_new_with_backend_and_path ("org.gtk.test.no-path", kf_backend, "/"); - g_object_unref (kf_backend); - - g_assert_false (g_settings_is_writable (settings, "test-boolean")); - g_assert_true (g_settings_is_writable (settings, "child/test-boolean")); - - g_object_unref (settings); - - /* Clean up the temporary directory. The keyfile probably doesn’t exist, so - * don’t error on failure. */ - g_remove (store_path); - g_assert_no_errno (g_rmdir (keyfile_path)); - g_free (store_path); - g_free (keyfile_path); -} - /* Test that getting child schemas works */ static void @@ -2999,14 +2844,6 @@ main (int argc, char *argv[]) gchar *override_text; gchar *enums; gint result; - const KeyfileTestData keyfile_test_data_explicit_path = { "/tests/", "root", "tests", "/" }; - const KeyfileTestData keyfile_test_data_empty_path = { "/", "root", "root", "/" }; - const KeyfileTestData keyfile_test_data_long_path = { - "/tests/path/is/very/long/and/this/makes/some/comparisons/take/a/different/branch/", - "root", - "tests/path/is/very/long/and/this/makes/some/comparisons/take/a/different/branch", - "/" - }; /* Meson build sets this */ #ifdef TEST_LOCALE_PATH @@ -3130,11 +2967,6 @@ main (int argc, char *argv[]) } g_test_add ("/gsettings/keyfile", Fixture, NULL, setup, test_keyfile, teardown); - g_test_add ("/gsettings/keyfile/explicit-path", Fixture, &keyfile_test_data_explicit_path, setup, test_keyfile_no_path, teardown); - g_test_add ("/gsettings/keyfile/empty-path", Fixture, &keyfile_test_data_empty_path, setup, test_keyfile_no_path, teardown); - g_test_add ("/gsettings/keyfile/long-path", Fixture, &keyfile_test_data_long_path, setup, test_keyfile_no_path, teardown); - g_test_add ("/gsettings/keyfile/outside-root-path", Fixture, NULL, setup, test_keyfile_outside_root_path, teardown); - g_test_add ("/gsettings/keyfile/no-root-group", Fixture, NULL, setup, test_keyfile_no_root_group, teardown); g_test_add_func ("/gsettings/child-schema", test_child_schema); g_test_add_func ("/gsettings/strinfo", test_strinfo); g_test_add_func ("/gsettings/enums", test_enums); diff --git a/gio/tests/gsocketclient-slow.c b/gio/tests/gsocketclient-slow.c index 803ed9082..34410f4cf 100644 --- a/gio/tests/gsocketclient-slow.c +++ b/gio/tests/gsocketclient-slow.c @@ -79,26 +79,23 @@ on_connected_cancelled (GObject *source_object, g_main_loop_quit (user_data); } -typedef struct +static int +on_timer (GCancellable *cancel) { - GCancellable *cancellable; - gboolean completed; -} EventCallbackData; + g_cancellable_cancel (cancel); + return G_SOURCE_REMOVE; +} static void on_event (GSocketClient *client, GSocketClientEvent event, GSocketConnectable *connectable, GIOStream *connection, - EventCallbackData *data) + gboolean *got_completed_event) { - if (data->cancellable && event == G_SOCKET_CLIENT_CONNECTED) - { - g_cancellable_cancel (data->cancellable); - } - else if (event == G_SOCKET_CLIENT_COMPLETE) + if (event == G_SOCKET_CLIENT_COMPLETE) { - data->completed = TRUE; + *got_completed_event = TRUE; g_assert_null (connection); } } @@ -111,7 +108,8 @@ test_happy_eyeballs_cancel_delayed (void) GError *error = NULL; guint16 port; GMainLoop *loop; - EventCallbackData data = { NULL, FALSE }; + GCancellable *cancel; + gboolean got_completed_event = FALSE; /* This just tests that cancellation works as expected, still emits the completed signal, * and never returns a connection */ @@ -124,16 +122,17 @@ test_happy_eyeballs_cancel_delayed (void) g_socket_service_start (service); client = g_socket_client_new (); - data.cancellable = g_cancellable_new (); - g_socket_client_connect_to_host_async (client, "localhost", port, data.cancellable, on_connected_cancelled, loop); - g_signal_connect (client, "event", G_CALLBACK (on_event), &data); + cancel = g_cancellable_new (); + g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop); + g_timeout_add (1, (GSourceFunc) on_timer, cancel); + g_signal_connect (client, "event", G_CALLBACK (on_event), &got_completed_event); g_main_loop_run (loop); - g_assert_true (data.completed); + g_assert_true (got_completed_event); g_main_loop_unref (loop); g_object_unref (service); g_object_unref (client); - g_object_unref (data.cancellable); + g_object_unref (cancel); } static void @@ -145,7 +144,7 @@ test_happy_eyeballs_cancel_instant (void) guint16 port; GMainLoop *loop; GCancellable *cancel; - EventCallbackData data = { NULL, FALSE }; + gboolean got_completed_event = FALSE; /* This tests the same things as above, test_happy_eyeballs_cancel_delayed(), but * with different timing since it sends an already cancelled cancellable */ @@ -161,10 +160,10 @@ test_happy_eyeballs_cancel_instant (void) cancel = g_cancellable_new (); g_cancellable_cancel (cancel); g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop); - g_signal_connect (client, "event", G_CALLBACK (on_event), &data); + g_signal_connect (client, "event", G_CALLBACK (on_event), &got_completed_event); g_main_loop_run (loop); - g_assert_true (data.completed); + g_assert_true (got_completed_event); g_main_loop_unref (loop); g_object_unref (service); g_object_unref (client); diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c index bccf6a308..3c248e610 100644 --- a/gio/tests/gsubprocess.c +++ b/gio/tests/gsubprocess.c @@ -6,6 +6,8 @@ #include <glib-unix.h> #include <gio/gunixinputstream.h> #include <gio/gfiledescriptorbased.h> +#include <unistd.h> +#include <fcntl.h> #endif /* We write 2^1 + 2^2 ... + 2^10 or 2047 copies of "Hello World!\n" @@ -1483,6 +1485,35 @@ test_cwd (void) g_object_unref (launcher); } #ifdef G_OS_UNIX + +static void +test_subprocess_launcher_close (void) +{ + GError *local_error = NULL; + GError **error = &local_error; + GSubprocessLauncher *launcher; + GSubprocess *proc; + GPtrArray *args; + int fd; + gboolean is_open; + + fd = dup(0); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + g_subprocess_launcher_take_fd (launcher, fd, fd); + is_open = fcntl (fd, F_GETFD) != -1; + g_assert_true (is_open); + g_subprocess_launcher_close (launcher); + is_open = fcntl (fd, F_GETFD) != -1; + g_assert_false (is_open); + args = get_test_subprocess_args ("cat", NULL); + proc = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error); + g_ptr_array_free (args, TRUE); + g_assert_null (proc); + g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_CLOSED); + g_clear_error (error); + g_object_unref (launcher); +} + static void test_stdout_file (void) { @@ -1835,6 +1866,7 @@ main (int argc, char **argv) g_test_add_func ("/gsubprocess/env/inherit", test_env_inherit); g_test_add_func ("/gsubprocess/cwd", test_cwd); #ifdef G_OS_UNIX + g_test_add_func ("/gsubprocess/launcher-close", test_subprocess_launcher_close); g_test_add_func ("/gsubprocess/stdout-file", test_stdout_file); g_test_add_func ("/gsubprocess/stdout-fd", test_stdout_fd); g_test_add_func ("/gsubprocess/child-setup", test_child_setup); diff --git a/gio/tests/gtesttlsbackend.c b/gio/tests/gtesttlsbackend.c index 346a20dd9..56d155031 100644 --- a/gio/tests/gtesttlsbackend.c +++ b/gio/tests/gtesttlsbackend.c @@ -93,6 +93,8 @@ struct _GTestTlsCertificate { gchar *key_pem; gchar *cert_pem; GTlsCertificate *issuer; + gchar *pkcs11_uri; + gchar *private_key_pkcs11_uri; }; struct _GTestTlsCertificateClass { @@ -105,7 +107,9 @@ enum PROP_CERT_CERTIFICATE_PEM, PROP_CERT_PRIVATE_KEY, PROP_CERT_PRIVATE_KEY_PEM, - PROP_CERT_ISSUER + PROP_CERT_ISSUER, + PROP_CERT_PKCS11_URI, + PROP_CERT_PRIVATE_KEY_PKCS11_URI, }; static void g_test_tls_certificate_initable_iface_init (GInitableIface *iface); @@ -143,6 +147,15 @@ g_test_tls_certificate_get_property (GObject *object, case PROP_CERT_ISSUER: g_value_set_object (value, cert->issuer); break; + case PROP_CERT_PKCS11_URI: + /* This test value simulates a backend that ignores the value + because it is unsupported */ + if (g_strcmp0 (cert->pkcs11_uri, "unsupported") != 0) + g_value_set_string (value, cert->pkcs11_uri); + break; + case PROP_CERT_PRIVATE_KEY_PKCS11_URI: + g_value_set_string (value, cert->private_key_pkcs11_uri); + break; default: g_assert_not_reached (); break; @@ -168,6 +181,12 @@ g_test_tls_certificate_set_property (GObject *object, case PROP_CERT_ISSUER: cert->issuer = g_value_dup_object (value); break; + case PROP_CERT_PKCS11_URI: + cert->pkcs11_uri = g_value_dup_string (value); + break; + case PROP_CERT_PRIVATE_KEY_PKCS11_URI: + cert->private_key_pkcs11_uri = g_value_dup_string (value); + break; case PROP_CERT_CERTIFICATE: case PROP_CERT_PRIVATE_KEY: /* ignore */ @@ -185,6 +204,8 @@ g_test_tls_certificate_finalize (GObject *object) g_free (cert->cert_pem); g_free (cert->key_pem); + g_free (cert->pkcs11_uri); + g_free (cert->private_key_pkcs11_uri); g_clear_object (&cert->issuer); G_OBJECT_CLASS (g_test_tls_certificate_parent_class)->finalize (object); @@ -207,6 +228,8 @@ g_test_tls_certificate_class_init (GTestTlsCertificateClass *test_class) g_object_class_override_property (gobject_class, PROP_CERT_PRIVATE_KEY, "private-key"); g_object_class_override_property (gobject_class, PROP_CERT_PRIVATE_KEY_PEM, "private-key-pem"); g_object_class_override_property (gobject_class, PROP_CERT_ISSUER, "issuer"); + g_object_class_override_property (gobject_class, PROP_CERT_PKCS11_URI, "pkcs11-uri"); + g_object_class_override_property (gobject_class, PROP_CERT_PRIVATE_KEY_PKCS11_URI, "private-key-pkcs11-uri"); } static void diff --git a/gio/tests/tls-certificate.c b/gio/tests/tls-certificate.c index 89e3d1421..c0fc80c4b 100644 --- a/gio/tests/tls-certificate.c +++ b/gio/tests/tls-certificate.c @@ -398,6 +398,38 @@ list_from_file (const Reference *ref) g_assert_cmpint (g_list_length (list), ==, 0); } +static void +from_pkcs11_uri (void) +{ + GError *error = NULL; + GTlsCertificate *cert; + gchar *pkcs11_uri = NULL; + + cert = g_tls_certificate_new_from_pkcs11_uris ("pkcs11:model=p11-kit-trust;manufacturer=PKCS%2311%20Kit;serial=1;token=ca-bundle.crt", NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (cert); + + g_object_get (cert, "pkcs11-uri", &pkcs11_uri, NULL); + g_assert_cmpstr ("pkcs11:model=p11-kit-trust;manufacturer=PKCS%2311%20Kit;serial=1;token=ca-bundle.crt", ==, pkcs11_uri); + g_free (pkcs11_uri); + + g_object_unref (cert); +} + +static void +from_unsupported_pkcs11_uri (void) +{ + GError *error = NULL; + GTlsCertificate *cert; + + /* This is a magic value in gtesttlsbackend.c simulating an unsupported backend */ + cert = g_tls_certificate_new_from_pkcs11_uris ("unsupported", NULL, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_assert_null (cert); + + g_clear_error (&error); +} + int main (int argc, char *argv[]) @@ -464,6 +496,10 @@ main (int argc, &ref, (GTestDataFunc)from_files_pkcs8enc); g_test_add_data_func ("/tls-certificate/list_from_file", &ref, (GTestDataFunc)list_from_file); + g_test_add_func ("/tls-certificate/pkcs11-uri", + from_pkcs11_uri); + g_test_add_func ("/tls-certificate/pkcs11-uri-unsupported", + from_unsupported_pkcs11_uri); rtv = g_test_run(); diff --git a/gio/tests/tls-interaction.c b/gio/tests/tls-interaction.c index 5661e8e0d..4f0737d7e 100644 --- a/gio/tests/tls-interaction.c +++ b/gio/tests/tls-interaction.c @@ -174,38 +174,6 @@ test_interaction_ask_password_finish_failure (GTlsInteraction *interaction, } -/* Return a copy of @str that is allocated in a silly way, to exercise - * custom free-functions. The returned pointer points to a copy of @str - * in a buffer of the form "BEFORE \0 str \0 AFTER". */ -static guchar * -special_dup (const char *str) -{ - GString *buf = g_string_new ("BEFORE"); - guchar *ret; - - g_string_append_c (buf, '\0'); - g_string_append (buf, str); - g_string_append_c (buf, '\0'); - g_string_append (buf, "AFTER"); - ret = (guchar *) g_string_free (buf, FALSE); - return ret + strlen ("BEFORE") + 1; -} - - -/* Free a copy of @str that was made with special_dup(), after asserting - * that it has not been corrupted. */ -static void -special_free (gpointer p) -{ - gchar *s = p; - gchar *buf = s - strlen ("BEFORE") - 1; - - g_assert_cmpstr (buf, ==, "BEFORE"); - g_assert_cmpstr (s + strlen (s) + 1, ==, "AFTER"); - g_free (buf); -} - - static GTlsInteractionResult test_interaction_ask_password_sync_success (GTlsInteraction *interaction, GTlsPassword *password, @@ -213,8 +181,6 @@ test_interaction_ask_password_sync_success (GTlsInteraction *interaction, GError **error) { TestInteraction *self; - const guchar *value; - gsize len; g_assert (TEST_IS_INTERACTION (interaction)); self = TEST_INTERACTION (interaction); @@ -226,27 +192,6 @@ test_interaction_ask_password_sync_success (GTlsInteraction *interaction, g_assert (error != NULL); g_assert (*error == NULL); - /* Exercise different ways to set the value */ - g_tls_password_set_value (password, (const guchar *) "foo", 4); - len = 0; - value = g_tls_password_get_value (password, &len); - g_assert_cmpmem (value, len, "foo", 4); - - g_tls_password_set_value (password, (const guchar *) "bar", -1); - len = 0; - value = g_tls_password_get_value (password, &len); - g_assert_cmpmem (value, len, "bar", 3); - - g_tls_password_set_value_full (password, special_dup ("baa"), 4, special_free); - len = 0; - value = g_tls_password_get_value (password, &len); - g_assert_cmpmem (value, len, "baa", 4); - - g_tls_password_set_value_full (password, special_dup ("baz"), -1, special_free); - len = 0; - value = g_tls_password_get_value (password, &len); - g_assert_cmpmem (value, len, "baz", 3); - /* Don't do this in real life. Include a null terminator for testing */ g_tls_password_set_value (password, (const guchar *)"the password", 13); return G_TLS_INTERACTION_HANDLED; diff --git a/gio/win32/gwin32fsmonitorutils.c b/gio/win32/gwin32fsmonitorutils.c index b47124634..e6b7d1dcd 100644 --- a/gio/win32/gwin32fsmonitorutils.c +++ b/gio/win32/gwin32fsmonitorutils.c @@ -345,7 +345,7 @@ g_win32_fs_monitor_init (GWin32FSMonitorPrivate *monitor, monitor->file_attribs = INVALID_FILE_ATTRIBUTES; monitor->pfni_prev = NULL; monitor->hDirectory = CreateFileW (wdirname_with_long_prefix != NULL ? wdirname_with_long_prefix : monitor->wfullpath_with_long_prefix, - FILE_LIST_DIRECTORY, + FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, diff --git a/gio/win32/gwinhttpfile.c b/gio/win32/gwinhttpfile.c index e0340e247..509cdeb33 100644 --- a/gio/win32/gwinhttpfile.c +++ b/gio/win32/gwinhttpfile.c @@ -30,7 +30,6 @@ #include "gio/gfileattribute.h" #include "gio/gfileinfo.h" #include "gio/gfileinfo-priv.h" -#include "gstrfuncsprivate.h" #include "gwinhttpfile.h" #include "gwinhttpfileinputstream.h" #include "gwinhttpfileoutputstream.h" @@ -410,10 +409,10 @@ g_winhttp_file_resolve_relative_path (GFile *file, child = g_object_new (G_TYPE_WINHTTP_FILE, NULL); child->vfs = winhttp_file->vfs; child->url = winhttp_file->url; - child->url.lpszScheme = g_memdup2 (winhttp_file->url.lpszScheme, ((gsize) winhttp_file->url.dwSchemeLength + 1) * 2); - child->url.lpszHostName = g_memdup2 (winhttp_file->url.lpszHostName, ((gsize) winhttp_file->url.dwHostNameLength + 1) * 2); - child->url.lpszUserName = g_memdup2 (winhttp_file->url.lpszUserName, ((gsize) winhttp_file->url.dwUserNameLength + 1) * 2); - child->url.lpszPassword = g_memdup2 (winhttp_file->url.lpszPassword, ((gsize) winhttp_file->url.dwPasswordLength + 1) * 2); + child->url.lpszScheme = g_memdup (winhttp_file->url.lpszScheme, (winhttp_file->url.dwSchemeLength+1)*2); + child->url.lpszHostName = g_memdup (winhttp_file->url.lpszHostName, (winhttp_file->url.dwHostNameLength+1)*2); + child->url.lpszUserName = g_memdup (winhttp_file->url.lpszUserName, (winhttp_file->url.dwUserNameLength+1)*2); + child->url.lpszPassword = g_memdup (winhttp_file->url.lpszPassword, (winhttp_file->url.dwPasswordLength+1)*2); child->url.lpszUrlPath = wnew_path; child->url.dwUrlPathLength = wcslen (wnew_path); child->url.lpszExtraInfo = NULL; |