diff options
author | DongHun Kwak <dh0128.kwak@samsung.com> | 2021-10-29 10:31:47 +0900 |
---|---|---|
committer | DongHun Kwak <dh0128.kwak@samsung.com> | 2021-10-29 10:31:47 +0900 |
commit | 171ab2d5613fb4a84f9c599300f4ddbdd5dc18f7 (patch) | |
tree | 5f829ef35765afca84a0cc556acaee9de428d5f8 /gio | |
parent | c56d78b101fcc361d6cd5467c59aed4ec78c44af (diff) | |
download | glib-171ab2d5613fb4a84f9c599300f4ddbdd5dc18f7.tar.gz glib-171ab2d5613fb4a84f9c599300f4ddbdd5dc18f7.tar.bz2 glib-171ab2d5613fb4a84f9c599300f4ddbdd5dc18f7.zip |
Imported Upstream version 2.67.2upstream/2.67.2
Diffstat (limited to 'gio')
52 files changed, 4256 insertions, 315 deletions
diff --git a/gio/completion/gio b/gio/completion/gio index 75473d255..c650475b9 100644 --- a/gio/completion/gio +++ b/gio/completion/gio @@ -22,7 +22,8 @@ # Check whether the suggestions have common prefix (i.e. suggestions won't be # shown and prefix will be completed first) -__has_common_prefix() { +__gio_has_common_prefix() { + local i for (( i = 1; i < ${#COMPREPLY[@]}; i++ )); do if [[ "${COMPREPLY[i-1]:${#cur}:1}" != "${COMPREPLY[i]:${#cur}:1}" ]]; then return 1 # False @@ -49,6 +50,7 @@ __gio_location() { # List volumes and mounts local mounts=( ) + local mount while IFS=$'\n' read mount; do # Do not care about local mounts [[ "$mount" =~ ^"file:" ]] && continue @@ -58,12 +60,13 @@ __gio_location() { done < <(gio mount -li | sed -n -r 's/^ *(default_location|activation_root)=(.*)$/\2/p' | sort -u) # Workaround to unescape dir name (e.g. "\ " -> " ") - declare -a tmp="( ${dir} )" - unescaped_dir="${tmp[0]}" + local -a tmp="( ${dir} )" + local unescaped_dir="${tmp[0]}" # List files local files=() local names=() + local name size type while IFS=$'\t' read name size type; do # Escape name properly local escaped_name="$(printf "%q" "$name")" @@ -75,7 +78,7 @@ __gio_location() { escaped_name="$escaped_name " fi - path="$dir$escaped_name" + local path="$dir$escaped_name" # Use only matching paths if [[ "$path" =~ ^"$cur" ]]; then @@ -87,7 +90,7 @@ __gio_location() { COMPREPLY=("${files[@]}" "${mounts[@]}") # Workaround to show suggestions as basenames only - if ! __has_common_prefix; then + if ! __gio_has_common_prefix; then COMPREPLY=("${mounts[@]} ${names[@]}") # Workaround to prevent overwriting suggestions, it adds empty @@ -105,7 +108,7 @@ __gio_location() { __gio() { # Complete subcommands if (( ${COMP_CWORD} == 1 )); then - COMPREPLY=($(compgen -W "help version cat copy info list mime mkdir monitor mount move open rename remove save set trash tree" -- "${COMP_WORDS[1]}")) + COMPREPLY=($(compgen -W "help version cat copy info launch list mime mkdir monitor mount move open rename remove save set trash tree" -- "${COMP_WORDS[1]}")) compopt +o nospace return 0 fi diff --git a/gio/gapplicationimpl-dbus.c b/gio/gapplicationimpl-dbus.c index 6f02788a2..263001b8e 100644 --- a/gio/gapplicationimpl-dbus.c +++ b/gio/gapplicationimpl-dbus.c @@ -371,6 +371,7 @@ g_application_impl_attempt_primary (GApplicationImpl *impl, GApplicationFlags app_flags; GVariant *reply; guint32 rval; + GError *local_error = NULL; if (org_gtk_Application == NULL) { @@ -430,8 +431,14 @@ g_application_impl_attempt_primary (GApplicationImpl *impl, if (!app_class->dbus_register (impl->app, impl->session_bus, impl->object_path, - error)) - return FALSE; + &local_error)) + { + g_return_val_if_fail (local_error != NULL, FALSE); + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + g_return_val_if_fail (local_error == NULL, FALSE); if (impl->bus_name == NULL) { diff --git a/gio/gbufferedinputstream.c b/gio/gbufferedinputstream.c index f5090d064..b0e609c0d 100644 --- a/gio/gbufferedinputstream.c +++ b/gio/gbufferedinputstream.c @@ -661,10 +661,10 @@ g_buffered_input_stream_real_fill (GBufferedInputStream *stream, in_buffer = priv->end - priv->pos; /* Never fill more than can fit in the buffer */ - count = MIN (count, priv->len - in_buffer); + count = MIN ((gsize) count, priv->len - in_buffer); /* If requested length does not fit at end, compact */ - if (priv->len - priv->end < count) + if (priv->len - priv->end < (gsize) count) compact_buffer (stream); base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream; @@ -1072,10 +1072,10 @@ g_buffered_input_stream_real_fill_async (GBufferedInputStream *stream, in_buffer = priv->end - priv->pos; /* Never fill more than can fit in the buffer */ - count = MIN (count, priv->len - in_buffer); + count = MIN ((gsize) count, priv->len - in_buffer); /* If requested length does not fit at end, compact */ - if (priv->len - priv->end < count) + if (priv->len - priv->end < (gsize) count) compact_buffer (stream); task = g_task_new (stream, cancellable, callback, user_data); diff --git a/gio/gbufferedoutputstream.c b/gio/gbufferedoutputstream.c index 98bda501d..969bbae0b 100644 --- a/gio/gbufferedoutputstream.c +++ b/gio/gbufferedoutputstream.c @@ -208,7 +208,7 @@ g_buffered_output_stream_set_buffer_size (GBufferedOutputStream *stream, if (priv->buffer) { - size = MAX (size, priv->pos); + size = (priv->pos > 0) ? MAX (size, (gsize) priv->pos) : size; buffer = g_malloc (size); memcpy (buffer, priv->buffer, priv->pos); diff --git a/gio/gcancellable.c b/gio/gcancellable.c index 00df6998d..a084282ec 100644 --- a/gio/gcancellable.c +++ b/gio/gcancellable.c @@ -770,6 +770,7 @@ static GSourceFuncs cancellable_source_funcs = cancellable_source_dispatch, NULL, (GSourceFunc)cancellable_source_closure_callback, + NULL, }; /** diff --git a/gio/gcontextspecificgroup.c b/gio/gcontextspecificgroup.c index ffd1307e4..acad72767 100644 --- a/gio/gcontextspecificgroup.c +++ b/gio/gcontextspecificgroup.c @@ -73,7 +73,8 @@ g_context_specific_source_new (const gchar *name, NULL, NULL, g_context_specific_source_dispatch, - g_context_specific_source_finalize + g_context_specific_source_finalize, + NULL, NULL }; GContextSpecificSource *css; GSource *source; diff --git a/gio/gcredentials.c b/gio/gcredentials.c index 9423f9ef9..c5d3d46ac 100644 --- a/gio/gcredentials.c +++ b/gio/gcredentials.c @@ -196,7 +196,7 @@ g_credentials_init (GCredentials *credentials) * Creates a new #GCredentials object with credentials matching the * the current process. * - * Returns: A #GCredentials. Free with g_object_unref(). + * Returns: (transfer full): A #GCredentials. Free with g_object_unref(). * * Since: 2.26 */ @@ -216,7 +216,7 @@ g_credentials_new (void) * that can be used in logging and debug messages. The format of the * returned string may change in future GLib release. * - * Returns: A string that should be freed with g_free(). + * Returns: (transfer full): A string that should be freed with g_free(). * * Since: 2.26 */ @@ -233,18 +233,18 @@ g_credentials_to_string (GCredentials *credentials) ret = g_string_new ("GCredentials:"); #if G_CREDENTIALS_USE_LINUX_UCRED g_string_append (ret, "linux-ucred:"); - if (credentials->native.pid != -1) + if (credentials->native.pid != (pid_t) -1) g_string_append_printf (ret, "pid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.pid); - if (credentials->native.uid != -1) + if (credentials->native.uid != (uid_t) -1) g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.uid); - if (credentials->native.gid != -1) + if (credentials->native.gid != (gid_t) -1) g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.gid); if (ret->str[ret->len - 1] == ',') ret->str[ret->len - 1] = '\0'; #elif G_CREDENTIALS_USE_APPLE_XUCRED g_string_append (ret, "apple-xucred:"); g_string_append_printf (ret, "version=%u,", credentials->native.cr_version); - if (credentials->native.cr_uid != -1) + if (credentials->native.cr_uid != (uid_t) -1) g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.cr_uid); for (i = 0; i < credentials->native.cr_ngroups; i++) g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.cr_groups[i]); @@ -252,28 +252,28 @@ g_credentials_to_string (GCredentials *credentials) ret->str[ret->len - 1] = '\0'; #elif G_CREDENTIALS_USE_FREEBSD_CMSGCRED g_string_append (ret, "freebsd-cmsgcred:"); - if (credentials->native.cmcred_pid != -1) + if (credentials->native.cmcred_pid != (pid_t) -1) g_string_append_printf (ret, "pid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.cmcred_pid); - if (credentials->native.cmcred_euid != -1) + if (credentials->native.cmcred_euid != (uid_t) -1) g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.cmcred_euid); - if (credentials->native.cmcred_gid != -1) + if (credentials->native.cmcred_gid != (gid_t) -1) g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.cmcred_gid); #elif G_CREDENTIALS_USE_NETBSD_UNPCBID g_string_append (ret, "netbsd-unpcbid:"); - if (credentials->native.unp_pid != -1) + if (credentials->native.unp_pid != (pid_t) -1) g_string_append_printf (ret, "pid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.unp_pid); - if (credentials->native.unp_euid != -1) + if (credentials->native.unp_euid != (uid_t) -1) g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.unp_euid); - if (credentials->native.unp_egid != -1) + if (credentials->native.unp_egid != (gid_t) -1) g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.unp_egid); ret->str[ret->len - 1] = '\0'; #elif G_CREDENTIALS_USE_OPENBSD_SOCKPEERCRED g_string_append (ret, "openbsd-sockpeercred:"); - if (credentials->native.pid != -1) + if (credentials->native.pid != (pid_t) -1) g_string_append_printf (ret, "pid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.pid); - if (credentials->native.uid != -1) + if (credentials->native.uid != (uid_t) -1) g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.uid); - if (credentials->native.gid != -1) + if (credentials->native.gid != (gid_t) -1) g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) credentials->native.gid); if (ret->str[ret->len - 1] == ',') ret->str[ret->len - 1] = '\0'; @@ -281,11 +281,11 @@ g_credentials_to_string (GCredentials *credentials) g_string_append (ret, "solaris-ucred:"); { id_t id; - if ((id = ucred_getpid (credentials->native)) != -1) + if ((id = ucred_getpid (credentials->native)) != (id_t) -1) g_string_append_printf (ret, "pid=%" G_GINT64_FORMAT ",", (gint64) id); - if ((id = ucred_geteuid (credentials->native)) != -1) + if ((id = ucred_geteuid (credentials->native)) != (id_t) -1) g_string_append_printf (ret, "uid=%" G_GINT64_FORMAT ",", (gint64) id); - if ((id = ucred_getegid (credentials->native)) != -1) + if ((id = ucred_getegid (credentials->native)) != (id_t) -1) g_string_append_printf (ret, "gid=%" G_GINT64_FORMAT ",", (gint64) id); if (ret->str[ret->len - 1] == ',') ret->str[ret->len - 1] = '\0'; @@ -435,10 +435,10 @@ credentials_native_type_check (GCredentialsType requested_type, * logged) to use this method if there is no #GCredentials support for * the OS or if @native_type isn't supported by the OS. * - * Returns: The pointer to native credentials or %NULL if the - * operation there is no #GCredentials support for the OS or if - * @native_type isn't supported by the OS. Do not free the returned - * data, it is owned by @credentials. + * Returns: (transfer none) (nullable): The pointer to native credentials or + * %NULL if there is no #GCredentials support for the OS or if @native_type + * isn't supported by the OS. Do not free the returned data, it is owned + * by @credentials. * * Since: 2.26 */ @@ -507,7 +507,7 @@ g_credentials_set_native (GCredentials *credentials, * OS or if the native credentials type does not contain information * about the UNIX user. * - * Returns: The UNIX user identifier or -1 if @error is set. + * Returns: The UNIX user identifier or `-1` if @error is set. * * Since: 2.26 */ @@ -572,7 +572,7 @@ g_credentials_get_unix_user (GCredentials *credentials, * about the UNIX process ID (for example this is the case for * %G_CREDENTIALS_TYPE_APPLE_XUCRED). * - * Returns: The UNIX process ID, or -1 if @error is set. + * Returns: The UNIX process ID, or `-1` if @error is set. * * Since: 2.36 */ diff --git a/gio/gdatainputstream.c b/gio/gdatainputstream.c index 2e7750cb5..4440eb41f 100644 --- a/gio/gdatainputstream.c +++ b/gio/gdatainputstream.c @@ -631,7 +631,7 @@ scan_for_newline (GDataInputStream *stream, GDataInputStreamPrivate *priv; const char *buffer; gsize start, end, peeked; - int i; + gsize i; gssize found_pos; int newline_len; gsize available, checked; @@ -861,7 +861,7 @@ scan_for_chars (GDataInputStream *stream, GBufferedInputStream *bstream; const char *buffer; gsize start, end, peeked; - int i; + gsize i; gsize available, checked; const char *stop_char; const char *stop_end; diff --git a/gio/gdbus-2.0/codegen/codegen.py b/gio/gdbus-2.0/codegen/codegen.py index 04bf44a71..9d28cb77d 100644 --- a/gio/gdbus-2.0/codegen/codegen.py +++ b/gio/gdbus-2.0/codegen/codegen.py @@ -392,7 +392,7 @@ class HeaderCodeGenerator: self.outfile.write("G_GNUC_DEPRECATED ") self.outfile.write( "void %s_set_%s (%s *object, %svalue);\n" - % (i.name_lower, p.name_lower, i.camel_name, p.arg.ctype_in,) + % (i.name_lower, p.name_lower, i.camel_name, p.arg.ctype_in) ) self.outfile.write("\n") @@ -1975,10 +1975,11 @@ class CodeGenerator: ) self.write_gtkdoc_deprecated_and_since_and_close(i, self.outfile, 0) self.outfile.write( - "guint\n" - "%s_override_properties (GObjectClass *klass, guint property_id_begin)\n" - "{\n" % (i.name_lower) + "guint\n" "%s_override_properties (GObjectClass *klass" % (i.name_lower) ) + if len(i.properties) == 0: + self.outfile.write(" G_GNUC_UNUSED") + self.outfile.write(", guint property_id_begin)\n" "{\n") for p in i.properties: self.outfile.write( ' g_object_class_override_property (klass, property_id_begin++, "%s");\n' @@ -2065,10 +2066,13 @@ class CodeGenerator: self.outfile.write( "static void\n" - "%s_default_init (%sIface *iface)\n" - "{\n" % (i.name_lower, i.camel_name) + "%s_default_init (%sIface *iface" % (i.name_lower, i.camel_name) ) - + if len(i.methods) == 0 and len(i.signals) == 0 and len(i.properties) == 0: + self.outfile.write(" G_GNUC_UNUSED)\n") + else: + self.outfile.write(")\n") + self.outfile.write("{\n") if len(i.methods) > 0: self.outfile.write( " /* GObject signals for incoming D-Bus method calls: */\n" @@ -2443,7 +2447,7 @@ class CodeGenerator: self.outfile.write( "void\n" "%s_set_%s (%s *object, %svalue)\n" - "{\n" % (i.name_lower, p.name_lower, i.camel_name, p.arg.ctype_in,) + "{\n" % (i.name_lower, p.name_lower, i.camel_name, p.arg.ctype_in) ) self.outfile.write( ' g_object_set (G_OBJECT (object), "%s", value, NULL);\n' @@ -2781,7 +2785,7 @@ class CodeGenerator: self.outfile.write( "void\n" "%s_complete_%s (\n" - " %s *object,\n" + " %s *object G_GNUC_UNUSED,\n" " GDBusMethodInvocation *invocation" % (i.name_lower, m.name_lower, i.camel_name) ) @@ -2906,12 +2910,19 @@ class CodeGenerator: # self.outfile.write( "static void\n" - "%s_proxy_get_property (GObject *object,\n" - " guint prop_id,\n" - " GValue *value,\n" - " GParamSpec *pspec G_GNUC_UNUSED)\n" - "{\n" % (i.name_lower) + "%s_proxy_get_property (GObject *object" % (i.name_lower) ) + if len(i.properties) == 0: + self.outfile.write( + " G_GNUC_UNUSED,\n" + " guint prop_id G_GNUC_UNUSED,\n" + " GValue *value G_GNUC_UNUSED,\n" + ) + else: + self.outfile.write( + ",\n" " guint prop_id,\n" " GValue *value,\n" + ) + self.outfile.write(" GParamSpec *pspec G_GNUC_UNUSED)\n" "{\n") if len(i.properties) > 0: self.outfile.write( " const _ExtendedGDBusPropertyInfo *info;\n" @@ -2961,14 +2972,20 @@ class CodeGenerator: " }\n" % (i.name) ) self.outfile.write("}\n" "\n") - self.outfile.write( - "static void\n" - "%s_proxy_set_property (GObject *object,\n" - " guint prop_id,\n" - " const GValue *value,\n" - " GParamSpec *pspec G_GNUC_UNUSED)\n" - "{\n" % (i.name_lower) - ) + self.outfile.write("static void\n" "%s_proxy_set_property (" % (i.name_lower)) + if len(i.properties) == 0: + self.outfile.write( + "GObject *object G_GNUC_UNUSED,\n" + " guint prop_id G_GNUC_UNUSED,\n" + " const GValue *value G_GNUC_UNUSED,\n" + ) + else: + self.outfile.write( + "GObject *object,\n" + " guint prop_id,\n" + " const GValue *value,\n" + ) + self.outfile.write(" GParamSpec *pspec G_GNUC_UNUSED)\n" "{\n") if len(i.properties) > 0: self.outfile.write( " const _ExtendedGDBusPropertyInfo *info;\n" @@ -3221,9 +3238,13 @@ class CodeGenerator: self.outfile.write( "static void\n" - "%s_proxy_iface_init (%sIface *iface)\n" - "{\n" % (i.name_lower, i.camel_name) + "%s_proxy_iface_init (%sIface *iface" % (i.name_lower, i.camel_name) ) + if len(i.properties) == 0: + self.outfile.write(" G_GNUC_UNUSED)\n") + else: + self.outfile.write(")\n") + self.outfile.write("{\n") for p in i.properties: self.outfile.write( " iface->get_%s = %s_proxy_get_%s;\n" @@ -3753,9 +3774,14 @@ class CodeGenerator: self.outfile.write( "static void\n" - "%s_skeleton_dbus_interface_flush (GDBusInterfaceSkeleton *_skeleton)\n" - "{\n" % (i.name_lower) + "%s_skeleton_dbus_interface_flush (GDBusInterfaceSkeleton *_skeleton" + % (i.name_lower) ) + if len(i.properties) == 0: + self.outfile.write(" G_GNUC_UNUSED)\n") + else: + self.outfile.write(")\n") + self.outfile.write("{\n") if len(i.properties) > 0: self.outfile.write( " %sSkeleton *skeleton = %s%s_SKELETON (_skeleton);\n" @@ -4193,9 +4219,13 @@ class CodeGenerator: self.outfile.write( "static void\n" - "%s_skeleton_iface_init (%sIface *iface)\n" - "{\n" % (i.name_lower, i.camel_name) + "%s_skeleton_iface_init (%sIface *iface" % (i.name_lower, i.camel_name) ) + if len(i.signals) == 0 and len(i.properties) == 0: + self.outfile.write(" G_GNUC_UNUSED)\n") + else: + self.outfile.write(")\n") + self.outfile.write("{\n") for s in i.signals: self.outfile.write( " iface->%s = _%s_on_signal_%s;\n" diff --git a/gio/gdbus-2.0/codegen/codegen_main.py b/gio/gdbus-2.0/codegen/codegen_main.py index dc0177f0f..238d7dd12 100644 --- a/gio/gdbus-2.0/codegen/codegen_main.py +++ b/gio/gdbus-2.0/codegen/codegen_main.py @@ -343,7 +343,7 @@ def codegen_main(): parts = args.glib_min_required.split(".", 3) glib_min_required = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0)) # Ignore micro component, but still validate it: - _ = int(parts[2] if len(parts) > 2 else 0) + _ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841 except (ValueError, IndexError): print_error( "Unrecognized --glib-min-required string ‘{}’".format( @@ -365,7 +365,7 @@ def codegen_main(): parts = args.glib_max_allowed.split(".", 3) glib_max_allowed = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0)) # Ignore micro component, but still validate it: - _ = int(parts[2] if len(parts) > 2 else 0) + _ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841 except (ValueError, IndexError): print_error( "Unrecognized --glib-max-allowed string ‘{}’".format( diff --git a/gio/gdbusaddress.c b/gio/gdbusaddress.c index 3dd3cc84b..d26c4d25f 100644 --- a/gio/gdbusaddress.c +++ b/gio/gdbusaddress.c @@ -30,6 +30,7 @@ #include "gdbusaddress.h" #include "gdbuserror.h" #include "gioenumtypes.h" +#include "glib-private.h" #include "gnetworkaddress.h" #include "gsocketclient.h" #include "giostream.h" @@ -903,11 +904,14 @@ g_dbus_address_get_stream (const gchar *address, /** * g_dbus_address_get_stream_finish: * @res: A #GAsyncResult obtained from the GAsyncReadyCallback passed to g_dbus_address_get_stream(). - * @out_guid: (optional) (out): %NULL or return location to store the GUID extracted from @address, if any. + * @out_guid: (optional) (out) (nullable): %NULL or return location to store the GUID extracted from @address, if any. * @error: Return location for error or %NULL. * * Finishes an operation started with g_dbus_address_get_stream(). * + * A server is not required to set a GUID, so @out_guid may be set to %NULL + * even on success. + * * Returns: (transfer full): A #GIOStream or %NULL if @error is set. * * Since: 2.26 @@ -940,7 +944,7 @@ g_dbus_address_get_stream_finish (GAsyncResult *res, /** * g_dbus_address_get_stream_sync: * @address: A valid D-Bus address. - * @out_guid: (optional) (out): %NULL or return location to store the GUID extracted from @address, if any. + * @out_guid: (optional) (out) (nullable): %NULL or return location to store the GUID extracted from @address, if any. * @cancellable: (nullable): A #GCancellable or %NULL. * @error: Return location for error or %NULL. * @@ -949,6 +953,9 @@ g_dbus_address_get_stream_finish (GAsyncResult *res, * of the D-Bus authentication conversation. @address must be in the * [D-Bus address format](https://dbus.freedesktop.org/doc/dbus-specification.html#addresses). * + * A server is not required to set a GUID, so @out_guid may be set to %NULL + * even on success. + * * This is a synchronous failable function. See * g_dbus_address_get_stream() for the asynchronous version. * @@ -1279,6 +1286,7 @@ g_dbus_address_get_for_bus_sync (GBusType bus_type, GCancellable *cancellable, GError **error) { + gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) (); gchar *ret, *s = NULL; const gchar *starter_bus; GError *local_error; @@ -1317,10 +1325,12 @@ 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: - ret = g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS")); + ret = !is_setuid ? g_strdup (g_getenv ("DBUS_SYSTEM_BUS_ADDRESS")) : NULL; if (ret == NULL) { ret = g_strdup ("unix:path=/var/run/dbus/system_bus_socket"); @@ -1328,7 +1338,7 @@ g_dbus_address_get_for_bus_sync (GBusType bus_type, break; case G_BUS_TYPE_SESSION: - ret = g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")); + ret = !is_setuid ? g_strdup (g_getenv ("DBUS_SESSION_BUS_ADDRESS")) : NULL; if (ret == NULL) { ret = get_session_address_platform_specific (&local_error); diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c index 65939a4d2..5c5ef1718 100644 --- a/gio/gdbusconnection.c +++ b/gio/gdbusconnection.c @@ -885,7 +885,7 @@ g_dbus_connection_class_init (GDBusConnectionClass *klass) * * If you are constructing a #GDBusConnection and pass * %G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER in the - * #GDBusConnection:flags property then you MUST also set this + * #GDBusConnection:flags property then you **must** also set this * property to a valid guid. * * If you are constructing a #GDBusConnection and pass @@ -1102,7 +1102,7 @@ g_dbus_connection_init (GDBusConnection *connection) * stream from a worker thread, so it is not safe to interact with * the stream directly. * - * Returns: (transfer none): the stream used for IO + * Returns: (transfer none) (not nullable): the stream used for IO * * Since: 2.26 */ @@ -2732,7 +2732,7 @@ g_dbus_connection_new (GIOStream *stream, * * Finishes an operation started with g_dbus_connection_new(). * - * Returns: a #GDBusConnection or %NULL if @error is set. Free + * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. Free * with g_object_unref(). * * Since: 2.26 @@ -2784,7 +2784,8 @@ g_dbus_connection_new_finish (GAsyncResult *res, * This is a synchronous failable constructor. See * g_dbus_connection_new() for the asynchronous version. * - * Returns: a #GDBusConnection or %NULL if @error is set. Free with g_object_unref(). + * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. + * Free with g_object_unref(). * * Since: 2.26 */ @@ -2875,8 +2876,8 @@ g_dbus_connection_new_for_address (const gchar *address, * * Finishes an operation started with g_dbus_connection_new_for_address(). * - * Returns: a #GDBusConnection or %NULL if @error is set. Free with - * g_object_unref(). + * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. + * Free with g_object_unref(). * * Since: 2.26 */ @@ -2927,8 +2928,8 @@ g_dbus_connection_new_for_address_finish (GAsyncResult *res, * If @observer is not %NULL it may be used to control the * authentication process. * - * Returns: a #GDBusConnection or %NULL if @error is set. Free with - * g_object_unref(). + * Returns: (transfer full): a #GDBusConnection or %NULL if @error is set. + * Free with g_object_unref(). * * Since: 2.26 */ @@ -3017,7 +3018,7 @@ g_dbus_connection_get_exit_on_close (GDBusConnection *connection) * The GUID of the peer performing the role of server when * authenticating. See #GDBusConnection:guid for more details. * - * Returns: The GUID. Do not free this string, it is owned by + * Returns: (not nullable): The GUID. Do not free this string, it is owned by * @connection. * * Since: 2.26 @@ -5510,7 +5511,7 @@ register_with_closures_on_set_property (GDBusConnection *connection, * Version of g_dbus_connection_register_object() using closures instead of a * #GDBusInterfaceVTable for easier binding in other languages. * - * Returns: 0 if @error is set, otherwise a registration id (never 0) + * Returns: 0 if @error is set, otherwise a registration ID (never 0) * that can be used with g_dbus_connection_unregister_object() . * * Since: 2.46 @@ -6139,8 +6140,8 @@ g_dbus_connection_call (GDBusConnection *connection, * * Finishes an operation started with g_dbus_connection_call(). * - * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with - * return values. Free with g_variant_unref(). + * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating + * #GVariant tuple with return values. Free with g_variant_unref(). * * Since: 2.26 */ @@ -6206,8 +6207,8 @@ g_dbus_connection_call_finish (GDBusConnection *connection, * g_dbus_connection_call() for the asynchronous version of * this method. * - * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with - * return values. Free with g_variant_unref(). + * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating + * #GVariant tuple with return values. Free with g_variant_unref(). * * Since: 2.26 */ @@ -6309,8 +6310,8 @@ g_dbus_connection_call_with_unix_fd_list (GDBusConnection *connection, * access file descriptors if they are referenced in this way by a * value of type %G_VARIANT_TYPE_HANDLE in the body of the message. * - * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with - * return values. Free with g_variant_unref(). + * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating + * #GVariant tuple with return values. Free with g_variant_unref(). * * Since: 2.30 */ @@ -6348,8 +6349,8 @@ g_dbus_connection_call_with_unix_fd_list_finish (GDBusConnection *connection, * * This method is only available on UNIX. * - * Returns: %NULL if @error is set. Otherwise a #GVariant tuple with - * return values. Free with g_variant_unref(). + * Returns: (transfer full): %NULL if @error is set. Otherwise a non-floating + * #GVariant tuple with return values. Free with g_variant_unref(). * * Since: 2.30 */ @@ -6859,8 +6860,8 @@ subtree_message_func (GDBusConnection *connection, * See this [server][gdbus-subtree-server] for an example of how to use * this method. * - * Returns: 0 if @error is set, otherwise a subtree registration id (never 0) - * that can be used with g_dbus_connection_unregister_subtree() . + * Returns: 0 if @error is set, otherwise a subtree registration ID (never 0) + * that can be used with g_dbus_connection_unregister_subtree() * * Since: 2.26 */ diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h index 05ef384e4..4bd3e9a4b 100644 --- a/gio/gdbusconnection.h +++ b/gio/gdbusconnection.h @@ -432,11 +432,11 @@ gboolean g_dbus_connection_unregister_object (GDBusConnection * specified (ie: to verify that the object path is valid). * * Hierarchies are not supported; the items that you return should not - * contain the '/' character. + * contain the `/` character. * * The return value will be freed with g_strfreev(). * - * Returns: A newly allocated array of strings for node names that are children of @object_path. + * Returns: (array zero-terminated=1) (transfer full): A newly allocated array of strings for node names that are children of @object_path. * * Since: 2.26 */ @@ -472,7 +472,7 @@ typedef gchar** (*GDBusSubtreeEnumerateFunc) (GDBusConnection *connection, * remote introspector in the empty array case, but not in the %NULL * case. * - * Returns: A %NULL-terminated array of pointers to #GDBusInterfaceInfo, or %NULL. + * Returns: (array zero-terminated=1) (nullable) (transfer full): A %NULL-terminated array of pointers to #GDBusInterfaceInfo, or %NULL. * * Since: 2.26 */ @@ -497,7 +497,7 @@ typedef GDBusInterfaceInfo ** (*GDBusSubtreeIntrospectFunc) (GDBusConnection * Subtrees are flat. @node, if non-%NULL, is always exactly one * segment of the object path (ie: it never contains a slash). * - * Returns: A #GDBusInterfaceVTable or %NULL if you don't want to handle the methods. + * Returns: (nullable): A #GDBusInterfaceVTable or %NULL if you don't want to handle the methods. * * Since: 2.26 */ diff --git a/gio/gdbuserror.c b/gio/gdbuserror.c index 4ad97bb6e..b3ea28f5a 100644 --- a/gio/gdbuserror.c +++ b/gio/gdbuserror.c @@ -512,8 +512,8 @@ g_dbus_error_is_remote_error (const GError *error) * (e.g. g_dbus_connection_call_finish()) unless * g_dbus_error_strip_remote_error() has been used on @error. * - * Returns: an allocated string or %NULL if the D-Bus error name - * could not be found. Free with g_free(). + * Returns: (nullable) (transfer full): an allocated string or %NULL if the + * D-Bus error name could not be found. Free with g_free(). * * Since: 2.26 */ @@ -600,7 +600,7 @@ g_dbus_error_get_remote_error (const GError *error) * #GError instances for applications. Regular applications should not use * it. * - * Returns: An allocated #GError. Free with g_error_free(). + * Returns: (transfer full): An allocated #GError. Free with g_error_free(). * * Since: 2.26 */ @@ -810,7 +810,8 @@ g_dbus_error_strip_remote_error (GError *error) * This function is typically only used in object mappings to put a * #GError on the wire. Regular applications should not use it. * - * Returns: A D-Bus error name (never %NULL). Free with g_free(). + * Returns: (transfer full) (not nullable): A D-Bus error name (never %NULL). + * Free with g_free(). * * Since: 2.26 */ diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 6b9185447..c10e1fc18 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -305,6 +305,27 @@ 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 const gchar * const * get_lowercase_current_desktops (void) { @@ -320,12 +341,22 @@ get_lowercase_current_desktops (void) if (envvar) { gint i, j; + gsize tmp_len; tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0); + tmp_len = g_strv_length (tmp); for (i = 0; tmp[i]; i++) - for (j = 0; tmp[i][j]; j++) - tmp[i][j] = g_ascii_tolower (tmp[i][j]); + { + /* If the desktop is invalid, drop it and shift the following + * ones (and trailing %NULL) up. */ + if (!validate_xdg_desktop (tmp[i])) + memmove (tmp + i, tmp + i + 1, tmp_len - i); + + /* Convert to lowercase. */ + for (j = 0; tmp[i][j]; j++) + tmp[i][j] = g_ascii_tolower (tmp[i][j]); + } } else tmp = g_new0 (gchar *, 0 + 1); @@ -344,6 +375,7 @@ get_current_desktops (const gchar *value) if (g_once_init_enter (&result)) { gchar **tmp; + gsize tmp_len, i; if (!value) value = g_getenv ("XDG_CURRENT_DESKTOP"); @@ -352,6 +384,15 @@ get_current_desktops (const gchar *value) value = ""; tmp = g_strsplit (value, ":", 0); + tmp_len = g_strv_length (tmp); + + for (i = 0; tmp[i]; i++) + { + /* If the desktop is invalid, drop it and shift the following + * ones (and trailing %NULL) up. */ + if (!validate_xdg_desktop (tmp[i])) + memmove (tmp + i, tmp + i + 1, tmp_len - i); + } g_once_init_leave (&result, tmp); } diff --git a/gio/gfile.c b/gio/gfile.c index f92c07612..b3acc25f1 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -456,11 +456,14 @@ g_file_has_uri_scheme (GFile *file, * ]| * Common schemes include "file", "http", "ftp", etc. * + * The scheme can be different from the one used to construct the #GFile, + * in that it might be replaced with one that is logically equivalent to the #GFile. + * * This call does no blocking I/O. * - * Returns: a string containing the URI scheme for the given - * #GFile. The returned string should be freed with g_free() - * when no longer needed. + * Returns: (nullable): a string containing the URI scheme for the given + * #GFile or %NULL if the #GFile was constructed with an invalid URI. The + * returned string should be freed with g_free() when no longer needed. */ char * g_file_get_uri_scheme (GFile *file) @@ -611,7 +614,8 @@ g_file_peek_path (GFile *file) * * This call does no blocking I/O. * - * Returns: a string containing the #GFile's URI. + * Returns: a string containing the #GFile's URI. If the #GFile was constructed + * with an invalid URI, an invalid URI is returned. * The returned string should be freed with g_free() * when no longer needed. */ diff --git a/gio/gfileattribute.c b/gio/gfileattribute.c index a38ac09d5..8075d1d53 100644 --- a/gio/gfileattribute.c +++ b/gio/gfileattribute.c @@ -274,8 +274,8 @@ valid_char (char c) static char * escape_byte_string (const char *str) { - size_t len; - int num_invalid, i; + size_t i, len; + int num_invalid; char *escaped_val, *p; unsigned char c; const char hex_digits[] = "0123456789abcdef"; diff --git a/gio/gfileinfo.c b/gio/gfileinfo.c index 1b008f79a..42cdc7a5d 100644 --- a/gio/gfileinfo.c +++ b/gio/gfileinfo.c @@ -318,7 +318,7 @@ static void g_file_info_finalize (GObject *object) { GFileInfo *info; - int i; + guint i; GFileAttribute *attrs; info = G_FILE_INFO (object); @@ -376,7 +376,7 @@ g_file_info_copy_into (GFileInfo *src_info, GFileInfo *dest_info) { GFileAttribute *source, *dest; - int i; + guint i; g_return_if_fail (G_IS_FILE_INFO (src_info)); g_return_if_fail (G_IS_FILE_INFO (dest_info)); @@ -439,7 +439,7 @@ g_file_info_set_attribute_mask (GFileInfo *info, GFileAttributeMatcher *mask) { GFileAttribute *attr; - int i; + guint i; g_return_if_fail (G_IS_FILE_INFO (info)); @@ -491,7 +491,7 @@ void g_file_info_clear_status (GFileInfo *info) { GFileAttribute *attrs; - int i; + guint i; g_return_if_fail (G_IS_FILE_INFO (info)); @@ -536,7 +536,7 @@ g_file_info_find_value (GFileInfo *info, guint32 attr_id) { GFileAttribute *attrs; - int i; + guint i; i = g_file_info_find_place (info, attr_id); attrs = (GFileAttribute *)info->attributes->data; @@ -599,7 +599,7 @@ g_file_info_has_namespace (GFileInfo *info, { GFileAttribute *attrs; guint32 ns_id; - int i; + guint i; g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); g_return_val_if_fail (name_space != NULL, FALSE); @@ -636,7 +636,7 @@ g_file_info_list_attributes (GFileInfo *info, GFileAttribute *attrs; guint32 attribute; guint32 ns_id = (name_space) ? lookup_namespace (name_space) : 0; - int i; + guint i; g_return_val_if_fail (G_IS_FILE_INFO (info), NULL); @@ -694,7 +694,7 @@ g_file_info_remove_attribute (GFileInfo *info, { guint32 attr_id; GFileAttribute *attrs; - int i; + guint i; g_return_if_fail (G_IS_FILE_INFO (info)); g_return_if_fail (attribute != NULL && *attribute != '\0'); @@ -734,6 +734,9 @@ g_file_info_get_attribute_data (GFileInfo *info, { GFileAttributeValue *value; + g_return_val_if_fail (G_IS_FILE_INFO (info), FALSE); + g_return_val_if_fail (attribute != NULL && *attribute != '\0', FALSE); + value = g_file_info_find_value_by_name (info, attribute); if (value == NULL) return FALSE; @@ -1072,7 +1075,7 @@ g_file_info_create_value (GFileInfo *info, guint32 attr_id) { GFileAttribute *attrs; - int i; + guint i; if (info->mask != NO_ATTRIBUTE_MASK && !_g_file_attribute_matcher_matches_id (info->mask, attr_id)) @@ -2616,7 +2619,7 @@ matcher_matches_id (GFileAttributeMatcher *matcher, guint32 id) { SubMatcher *sub_matchers; - int i; + guint i; if (matcher->sub_matchers) { @@ -2693,8 +2696,8 @@ g_file_attribute_matcher_enumerate_namespace (GFileAttributeMatcher *matcher, const char *ns) { SubMatcher *sub_matchers; - int ns_id; - int i; + guint ns_id; + guint i; g_return_val_if_fail (ns != NULL && *ns != '\0', FALSE); @@ -2735,7 +2738,7 @@ g_file_attribute_matcher_enumerate_namespace (GFileAttributeMatcher *matcher, const char * g_file_attribute_matcher_enumerate_next (GFileAttributeMatcher *matcher) { - int i; + guint i; SubMatcher *sub_matcher; /* We return a NULL matcher for an empty match string, so handle this */ diff --git a/gio/gicon.c b/gio/gicon.c index f1ba0e2dc..29fae1068 100644 --- a/gio/gicon.c +++ b/gio/gicon.c @@ -138,7 +138,7 @@ g_icon_to_string_tokenized (GIcon *icon, GString *s) GPtrArray *tokens; gint version; GIconIface *icon_iface; - int i; + guint i; g_return_val_if_fail (icon != NULL, FALSE); g_return_val_if_fail (G_IS_ICON (icon), FALSE); diff --git a/gio/gio-tool-info.c b/gio/gio-tool-info.c index 7cf568370..a06263545 100644 --- a/gio/gio-tool-info.c +++ b/gio/gio-tool-info.c @@ -182,7 +182,8 @@ show_info (GFile *file, GFileInfo *info) gchar *root_string = NULL; gchar *mount; gchar *fs; - gchar *options; + const gchar *options; + gchar *options_string = NULL; device = g_strescape (g_unix_mount_get_device_path (entry), NULL); root = g_unix_mount_get_root_path (entry); @@ -194,16 +195,22 @@ 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_strescape (g_unix_mount_get_options (entry), NULL); + + options = g_unix_mount_get_options (entry); + if (options != NULL) + { + options_string = g_strescape (options, NULL); + } g_print (_("unix mount: %s%s %s %s %s\n"), device, - root_string ? root_string : "", mount, fs, options); + root_string ? root_string : "", mount, fs, + options_string ? options_string : ""); g_free (device); g_free (root_string); g_free (mount); g_free (fs); - g_free (options); + g_free (options_string); g_unix_mount_free (entry); } diff --git a/gio/gio-tool-launch.c b/gio/gio-tool-launch.c new file mode 100644 index 000000000..08c91c68a --- /dev/null +++ b/gio/gio-tool-launch.c @@ -0,0 +1,131 @@ +/* + * Copyright 2020 Frederic Martinsons + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Frederic Martinsons <frederic.martinsons@sigfox.com> + */ + +#include "config.h" + +#include <gio/gio.h> + +#if defined(G_OS_UNIX) && !defined(HAVE_COCOA) +#include <gio/gdesktopappinfo.h> +#endif + +#include <gi18n.h> + +#include "gio-tool.h" + +static const GOptionEntry entries[] = { + { NULL } +}; + +int +handle_launch (int argc, char *argv[], gboolean do_help) +{ + GOptionContext *context; + GError *error = NULL; +#if defined(G_OS_UNIX) && !defined(HAVE_COCOA) + int i; + GAppInfo *app = NULL; + GAppLaunchContext *app_context = NULL; + GKeyFile *keyfile = NULL; + GList *args = NULL; + char *desktop_file = NULL; +#endif + int retval; + + g_set_prgname ("gio launch"); + + /* Translators: commandline placeholder */ + context = g_option_context_new (_("DESKTOP-FILE [FILE-ARG …]")); + g_option_context_set_help_enabled (context, FALSE); + g_option_context_set_summary (context, + _("Launch an application from a desktop file, passing optional filename arguments to it.")); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + + if (do_help) + { + show_help (context, NULL); + g_option_context_free (context); + return 0; + } + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + show_help (context, error->message); + g_error_free (error); + g_option_context_free (context); + return 1; + } + + if (argc < 2) + { + show_help (context, _("No desktop file given")); + g_option_context_free (context); + return 1; + } + + g_option_context_free (context); + +#if !defined(G_OS_UNIX) || defined(HAVE_COCOA) + print_error (_("The launch command is not currently supported on this platform")); + retval = 1; +#else + retval = 0; + desktop_file = argv[1]; + + /* Use keyfile api for loading desktop app in order to check for + * - not existing file. + * - invalid keyfile format. + */ + keyfile = g_key_file_new (); + if (!g_key_file_load_from_file (keyfile, desktop_file, G_KEY_FILE_NONE, &error)) + { + print_error (_("Unable to load ‘%s‘: %s"), desktop_file, error->message); + g_clear_error (&error); + retval = 1; + } + else + { + app = (GAppInfo*)g_desktop_app_info_new_from_keyfile (keyfile); + if (!app) + { + print_error (_("Unable to load application information for ‘%s‘"), desktop_file); + retval = 1; + } + else + { + for (i = 2; i < argc; i++) + { + args = g_list_append (args, g_file_new_for_commandline_arg (argv[i])); + } + app_context = g_app_launch_context_new (); + if (!g_app_info_launch (app, args, app_context, &error)) + { + print_error (_("Unable to launch application ‘%s’: %s"), desktop_file, error->message); + g_clear_error (&error); + retval = 1; + } + g_list_free_full (args, g_object_unref); + g_clear_object (&app_context); + } + g_clear_object (&app); + } + g_key_file_free (keyfile); +#endif + return retval; +} diff --git a/gio/gio-tool-trash.c b/gio/gio-tool-trash.c index 3be63a9b7..fc17b4cba 100644 --- a/gio/gio-tool-trash.c +++ b/gio/gio-tool-trash.c @@ -27,9 +27,14 @@ static gboolean force = FALSE; static gboolean empty = FALSE; +static gboolean restore = FALSE; +static gboolean list = FALSE; static const GOptionEntry entries[] = { { "force", 'f', 0, G_OPTION_ARG_NONE, &force, N_("Ignore nonexistent files, never prompt"), NULL }, { "empty", 0, 0, G_OPTION_ARG_NONE, &empty, N_("Empty the trash"), NULL }, + { "list", 0, 0, G_OPTION_ARG_NONE, &list, N_("List files in the trash with their original locations"), NULL }, + { "restore", 0, 0, G_OPTION_ARG_NONE, &restore, N_("Restore a file from trash to its original location (possibly " + "recreating the directory)"), NULL }, { NULL } }; @@ -75,6 +80,131 @@ delete_trash_file (GFile *file, gboolean del_file, gboolean del_children) g_file_delete (file, NULL, NULL); } +static gboolean +restore_trash (GFile *file, + gboolean force, + GCancellable *cancellable, + GError **error) +{ + GFileInfo *info = NULL; + GFile *target = NULL; + GFile *dir_target = NULL; + gboolean ret = FALSE; + gchar *orig_path = NULL; + GError *local_error = NULL; + + info = g_file_query_info (file, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, G_FILE_QUERY_INFO_NONE, cancellable, &local_error); + if (local_error) + { + g_propagate_error (error, local_error); + goto exit_func; + } + + orig_path = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH); + if (!orig_path) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Unable to find original path")); + goto exit_func; + } + + target = g_file_new_for_commandline_arg (orig_path); + g_free (orig_path); + + dir_target = g_file_get_parent (target); + if (dir_target) + { + g_file_make_directory_with_parents (dir_target, cancellable, &local_error); + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + { + g_clear_error (&local_error); + } + else if (local_error != NULL) + { + g_propagate_prefixed_error (error, local_error, _("Unable to recreate original location: ")); + goto exit_func; + } + } + + if (!g_file_move (file, + target, + force ? G_FILE_COPY_OVERWRITE : G_FILE_COPY_NONE, + cancellable, + NULL, + NULL, + &local_error)) + { + g_propagate_prefixed_error (error, local_error, _("Unable to move file to its original location: ")); + goto exit_func; + } + ret = TRUE; + +exit_func: + g_clear_object (&target); + g_clear_object (&dir_target); + g_clear_object (&info); + return ret; +} + +static gboolean +trash_list (GFile *file, + GCancellable *cancellable, + GError **error) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + GError *local_error = NULL; + gboolean res; + + enumerator = g_file_enumerate_children (file, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_TRASH_ORIG_PATH, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, + &local_error); + if (!enumerator) + { + g_propagate_error (error, local_error); + return FALSE; + } + + res = TRUE; + while ((info = g_file_enumerator_next_file (enumerator, cancellable, &local_error)) != NULL) + { + const char *name; + char *orig_path; + char *uri; + GFile* child; + + name = g_file_info_get_name (info); + child = g_file_get_child (file, name); + uri = g_file_get_uri (child); + g_object_unref (child); + orig_path = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH); + + g_print ("%s\t%s\n", uri, orig_path); + + g_object_unref (info); + g_free (orig_path); + g_free (uri); + } + + if (local_error) + { + g_propagate_error (error, local_error); + local_error = NULL; + res = FALSE; + } + + if (!g_file_enumerator_close (enumerator, cancellable, &local_error)) + { + print_file_error (file, local_error->message); + g_clear_error (&local_error); + res = FALSE; + } + + return res; +} + int handle_trash (int argc, char *argv[], gboolean do_help) { @@ -92,7 +222,10 @@ handle_trash (int argc, char *argv[], gboolean do_help) g_free (param); g_option_context_set_help_enabled (context, FALSE); g_option_context_set_summary (context, - _("Move files or directories to the trash.")); + _("Move/Restore files or directories to the trash.")); + g_option_context_set_description (context, + _("Note: for --restore switch, if the original location of the trashed file \n" + "already exists, it will not be overwritten unless --force is set.")); g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); if (do_help) @@ -118,7 +251,20 @@ handle_trash (int argc, char *argv[], gboolean do_help) { file = g_file_new_for_commandline_arg (argv[i]); error = NULL; - if (!g_file_trash (file, NULL, &error)) + if (restore) + { + if (!g_file_has_uri_scheme (file, "trash")) + { + print_file_error (file, _("Location given doesn't start with trash:///")); + retval = 1; + } + else if (!restore_trash (file, force, NULL, &error)) + { + print_file_error (file, error->message); + retval = 1; + } + } + else if (!g_file_trash (file, NULL, &error)) { if (!force || !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) @@ -126,13 +272,25 @@ handle_trash (int argc, char *argv[], gboolean do_help) print_file_error (file, error->message); retval = 1; } - g_error_free (error); } + g_clear_error (&error); g_object_unref (file); } } - - if (empty) + else if (list) + { + GFile *file; + file = g_file_new_for_uri ("trash:"); + trash_list (file, NULL, &error); + if (error) + { + print_file_error (file, error->message); + g_clear_error (&error); + retval = 1; + } + g_object_unref (file); + } + else if (empty) { GFile *file; file = g_file_new_for_uri ("trash:"); @@ -140,7 +298,7 @@ handle_trash (int argc, char *argv[], gboolean do_help) g_object_unref (file); } - if (argc == 1 && !empty) + if (argc == 1 && !empty && !list) { show_help (context, _("No locations given")); g_option_context_free (context); diff --git a/gio/gio-tool.c b/gio/gio-tool.c index c0aec7492..ff82c638e 100644 --- a/gio/gio-tool.c +++ b/gio/gio-tool.c @@ -229,6 +229,7 @@ usage (void) g_printerr (" cat %s\n", _("Concatenate files to standard output")); g_printerr (" copy %s\n", _("Copy one or more files")); g_printerr (" info %s\n", _("Show information about locations")); + g_printerr (" launch %s\n", _("Launch an application from a desktop file")); g_printerr (" list %s\n", _("List the contents of locations")); g_printerr (" mime %s\n", _("Get or set the handler for a mimetype")); g_printerr (" mkdir %s\n", _("Create directories")); @@ -312,6 +313,8 @@ main (int argc, char **argv) return handle_copy (argc, argv, do_help); else if (g_str_equal (command, "info")) return handle_info (argc, argv, do_help); + else if (g_str_equal (command, "launch")) + return handle_launch (argc, argv, do_help); else if (g_str_equal (command, "list")) return handle_list (argc, argv, do_help); else if (g_str_equal (command, "mime")) diff --git a/gio/gio-tool.h b/gio/gio-tool.h index 5064165a6..6cd1d946a 100644 --- a/gio/gio-tool.h +++ b/gio/gio-tool.h @@ -37,6 +37,7 @@ gboolean file_is_dir (GFile *file); int handle_cat (int argc, char *argv[], gboolean do_help); int handle_copy (int argc, char *argv[], gboolean do_help); int handle_info (int argc, char *argv[], gboolean do_help); +int handle_launch (int argc, char *argv[], gboolean do_help); int handle_list (int argc, char *argv[], gboolean do_help); int handle_mime (int argc, char *argv[], gboolean do_help); int handle_mkdir (int argc, char *argv[], gboolean do_help); diff --git a/gio/giomodule.c b/gio/giomodule.c index e27f1ab76..4c4c75b90 100644 --- a/gio/giomodule.c +++ b/gio/giomodule.c @@ -30,6 +30,7 @@ #include "giomodule.h" #include "giomodule-priv.h" +#include "glib-private.h" #include "glocalfilemonitor.h" #include "gnativevolumemonitor.h" #include "gproxyresolver.h" @@ -477,9 +478,7 @@ g_io_modules_scan_all_in_directory_with_scope (const char *dirname, filename = g_build_filename (dirname, "giomodule.cache", NULL); - cache = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, (GDestroyNotify)g_strfreev); - + cache = NULL; cache_time = 0; if (g_stat (filename, &statbuf) == 0 && g_file_get_contents (filename, &data, NULL, NULL)) @@ -523,6 +522,10 @@ g_io_modules_scan_all_in_directory_with_scope (const char *dirname, while (g_ascii_isspace (*colon)) colon++; + if (G_UNLIKELY (!cache)) + cache = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)g_strfreev); + extension_points = g_strsplit (colon, ",", -1); g_hash_table_insert (cache, file, extension_points); } @@ -536,13 +539,15 @@ g_io_modules_scan_all_in_directory_with_scope (const char *dirname, GIOExtensionPoint *extension_point; GIOModule *module; gchar *path; - char **extension_points; + char **extension_points = NULL; int i; path = g_build_filename (dirname, name, NULL); module = g_io_module_new (path); - extension_points = g_hash_table_lookup (cache, name); + if (cache) + extension_points = g_hash_table_lookup (cache, name); + if (extension_points != NULL && g_stat (path, &statbuf) == 0 && statbuf.st_ctime <= cache_time) @@ -577,7 +582,8 @@ g_io_modules_scan_all_in_directory_with_scope (const char *dirname, g_dir_close (dir); - g_hash_table_destroy (cache); + if (cache) + g_hash_table_destroy (cache); g_free (filename); } @@ -807,6 +813,9 @@ _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) { @@ -956,6 +965,9 @@ _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) { @@ -1154,8 +1166,16 @@ static gchar * get_gio_module_dir (void) { gchar *module_dir; - - module_dir = g_strdup (g_getenv ("GIO_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; if (module_dir == NULL) { #ifdef G_OS_WIN32 @@ -1187,13 +1207,14 @@ _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 */ - module_path = g_getenv ("GIO_EXTRA_MODULES"); + /* First load any overrides, extras (but not if running as setuid!) */ + module_path = !is_setuid ? g_getenv ("GIO_EXTRA_MODULES") : NULL; if (module_path) { gchar **paths; diff --git a/gio/gliststore.c b/gio/gliststore.c index 3c2361e1b..da7d12ce5 100644 --- a/gio/gliststore.c +++ b/gio/gliststore.c @@ -269,7 +269,7 @@ g_list_store_insert (GListStore *store, g_return_if_fail (G_IS_LIST_STORE (store)); g_return_if_fail (g_type_is_a (G_OBJECT_TYPE (item), store->item_type)); - g_return_if_fail (position <= g_sequence_get_length (store->items)); + g_return_if_fail (position <= (guint) g_sequence_get_length (store->items)); it = g_sequence_get_iter_at_pos (store->items, position); g_sequence_insert_before (it, g_object_ref (item)); @@ -477,7 +477,7 @@ g_list_store_splice (GListStore *store, if (n_additions) { - gint i; + guint i; for (i = 0; i < n_additions; i++) { diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 15738d8b8..f9603e053 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -2026,7 +2026,7 @@ g_local_file_trash (GFile *file, display_name = g_filename_display_name (trashdir); g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), - _("Unable to create trash dir %s: %s"), + _("Unable to create trash directory %s: %s"), display_name, g_strerror (errsv)); g_free (display_name); g_free (trashdir); @@ -2038,6 +2038,7 @@ g_local_file_trash (GFile *file, { uid_t uid; char uid_str[32]; + gboolean success; uid = geteuid (); g_snprintf (uid_str, sizeof (uid_str), "%lu", (unsigned long)uid); @@ -2093,6 +2094,7 @@ g_local_file_trash (GFile *file, /* No global trash dir, or it failed the tests, fall back to $topdir/.Trash-$uid */ dirname = g_strdup_printf (".Trash-%s", uid_str); trashdir = g_build_filename (topdir, dirname, NULL); + success = TRUE; g_free (dirname); tried_create = FALSE; @@ -2108,8 +2110,7 @@ g_local_file_trash (GFile *file, g_remove (trashdir); /* Not a directory or not owned by user, ignore */ - g_free (trashdir); - trashdir = NULL; + success = FALSE; } } else @@ -2124,18 +2125,28 @@ g_local_file_trash (GFile *file, } else { - g_free (trashdir); - trashdir = NULL; + success = FALSE; } } } - if (trashdir == NULL) + if (!success) { - g_free (topdir); - g_set_io_error (error, - _("Unable to find or create trash directory for %s"), - file, G_IO_ERROR_NOT_SUPPORTED); + gchar *trashdir_display_name = NULL, *file_display_name = NULL; + + trashdir_display_name = g_filename_display_name (trashdir); + file_display_name = g_filename_display_name (local->filename); + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Unable to find or create trash directory %s to trash %s"), + trashdir_display_name, file_display_name); + + g_free (trashdir_display_name); + g_free (file_display_name); + + g_free (topdir); + g_free (trashdir); + return FALSE; } } @@ -2144,21 +2155,33 @@ g_local_file_trash (GFile *file, infodir = g_build_filename (trashdir, "info", NULL); filesdir = g_build_filename (trashdir, "files", NULL); - g_free (trashdir); /* Make sure we have the subdirectories */ if ((g_mkdir (infodir, 0700) == -1 && errno != EEXIST) || (g_mkdir (filesdir, 0700) == -1 && errno != EEXIST)) { + gchar *trashdir_display_name = NULL, *file_display_name = NULL; + + trashdir_display_name = g_filename_display_name (trashdir); + file_display_name = g_filename_display_name (local->filename); + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Unable to find or create trash directory %s to trash %s"), + trashdir_display_name, file_display_name); + + g_free (trashdir_display_name); + g_free (file_display_name); + g_free (topdir); + g_free (trashdir); g_free (infodir); g_free (filesdir); - g_set_io_error (error, - _("Unable to find or create trash directory for %s"), - file, G_IO_ERROR_NOT_SUPPORTED); + return FALSE; } + g_free (trashdir); + basename = g_path_get_basename (local->filename); i = 1; trashname = NULL; @@ -2842,22 +2865,24 @@ g_local_file_measure_size_of_contents (gint fd, gboolean success = TRUE; const gchar *name; GDir *dir; + gint saved_errno; #ifdef AT_FDCWD { - /* If this fails, we want to preserve the errno from fopendir() */ + /* If this fails, we want to preserve the errno from fdopendir() */ DIR *dirp; dirp = fdopendir (fd); + saved_errno = errno; dir = dirp ? GLIB_PRIVATE_CALL(g_dir_new_from_dirp) (dirp) : NULL; + g_assert ((dirp == NULL) == (dir == NULL)); } #else dir = GLIB_PRIVATE_CALL(g_dir_open_with_errno) (dir_name->data, 0); + saved_errno = errno; #endif if (dir == NULL) { - gint saved_errno = errno; - #ifdef AT_FDCWD close (fd); #endif diff --git a/gio/gosxappinfo.h b/gio/gosxappinfo.h index 7beeaad16..793ce1fcd 100644 --- a/gio/gosxappinfo.h +++ b/gio/gosxappinfo.h @@ -43,7 +43,7 @@ GLIB_AVAILABLE_IN_2_52 GType g_osx_app_info_get_type (void) G_GNUC_CONST; GLIB_AVAILABLE_IN_2_52 -char * g_osx_app_info_get_filename (GOsxAppInfo *info); +const char *g_osx_app_info_get_filename (GOsxAppInfo *info); GLIB_AVAILABLE_IN_2_52 GList * g_osx_app_info_get_all_for_scheme (const gchar *scheme); diff --git a/gio/gosxappinfo.m b/gio/gosxappinfo.m index 849d63575..03c373778 100644 --- a/gio/gosxappinfo.m +++ b/gio/gosxappinfo.m @@ -168,6 +168,7 @@ get_bundle_string_value (NSBundle *bundle, static CFStringRef create_cfstring_from_cstr (const gchar *cstr) { + g_return_val_if_fail (cstr != NULL, NULL); return CFStringCreateWithCString (NULL, cstr, kCFStringEncodingUTF8); } @@ -226,8 +227,8 @@ url_escape_hostname (const char *url) } static CFURLRef -create_url_from_cstr (gchar *cstr, - gboolean is_file) +create_url_from_cstr (const gchar *cstr, + gboolean is_file) { gchar *puny_cstr; CFStringRef str; @@ -279,11 +280,17 @@ create_urlspec_for_appinfo (GOsxAppInfo *info, GList *uris, gboolean are_files) { - LSLaunchURLSpec *urlspec = g_new0 (LSLaunchURLSpec, 1); - gchar *app_cstr = g_osx_app_info_get_filename (info); + LSLaunchURLSpec *urlspec = NULL; + const gchar *app_cstr; + + g_return_val_if_fail (G_IS_OSX_APP_INFO (info), NULL); + + urlspec = g_new0 (LSLaunchURLSpec, 1); + app_cstr = g_osx_app_info_get_filename (info); + g_assert (app_cstr != NULL); /* Strip file:// from app url but ensure filesystem url */ - urlspec->appURL = create_url_from_cstr (app_cstr + 7, TRUE); + urlspec->appURL = create_url_from_cstr (app_cstr + strlen ("file://"), TRUE); urlspec->launchFlags = kLSLaunchDefaults; urlspec->itemURLs = create_url_list_from_glist (uris, are_files); @@ -402,7 +409,7 @@ g_osx_app_info_get_executable (GAppInfo *appinfo) return info->executable; } -char * +const char * g_osx_app_info_get_filename (GOsxAppInfo *info) { g_return_val_if_fail (info != NULL, NULL); @@ -431,7 +438,8 @@ g_osx_app_info_get_icon (GAppInfo *appinfo) if (!info->icon) { - gchar *icon_name, *app_uri, *icon_uri; + const gchar *app_uri; + gchar *icon_name, *icon_uri; GFile *file; icon_name = get_bundle_string_value (info->bundle, @"CFBundleIconFile"); @@ -439,7 +447,7 @@ g_osx_app_info_get_icon (GAppInfo *appinfo) return NULL; app_uri = g_osx_app_info_get_filename (info); - icon_uri = g_strconcat (app_uri + 7, "/Contents/Resources/", icon_name, + icon_uri = g_strconcat (app_uri + strlen ("file://"), "/Contents/Resources/", icon_name, g_str_has_suffix (icon_name, ".icns") ? NULL : ".icns", NULL); g_free (icon_name); @@ -459,9 +467,14 @@ g_osx_app_info_launch_internal (GAppInfo *appinfo, GError **error) { GOsxAppInfo *info = G_OSX_APP_INFO (appinfo); - LSLaunchURLSpec *urlspec = create_urlspec_for_appinfo (info, uris, are_files); + LSLaunchURLSpec *urlspec; gint ret, success = TRUE; + g_return_val_if_fail (G_IS_OSX_APP_INFO (appinfo), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + urlspec = create_urlspec_for_appinfo (info, uris, are_files); + if ((ret = LSOpenFromURLSpec (urlspec, NULL))) { /* TODO: Better error codes */ diff --git a/gio/gresource.c b/gio/gresource.c index b495d12ac..53933f9d2 100644 --- a/gio/gresource.c +++ b/gio/gresource.c @@ -32,6 +32,8 @@ #include <gio/gzlibdecompressor.h> #include <gio/gconverterinputstream.h> +#include "glib-private.h" + struct _GResource { int ref_count; @@ -76,12 +78,16 @@ G_DEFINE_BOXED_TYPE (GResource, g_resource, g_resource_ref, g_resource_unref) * the xmllint executable, or xmllint must be in the `PATH`; otherwise * the preprocessing step is skipped. * - * `to-pixdata` which will use the gdk-pixbuf-pixdata command to convert - * images to the GdkPixdata format, which allows you to create pixbufs directly using the data inside - * the resource file, rather than an (uncompressed) copy of it. For this, the gdk-pixbuf-pixdata - * program must be in the PATH, or the `GDK_PIXBUF_PIXDATA` environment variable must be - * set to the full path to the gdk-pixbuf-pixdata executable; otherwise the resource compiler will - * abort. + * `to-pixdata` (deprecated since gdk-pixbuf 2.32) which will use the + * `gdk-pixbuf-pixdata` command to convert images to the #GdkPixdata format, + * which allows you to create pixbufs directly using the data inside the + * resource file, rather than an (uncompressed) copy of it. For this, the + * `gdk-pixbuf-pixdata` program must be in the `PATH`, or the + * `GDK_PIXBUF_PIXDATA` environment variable must be set to the full path to the + * `gdk-pixbuf-pixdata` executable; otherwise the resource compiler will abort. + * `to-pixdata` has been deprecated since gdk-pixbuf 2.32, as #GResource + * supports embedding modern image formats just as well. Instead of using it, + * embed a PNG or SVG file in your #GResource. * * `json-stripblanks` which will use the `json-glib-format` command to strip * ignorable whitespace from the JSON file. For this to work, the @@ -159,7 +165,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. + * during resource lookups. It is ignored when running in a setuid process. * * A substitution has the form * @@ -330,10 +336,13 @@ 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; - envvar = g_getenv ("G_RESOURCE_OVERLAYS"); + /* Don’t load overlays if setuid, as they could allow reading privileged + * files. */ + envvar = !is_setuid ? g_getenv ("G_RESOURCE_OVERLAYS") : NULL; if (envvar != NULL) { gchar **parts; @@ -929,9 +938,10 @@ g_resource_enumerate_children (GResource *resource, if (*path == 0) { - g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, - _("The resource at “%s” does not exist"), - path); + if (error) + g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, + _("The resource at “%s” does not exist"), + path); return NULL; } @@ -968,9 +978,10 @@ g_resource_enumerate_children (GResource *resource, if (children == NULL) { - g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, - _("The resource at “%s” does not exist"), - path); + if (error) + g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, + _("The resource at “%s” does not exist"), + path); return NULL; } @@ -1237,9 +1248,10 @@ g_resources_enumerate_children (const gchar *path, if (hash == NULL) { - g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, - _("The resource at “%s” does not exist"), - path); + if (error) + g_set_error (error, G_RESOURCE_ERROR, G_RESOURCE_ERROR_NOT_FOUND, + _("The resource at “%s” does not exist"), + path); return NULL; } else diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index cbc3fad27..26b9a65ad 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -18,6 +18,7 @@ #include "config.h" +#include "glib-private.h" #include "gsettingsschema-internal.h" #include "gsettings.h" @@ -343,6 +344,7 @@ 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; @@ -357,7 +359,9 @@ initialise_schema_sources (void) try_prepend_data_dir (g_get_user_data_dir ()); - if ((path = g_getenv ("GSETTINGS_SCHEMA_DIR")) != NULL) + /* 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) { extra_schema_dirs = g_strsplit (path, G_SEARCHPATH_SEPARATOR_S, 0); for (i = 0; extra_schema_dirs[i]; i++); diff --git a/gio/gsocket.c b/gio/gsocket.c index 0f8f9259f..69fc72825 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -624,6 +624,16 @@ g_socket (gint domain, fcntl (fd, F_SETFD, flags); } } +#else + if ((domain == AF_INET || domain == AF_INET6) && type == SOCK_DGRAM) + { + BOOL new_behavior = FALSE; + DWORD bytes_returned = 0; + + /* Disable connection reset error on ICMP port unreachable. */ + WSAIoctl (fd, SIO_UDP_CONNRESET, &new_behavior, sizeof (new_behavior), + NULL, 0, &bytes_returned, NULL, NULL); + } #endif return fd; @@ -3790,6 +3800,9 @@ update_select_events (GSocket *socket) GList *l; WSAEVENT event; + if (socket->priv->closed) + return; + ensure_event (socket); event_mask = 0; @@ -3848,7 +3861,8 @@ update_condition_unlocked (GSocket *socket) WSANETWORKEVENTS events; GIOCondition condition; - if (WSAEnumNetworkEvents (socket->priv->fd, + if (!socket->priv->closed && + WSAEnumNetworkEvents (socket->priv->fd, socket->priv->event, &events) == 0) { @@ -5475,10 +5489,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, @@ -5965,10 +5979,11 @@ g_socket_get_credentials (GSocket *socket, socklen_t optlen = sizeof (cred); if (getsockopt (socket->priv->fd, - 0, + SOL_LOCAL, LOCAL_PEERCRED, &cred, - &optlen) == 0) + &optlen) == 0 + && optlen != 0) { if (cred.cr_version == XUCRED_VERSION) { @@ -5993,6 +6008,15 @@ g_socket_get_credentials (GSocket *socket, return NULL; } } + else if (optlen == 0 || errno == EINVAL) + { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Unable to read socket credentials: %s"), + "unsupported socket type"); + return NULL; + } } #elif G_CREDENTIALS_USE_NETBSD_UNPCBID { diff --git a/gio/gtlsdatabase.c b/gio/gtlsdatabase.c index 9341206f1..d7dcf4bbe 100644 --- a/gio/gtlsdatabase.c +++ b/gio/gtlsdatabase.c @@ -516,8 +516,6 @@ g_tls_database_verify_chain (GTlsDatabase *self, GError **error) { g_return_val_if_fail (G_IS_TLS_DATABASE (self), G_TLS_CERTIFICATE_GENERIC_ERROR); - g_return_val_if_fail (G_IS_TLS_DATABASE (self), - G_TLS_CERTIFICATE_GENERIC_ERROR); g_return_val_if_fail (G_IS_TLS_CERTIFICATE (chain), G_TLS_CERTIFICATE_GENERIC_ERROR); g_return_val_if_fail (purpose, G_TLS_CERTIFICATE_GENERIC_ERROR); diff --git a/gio/gvdb/gvdb-builder.c b/gio/gvdb/gvdb-builder.c index b8ecbe3d7..918ee43fd 100644 --- a/gio/gvdb/gvdb-builder.c +++ b/gio/gvdb/gvdb-builder.c @@ -206,7 +206,7 @@ item_to_index (GvdbItem *item) if (item != NULL) return item->assigned_index; - return guint32_to_le (-1u); + return guint32_to_le ((guint32) -1); } typedef struct @@ -234,7 +234,7 @@ file_builder_allocate (FileBuilder *fb, if (size == 0) return NULL; - fb->offset += (-fb->offset) & (alignment - 1); + fb->offset += (guint64) (-fb->offset) & (alignment - 1); chunk = g_slice_new (FileChunk); chunk->offset = fb->offset; chunk->size = size; @@ -463,9 +463,11 @@ static GString * file_builder_serialise (FileBuilder *fb, struct gvdb_pointer root) { - struct gvdb_header header = { { 0, }, }; + struct gvdb_header header; GString *result; + memset (&header, 0, sizeof (header)); + if (fb->byteswap) { header.signature[0] = GVDB_SWAPPED_SIGNATURE0; diff --git a/gio/gvdb/gvdb-reader.c b/gio/gvdb/gvdb-reader.c index 6bc4c6f36..820ce4c3d 100644 --- a/gio/gvdb/gvdb-reader.c +++ b/gio/gvdb/gvdb-reader.c @@ -379,7 +379,7 @@ gvdb_table_get_names (GvdbTable *table, * a pass that fills in no additional items. * * This takes an O(n) algorithm and turns it into O(n*m) where m is - * the depth of the tree, but in all sane cases the tree won't be very + * the depth of the tree, but typically the tree won't be very * deep and the constant factor of this algorithm is lower (and the * complexity of coding it, as well). */ diff --git a/gio/gwin32api-application-activation-manager.h b/gio/gwin32api-application-activation-manager.h new file mode 100755 index 000000000..cf44b93e6 --- /dev/null +++ b/gio/gwin32api-application-activation-manager.h @@ -0,0 +1,126 @@ +#if NTDDI_VERSION < NTDDI_WIN8 +/* The following code is copied verbatim from MinGW-w64 shobjidl.h */ +/* + * IApplicationActivationManager interface + */ +typedef enum ACTIVATEOPTIONS { + AO_NONE = 0x0, + AO_DESIGNMODE = 0x1, + AO_NOERRORUI = 0x2, + AO_NOSPLASHSCREEN = 0x4 +} ACTIVATEOPTIONS; + +DEFINE_ENUM_FLAG_OPERATORS(ACTIVATEOPTIONS) + +#ifndef __IApplicationActivationManager_INTERFACE_DEFINED__ +#define __IApplicationActivationManager_INTERFACE_DEFINED__ + +DEFINE_GUID(IID_IApplicationActivationManager, 0x2e941141, 0x7f97, 0x4756, 0xba,0x1d, 0x9d,0xec,0xde,0x89,0x4a,0x3d); +#if defined(__cplusplus) && !defined(CINTERFACE) +MIDL_INTERFACE("2e941141-7f97-4756-ba1d-9decde894a3d") +IApplicationActivationManager : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE ActivateApplication( + LPCWSTR appUserModelId, + LPCWSTR arguments, + ACTIVATEOPTIONS options, + DWORD *processId) = 0; + + virtual HRESULT STDMETHODCALLTYPE ActivateForFile( + LPCWSTR appUserModelId, + IShellItemArray *itemArray, + LPCWSTR verb, + DWORD *processId) = 0; + + virtual HRESULT STDMETHODCALLTYPE ActivateForProtocol( + LPCWSTR appUserModelId, + IShellItemArray *itemArray, + DWORD *processId) = 0; + +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IApplicationActivationManager, 0x2e941141, 0x7f97, 0x4756, 0xba,0x1d, 0x9d,0xec,0xde,0x89,0x4a,0x3d) +#endif +#else +typedef struct IApplicationActivationManagerVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IApplicationActivationManager *This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IApplicationActivationManager *This); + + ULONG (STDMETHODCALLTYPE *Release)( + IApplicationActivationManager *This); + + /*** IApplicationActivationManager methods ***/ + HRESULT (STDMETHODCALLTYPE *ActivateApplication)( + IApplicationActivationManager *This, + LPCWSTR appUserModelId, + LPCWSTR arguments, + ACTIVATEOPTIONS options, + DWORD *processId); + + HRESULT (STDMETHODCALLTYPE *ActivateForFile)( + IApplicationActivationManager *This, + LPCWSTR appUserModelId, + IShellItemArray *itemArray, + LPCWSTR verb, + DWORD *processId); + + HRESULT (STDMETHODCALLTYPE *ActivateForProtocol)( + IApplicationActivationManager *This, + LPCWSTR appUserModelId, + IShellItemArray *itemArray, + DWORD *processId); + + END_INTERFACE +} IApplicationActivationManagerVtbl; + +interface IApplicationActivationManager { + CONST_VTBL IApplicationActivationManagerVtbl* lpVtbl; +}; + +#ifdef COBJMACROS +#ifndef WIDL_C_INLINE_WRAPPERS +/*** IUnknown methods ***/ +#define IApplicationActivationManager_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IApplicationActivationManager_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IApplicationActivationManager_Release(This) (This)->lpVtbl->Release(This) +/*** IApplicationActivationManager methods ***/ +#define IApplicationActivationManager_ActivateApplication(This,appUserModelId,arguments,options,processId) (This)->lpVtbl->ActivateApplication(This,appUserModelId,arguments,options,processId) +#define IApplicationActivationManager_ActivateForFile(This,appUserModelId,itemArray,verb,processId) (This)->lpVtbl->ActivateForFile(This,appUserModelId,itemArray,verb,processId) +#define IApplicationActivationManager_ActivateForProtocol(This,appUserModelId,itemArray,processId) (This)->lpVtbl->ActivateForProtocol(This,appUserModelId,itemArray,processId) +#else +/*** IUnknown methods ***/ +static FORCEINLINE HRESULT IApplicationActivationManager_QueryInterface(IApplicationActivationManager* This,REFIID riid,void **ppvObject) { + return This->lpVtbl->QueryInterface(This,riid,ppvObject); +} +static FORCEINLINE ULONG IApplicationActivationManager_AddRef(IApplicationActivationManager* This) { + return This->lpVtbl->AddRef(This); +} +static FORCEINLINE ULONG IApplicationActivationManager_Release(IApplicationActivationManager* This) { + return This->lpVtbl->Release(This); +} +/*** IApplicationActivationManager methods ***/ +static FORCEINLINE HRESULT IApplicationActivationManager_ActivateApplication(IApplicationActivationManager* This,LPCWSTR appUserModelId,LPCWSTR arguments,ACTIVATEOPTIONS options,DWORD *processId) { + return This->lpVtbl->ActivateApplication(This,appUserModelId,arguments,options,processId); +} +static FORCEINLINE HRESULT IApplicationActivationManager_ActivateForFile(IApplicationActivationManager* This,LPCWSTR appUserModelId,IShellItemArray *itemArray,LPCWSTR verb,DWORD *processId) { + return This->lpVtbl->ActivateForFile(This,appUserModelId,itemArray,verb,processId); +} +static FORCEINLINE HRESULT IApplicationActivationManager_ActivateForProtocol(IApplicationActivationManager* This,LPCWSTR appUserModelId,IShellItemArray *itemArray,DWORD *processId) { + return This->lpVtbl->ActivateForProtocol(This,appUserModelId,itemArray,processId); +} +#endif +#endif + +#endif + + +#endif /* __IApplicationActivationManager_INTERFACE_DEFINED__ */ +#endif /* NTDDI_VERSION < NTDDI_WIN8 */
\ No newline at end of file diff --git a/gio/gwin32api-iterator.h b/gio/gwin32api-iterator.h new file mode 100755 index 000000000..d4df8a7b8 --- /dev/null +++ b/gio/gwin32api-iterator.h @@ -0,0 +1,125 @@ +typedef interface IIterator IIterator; +typedef interface IIterable IIterable; + +/* IIterator */ +typedef struct IIteratorVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IIterator *This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IIterator *This); + + ULONG (STDMETHODCALLTYPE *Release)( + IIterator *This); + + /*** IInspectable methods ***/ + HRESULT (STDMETHODCALLTYPE *GetIids)( + IIterator *This, + UINT32 *count, + IID **ids); + + HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)( + IIterator *This, + HSTRING *className); + + HRESULT (STDMETHODCALLTYPE *GetTrustLevel)( + IIterator *This, + TrustLevel *trustLevel); + + /*** IIterator methods ***/ + HRESULT (STDMETHODCALLTYPE *get_Current)( + IIterator *This, + IUnknown **current); + + HRESULT (STDMETHODCALLTYPE *get_HasCurrent)( + IIterator *This, + CHAR *hasCurrent); + + HRESULT (STDMETHODCALLTYPE *MoveNext)( + IIterator *This, + CHAR *hasCurrent); + + HRESULT (STDMETHODCALLTYPE *GetMany)( + IIterator *This, + UINT capacity, + void *value, + UINT *actual); + + END_INTERFACE +} IIteratorVtbl; + +interface IIterator { + CONST_VTBL IIteratorVtbl* lpVtbl; +}; + +/*** IUnknown methods ***/ +#define IIterator_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IIterator_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IIterator_Release(This) (This)->lpVtbl->Release(This) +/*** IInspectable methods ***/ +#define IIterator_GetIids(This,count,ids) (This)->lpVtbl->GetIids(This,count,ids) +#define IIterator_GetRuntimeClassName(This,name) (This)->lpVtbl->GetRuntimeClassName(This,name) +#define IIterator_GetTrustLevel(This,level) (This)->lpVtbl->GetTrustLevel(This,level) +/*** IIterator methods ***/ +#define IIterator_get_Current(This,current) (This)->lpVtbl->get_Current(This,current) +#define IIterator_get_HasCurrent(This,hasCurrent) (This)->lpVtbl->get_HasCurrent(This,hasCurrent) +#define IIterator_MoveNext(This,hasCurrent) (This)->lpVtbl->MoveNext(This,hasCurrent) +#define IIterator_GetMany(This,capacity,value,actual) (This)->lpVtbl->GetMany(This,capacity,value,actual) + +/* IIterable */ +typedef struct IIterableVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IIterable *This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IIterable *This); + + ULONG (STDMETHODCALLTYPE *Release)( + IIterable *This); + + /*** IInspectable methods ***/ + HRESULT (STDMETHODCALLTYPE *GetIids)( + IIterable *This, + UINT32 *count, + IID **ids); + + HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)( + IIterable *This, + HSTRING *className); + + HRESULT (STDMETHODCALLTYPE *GetTrustLevel)( + IIterable *This, + TrustLevel *trustLevel); + + /*** IIterable methods ***/ + HRESULT (STDMETHODCALLTYPE *First)( + IIterable *This, + IIterator **first); + + END_INTERFACE +} IIterableVtbl; + +interface IIterable { + CONST_VTBL IIterableVtbl* lpVtbl; +}; + +/*** IUnknown methods ***/ +#define IIterable_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IIterable_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IIterable_Release(This) (This)->lpVtbl->Release(This) +/*** IInspectable methods ***/ +#define IIterable_GetIids(This,count,ids) (This)->lpVtbl->GetIids(This,count,ids) +#define IIterable_GetRuntimeClassName(This,name) (This)->lpVtbl->GetRuntimeClassName(This,name) +#define IIterable_GetTrustLevel(This,level) (This)->lpVtbl->GetTrustLevel(This,level) +/*** IIterable methods ***/ +#define IIterable_First(This,retval) (This)->lpVtbl->First(This,retval) diff --git a/gio/gwin32api-misc.h b/gio/gwin32api-misc.h new file mode 100755 index 000000000..2b45d9abd --- /dev/null +++ b/gio/gwin32api-misc.h @@ -0,0 +1 @@ +typedef interface IProcessorArchitecture IProcessorArchitecture; diff --git a/gio/gwin32api-package.h b/gio/gwin32api-package.h new file mode 100755 index 000000000..9842a86fa --- /dev/null +++ b/gio/gwin32api-package.h @@ -0,0 +1,264 @@ +typedef interface IPackageManager IPackageManager; +typedef interface IPackage IPackage; +typedef interface IPackageId IPackageId; +typedef interface IPackageVersion IPackageVersion; + +DEFINE_GUID(IID_IPackageManager, 0x9A7D4B65, 0x5E8F, 0x4FC7, 0xA2, 0xE5, 0x7F, 0x69, 0x25, 0xCB, 0x8B, 0x53); +DEFINE_GUID(IID_IPackage, 0x163C792F, 0xBD75, 0x413C, 0xBF, 0x23, 0xB1, 0xFE, 0x7B, 0x95, 0xD8, 0x25); + +/* IPackageManager */ +typedef struct IPackageManagerVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IPackageManager *This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IPackageManager *This); + + ULONG (STDMETHODCALLTYPE *Release)( + IPackageManager *This); + + /*** IInspectable methods ***/ + HRESULT (STDMETHODCALLTYPE *GetIids)( + IPackageManager *This, + UINT32 *count, + IID **ids); + + HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)( + IPackageManager *This, + HSTRING *className); + + HRESULT (STDMETHODCALLTYPE *GetTrustLevel)( + IPackageManager *This, + TrustLevel *trustLevel); + + /*** IPackageManager methods ***/ + HRESULT (STDMETHODCALLTYPE *stub_AddPackageAsync)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_UpdatePackageAsync)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_RemovePackageAsync)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_StagePackageAsync)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_RegisterPackageAsync)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *FindPackages)( + IPackageManager *This, + IIterable **retval); + + HRESULT (STDMETHODCALLTYPE *FindPackagesByUserSecurityId)( + IPackageManager *This, + HSTRING userSecurityId, + IIterable **retval); + + HRESULT (STDMETHODCALLTYPE *stub_FindPackagesByNamePublisher)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_FindPackagesByUserSecurityIdNamePublisher)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_FindUsers)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_SetPackageState)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_FindPackageByPackageFullName)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_CleanupPackageForUserAsync)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_FindPackagesByPackageFamilyName)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_FindPackagesByUserSecurityIdPackageFamilyName)( + IPackageManager *This); + + HRESULT (STDMETHODCALLTYPE *stub_FindPackageByUserSecurityIdPackageFullName)( + IPackageManager *This); + + END_INTERFACE +} IPackageManagerVtbl; + +interface IPackageManager { + CONST_VTBL IPackageManagerVtbl* lpVtbl; +}; + +/*** IUnknown methods ***/ +#define IPackageManager_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IPackageManager_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IPackageManager_Release(This) (This)->lpVtbl->Release(This) +/*** IInspectable methods ***/ +#define IPackageManager_GetIids(This,count,ids) (This)->lpVtbl->GetIids(This,count,ids) +#define IPackageManager_GetRuntimeClassName(This,name) (This)->lpVtbl->GetRuntimeClassName(This,name) +#define IPackageManager_GetTrustLevel(This,level) (This)->lpVtbl->GetTrustLevel(This,level) +/*** IPackageManager methods ***/ +#define IPackageManager_FindPackages(This,retval) (This)->lpVtbl->FindPackages(This,retval) +#define IPackageManager_FindPackagesByUserSecurityId(This,userSecurityId,retval) (This)->lpVtbl->FindPackagesByUserSecurityId(This,userSecurityId,retval) + +/* IPackageId */ +typedef struct IPackageIdVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IPackageId *This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IPackageId *This); + + ULONG (STDMETHODCALLTYPE *Release)( + IPackageId *This); + + /*** IInspectable methods ***/ + HRESULT (STDMETHODCALLTYPE *GetIids)( + IPackageId *This, + UINT32 *count, + IID **ids); + + HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)( + IPackageId *This, + HSTRING *className); + + HRESULT (STDMETHODCALLTYPE *GetTrustLevel)( + IPackageId *This, + TrustLevel *trustLevel); + + /*** IPackageId methods ***/ + HRESULT (STDMETHODCALLTYPE *get_Name)( + IPackageId *This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_Version)( + IPackageId *This, + IPackageVersion *value); + + HRESULT (STDMETHODCALLTYPE *get_Architecture)( + IPackageId *This, + IProcessorArchitecture *value); + + HRESULT (STDMETHODCALLTYPE *get_ResourceId)( + IPackageId *This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_Publisher)( + IPackageId *This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_PublisherId)( + IPackageId *This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_FullName)( + IPackageId *This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_FamilyName)( + IPackageId *This, + HSTRING *value); + + END_INTERFACE +} IPackageIdVtbl; + +interface IPackageId { + CONST_VTBL IPackageIdVtbl* lpVtbl; +}; + +/*** IUnknown methods ***/ +#define IPackageId_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IPackageId_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IPackageId_Release(This) (This)->lpVtbl->Release(This) +/*** IInspectable methods ***/ +#define IPackageId_GetIids(This,count,ids) (This)->lpVtbl->GetIids(This,count,ids) +#define IPackageId_GetRuntimeClassName(This,name) (This)->lpVtbl->GetRuntimeClassName(This,name) +#define IPackageId_GetTrustLevel(This,level) (This)->lpVtbl->GetTrustLevel(This,level) +/*** IPackageId methods ***/ +#define IPackageId_get_Name(This,value) (This)->lpVtbl->get_Name(This,value) +#define IPackageId_get_Version(This,value) (This)->lpVtbl->get_Version(This,value) +#define IPackageId_get_Architecture(This,value) (This)->lpVtbl->get_Architecture(This,value) +#define IPackageId_get_ResourceId(This,value) (This)->lpVtbl->get_ResourceId(This,value) +#define IPackageId_get_Publisher(This,value) (This)->lpVtbl->get_Publisher(This,value) +#define IPackageId_get_PublisherId(This,value) (This)->lpVtbl->get_PublisherId(This,value) +#define IPackageId_get_FullName(This,value) (This)->lpVtbl->get_FullName(This,value) +#define IPackageId_get_FamilyName(This,value) (This)->lpVtbl->get_FamilyName(This,value) + +/* IPackage */ +typedef struct IPackageVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IPackage *This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IPackage *This); + + ULONG (STDMETHODCALLTYPE *Release)( + IPackage *This); + + /*** IInspectable methods ***/ + HRESULT (STDMETHODCALLTYPE *GetIids)( + IPackage *This, + UINT32 *count, + IID **ids); + + HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)( + IPackage *This, + HSTRING *className); + + HRESULT (STDMETHODCALLTYPE *GetTrustLevel)( + IPackage *This, + TrustLevel *trustLevel); + + /*** IPackage methods ***/ + HRESULT (STDMETHODCALLTYPE *get_Id)( + IPackage *This, + IPackageId **value); + + HRESULT (STDMETHODCALLTYPE *get_InstalledLocation)( + IPackage *This, + IUnknown **value); + + HRESULT (STDMETHODCALLTYPE *get_IsFramework)( + IPackage *This, + CHAR *value); + + HRESULT (STDMETHODCALLTYPE *get_Dependencies)( + IPackage *This, + void **value); + + END_INTERFACE +} IPackageVtbl; + +interface IPackage { + CONST_VTBL IPackageVtbl* lpVtbl; +}; + +/*** IUnknown methods ***/ +#define IPackage_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IPackage_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IPackage_Release(This) (This)->lpVtbl->Release(This) +/*** IInspectable methods ***/ +#define IPackage_GetIids(This,count,ids) (This)->lpVtbl->GetIids(This,count,ids) +#define IPackage_GetRuntimeClassName(This,name) (This)->lpVtbl->GetRuntimeClassName(This,name) +#define IPackage_GetTrustLevel(This,level) (This)->lpVtbl->GetTrustLevel(This,level) +/*** IPackage methods ***/ +#define IPackage_get_Id(This,value) (This)->lpVtbl->get_Id(This,value) +#define IPackage_get_InstalledLocation(This,value) (This)->lpVtbl->get_InstalledLocation(This,value) +#define IPackage_get_IsFramework(This,value) (This)->lpVtbl->get_IsFramework(This,value) +#define IPackage_get_Dependencies(This,value) (This)->lpVtbl->get_Dependencies(This,value) diff --git a/gio/gwin32api-storage.h b/gio/gwin32api-storage.h new file mode 100755 index 000000000..716a0a7a1 --- /dev/null +++ b/gio/gwin32api-storage.h @@ -0,0 +1,339 @@ +struct DateTime; + +typedef struct DateTime { + UINT64 UniversalTime; +} DateTime; + +/* The following is copied verbatim from MinGW-w64 windows.storage.h */ +enum StorageItemTypes; +enum FileAttributes; +enum NameCollisionOption; +enum StorageDeleteOption; + +typedef enum NameCollisionoption { + NameCollisionoption_GenerateUniqueName = 0, + NameCollisionoption_ReplaceExisting = 1, + NameCollisionoption_FailIfExists = 2 +} NameCollisionOption; + +typedef enum FileAttributes { + FileAttributes_Normal = 0, + FileAttributes_ReadOnly = 1, + FileAttributes_Directory = 2, + FileAttributes_Archive = 3, + FileAttributes_Temporary = 4 +} FileAttributes; + +typedef enum StorageItemTypes { + StorageItemTypes_None = 0, + StorageItemTypes_File = 1, + StorageItemTypes_Folder = 2 +} StorageItemTypes; + +typedef enum StorageDeleteOption { + StorageDeleteOption_Default = 0, + StorageDeleteOption_PermanentDelete = 1 +} StorageDeleteOption; + +#ifndef __IStorageItem_FWD_DEFINED__ +#define __IStorageItem_FWD_DEFINED__ +typedef interface IStorageItem IStorageItem; +#endif + +/* + * IStorageItem interface + */ +#ifndef __IStorageItem_INTERFACE_DEFINED__ +#define __IStorageItem_INTERFACE_DEFINED__ + +DEFINE_GUID(IID_IStorageItem, 0x4207a996, 0xca2f, 0x42f7, 0xbd,0xe8, 0x8b,0x10,0x45,0x7a,0x7f,0x30); +#if defined(__cplusplus) && !defined(CINTERFACE) +MIDL_INTERFACE("4207a996-ca2f-42f7-bde8-8b10457a7f30") +IStorageItem : public IInspectable +{ + virtual HRESULT STDMETHODCALLTYPE RenameAsyncOverloadDefaultOptions( + HSTRING desiredName, + IInspectable **action) = 0; + + virtual HRESULT STDMETHODCALLTYPE RenameAsync( + HSTRING desiredName, + NameCollisionOption option, + IInspectable **action) = 0; + + virtual HRESULT STDMETHODCALLTYPE DeleteAsyncOverloadDefaultOptions( + IInspectable **action) = 0; + + virtual HRESULT STDMETHODCALLTYPE DeleteAsync( + StorageDeleteOption option, + IInspectable **action) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetBasicPropertiesAsync( + IInspectable **action) = 0; + + virtual HRESULT STDMETHODCALLTYPE get_Name( + HSTRING *value) = 0; + + virtual HRESULT STDMETHODCALLTYPE get_Path( + HSTRING *value) = 0; + + virtual HRESULT STDMETHODCALLTYPE get_Attributes( + FileAttributes *value) = 0; + + virtual HRESULT STDMETHODCALLTYPE get_DateCreated( + DateTime *value) = 0; + + virtual HRESULT STDMETHODCALLTYPE IsOfType( + StorageItemTypes itemType, + boolean *value) = 0; + +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IStorageItem, 0x4207a996, 0xca2f, 0x42f7, 0xbd,0xe8, 0x8b,0x10,0x45,0x7a,0x7f,0x30) +#endif +#else +typedef struct IStorageItemVtbl { + BEGIN_INTERFACE + + /*** IUnknown methods ***/ + HRESULT (STDMETHODCALLTYPE *QueryInterface)( + IStorageItem* This, + REFIID riid, + void **ppvObject); + + ULONG (STDMETHODCALLTYPE *AddRef)( + IStorageItem* This); + + ULONG (STDMETHODCALLTYPE *Release)( + IStorageItem* This); + + /*** IInspectable methods ***/ + HRESULT (STDMETHODCALLTYPE *GetIids)( + IStorageItem* This, + ULONG *iidCount, + IID **iids); + + HRESULT (STDMETHODCALLTYPE *GetRuntimeClassName)( + IStorageItem* This, + HSTRING *className); + + HRESULT (STDMETHODCALLTYPE *GetTrustLevel)( + IStorageItem* This, + TrustLevel *trustLevel); + + /*** IStorageItem methods ***/ + HRESULT (STDMETHODCALLTYPE *RenameAsyncOverloadDefaultOptions)( + IStorageItem* This, + HSTRING desiredName, + IInspectable **action); + + HRESULT (STDMETHODCALLTYPE *RenameAsync)( + IStorageItem* This, + HSTRING desiredName, + NameCollisionOption option, + IInspectable **action); + + HRESULT (STDMETHODCALLTYPE *DeleteAsyncOverloadDefaultOptions)( + IStorageItem* This, + IInspectable **action); + + HRESULT (STDMETHODCALLTYPE *DeleteAsync)( + IStorageItem* This, + StorageDeleteOption option, + IInspectable **action); + + HRESULT (STDMETHODCALLTYPE *GetBasicPropertiesAsync)( + IStorageItem* This, + IInspectable **action); + + HRESULT (STDMETHODCALLTYPE *get_Name)( + IStorageItem* This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_Path)( + IStorageItem* This, + HSTRING *value); + + HRESULT (STDMETHODCALLTYPE *get_Attributes)( + IStorageItem* This, + FileAttributes *value); + + HRESULT (STDMETHODCALLTYPE *get_DateCreated)( + IStorageItem* This, + DateTime *value); + + HRESULT (STDMETHODCALLTYPE *IsOfType)( + IStorageItem* This, + StorageItemTypes itemType, + boolean *value); + + END_INTERFACE +} IStorageItemVtbl; +interface IStorageItem { + CONST_VTBL IStorageItemVtbl* lpVtbl; +}; + +#ifdef COBJMACROS +#ifndef WIDL_C_INLINE_WRAPPERS +/*** IUnknown methods ***/ +#define IStorageItem_QueryInterface(This,riid,ppvObject) (This)->lpVtbl->QueryInterface(This,riid,ppvObject) +#define IStorageItem_AddRef(This) (This)->lpVtbl->AddRef(This) +#define IStorageItem_Release(This) (This)->lpVtbl->Release(This) +/*** IInspectable methods ***/ +#define IStorageItem_GetIids(This,iidCount,iids) (This)->lpVtbl->GetIids(This,iidCount,iids) +#define IStorageItem_GetRuntimeClassName(This,className) (This)->lpVtbl->GetRuntimeClassName(This,className) +#define IStorageItem_GetTrustLevel(This,trustLevel) (This)->lpVtbl->GetTrustLevel(This,trustLevel) +/*** IStorageItem methods ***/ +#define IStorageItem_RenameAsyncOverloadDefaultOptions(This,desiredName,action) (This)->lpVtbl->RenameAsyncOverloadDefaultOptions(This,desiredName,action) +#define IStorageItem_RenameAsync(This,desiredName,option,action) (This)->lpVtbl->RenameAsync(This,desiredName,option,action) +#define IStorageItem_DeleteAsyncOverloadDefaultOptions(This,action) (This)->lpVtbl->DeleteAsyncOverloadDefaultOptions(This,action) +#define IStorageItem_DeleteAsync(This,option,action) (This)->lpVtbl->DeleteAsync(This,option,action) +#define IStorageItem_GetBasicPropertiesAsync(This,action) (This)->lpVtbl->GetBasicPropertiesAsync(This,action) +#define IStorageItem_get_Name(This,value) (This)->lpVtbl->get_Name(This,value) +#define IStorageItem_get_Path(This,value) (This)->lpVtbl->get_Path(This,value) +#define IStorageItem_get_Attributes(This,value) (This)->lpVtbl->get_Attributes(This,value) +#define IStorageItem_get_DateCreated(This,value) (This)->lpVtbl->get_DateCreated(This,value) +#define IStorageItem_IsOfType(This,itemType,value) (This)->lpVtbl->IsOfType(This,itemType,value) +#else +/*** IUnknown methods ***/ +static FORCEINLINE HRESULT IStorageItem_QueryInterface(IStorageItem* This,REFIID riid,void **ppvObject) { + return This->lpVtbl->QueryInterface(This,riid,ppvObject); +} +static FORCEINLINE ULONG IStorageItem_AddRef(IStorageItem* This) { + return This->lpVtbl->AddRef(This); +} +static FORCEINLINE ULONG IStorageItem_Release(IStorageItem* This) { + return This->lpVtbl->Release(This); +} +/*** IInspectable methods ***/ +static FORCEINLINE HRESULT IStorageItem_GetIids(IStorageItem* This,ULONG *iidCount,IID **iids) { + return This->lpVtbl->GetIids(This,iidCount,iids); +} +static FORCEINLINE HRESULT IStorageItem_GetRuntimeClassName(IStorageItem* This,HSTRING *className) { + return This->lpVtbl->GetRuntimeClassName(This,className); +} +static FORCEINLINE HRESULT IStorageItem_GetTrustLevel(IStorageItem* This,TrustLevel *trustLevel) { + return This->lpVtbl->GetTrustLevel(This,trustLevel); +} +/*** IStorageItem methods ***/ +static FORCEINLINE HRESULT IStorageItem_RenameAsyncOverloadDefaultOptions(IStorageItem* This,HSTRING desiredName,IInspectable **action) { + return This->lpVtbl->RenameAsyncOverloadDefaultOptions(This,desiredName,action); +} +static FORCEINLINE HRESULT IStorageItem_RenameAsync(IStorageItem* This,HSTRING desiredName,NameCollisionOption option,IInspectable **action) { + return This->lpVtbl->RenameAsync(This,desiredName,option,action); +} +static FORCEINLINE HRESULT IStorageItem_DeleteAsyncOverloadDefaultOptions(IStorageItem* This,IInspectable **action) { + return This->lpVtbl->DeleteAsyncOverloadDefaultOptions(This,action); +} +static FORCEINLINE HRESULT IStorageItem_DeleteAsync(IStorageItem* This,StorageDeleteOption option,IInspectable **action) { + return This->lpVtbl->DeleteAsync(This,option,action); +} +static FORCEINLINE HRESULT IStorageItem_GetBasicPropertiesAsync(IStorageItem* This,IInspectable **action) { + return This->lpVtbl->GetBasicPropertiesAsync(This,action); +} +static FORCEINLINE HRESULT IStorageItem_get_Name(IStorageItem* This,HSTRING *value) { + return This->lpVtbl->get_Name(This,value); +} +static FORCEINLINE HRESULT IStorageItem_get_Path(IStorageItem* This,HSTRING *value) { + return This->lpVtbl->get_Path(This,value); +} +static FORCEINLINE HRESULT IStorageItem_get_Attributes(IStorageItem* This,FileAttributes *value) { + return This->lpVtbl->get_Attributes(This,value); +} +static FORCEINLINE HRESULT IStorageItem_get_DateCreated(IStorageItem* This,DateTime *value) { + return This->lpVtbl->get_DateCreated(This,value); +} +static FORCEINLINE HRESULT IStorageItem_IsOfType(IStorageItem* This,StorageItemTypes itemType,boolean *value) { + return This->lpVtbl->IsOfType(This,itemType,value); +} +#endif +#endif + +#endif + +HRESULT STDMETHODCALLTYPE IStorageItem_RenameAsyncOverloadDefaultOptions_Proxy( + IStorageItem* This, + HSTRING desiredName, + IInspectable **action); +void __RPC_STUB IStorageItem_RenameAsyncOverloadDefaultOptions_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_RenameAsync_Proxy( + IStorageItem* This, + HSTRING desiredName, + NameCollisionOption option, + IInspectable **action); +void __RPC_STUB IStorageItem_RenameAsync_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_DeleteAsyncOverloadDefaultOptions_Proxy( + IStorageItem* This, + IInspectable **action); +void __RPC_STUB IStorageItem_DeleteAsyncOverloadDefaultOptions_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_DeleteAsync_Proxy( + IStorageItem* This, + StorageDeleteOption option, + IInspectable **action); +void __RPC_STUB IStorageItem_DeleteAsync_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_GetBasicPropertiesAsync_Proxy( + IStorageItem* This, + IInspectable **action); +void __RPC_STUB IStorageItem_GetBasicPropertiesAsync_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_get_Name_Proxy( + IStorageItem* This, + HSTRING *value); +void __RPC_STUB IStorageItem_get_Name_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_get_Path_Proxy( + IStorageItem* This, + HSTRING *value); +void __RPC_STUB IStorageItem_get_Path_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_get_Attributes_Proxy( + IStorageItem* This, + FileAttributes *value); +void __RPC_STUB IStorageItem_get_Attributes_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_get_DateCreated_Proxy( + IStorageItem* This, + DateTime *value); +void __RPC_STUB IStorageItem_get_DateCreated_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); +HRESULT STDMETHODCALLTYPE IStorageItem_IsOfType_Proxy( + IStorageItem* This, + StorageItemTypes itemType, + boolean *value); +void __RPC_STUB IStorageItem_IsOfType_Stub( + IRpcStubBuffer* This, + IRpcChannelBuffer* pRpcChannelBuffer, + PRPC_MESSAGE pRpcMessage, + DWORD* pdwStubPhase); + +#endif /* __IStorageItem_INTERFACE_DEFINED__ */ diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c index 8871acc9d..b12f9e092 100644 --- a/gio/gwin32appinfo.c +++ b/gio/gwin32appinfo.c @@ -22,6 +22,8 @@ #include "config.h" +#define COBJMACROS + #include <string.h> #include "gcontenttype.h" @@ -32,8 +34,16 @@ #include <glib/gstdio.h> #include "glibintl.h" #include <gio/gwin32registrykey.h> +#include <shlobj.h> +/* Contains the definitions from shlobj.h that are + * guarded as Windows8-or-newer and are unavailable + * to GLib, being only Windows7-or-newer. + */ +#include "gwin32api-application-activation-manager.h" #include <windows.h> +/* For SHLoadIndirectString() */ +#include <shlwapi.h> #include <glib/gstdioprivate.h> #include "giowin32-priv.h" @@ -181,7 +191,10 @@ struct _GWin32AppInfoHandler { /* Usually a class name in HKCR */ gunichar2 *handler_id; - /* Registry object obtained by opening @handler_id. Can be used to watch this handler. */ + /* Registry object obtained by opening @handler_id. + * Can be used to watch this handler. + * May be %NULL (for fake handlers that we made up). + */ GWin32RegistryKey *key; /* @handler_id, in UTF-8, folded */ @@ -192,6 +205,13 @@ struct _GWin32AppInfoHandler { /* Verbs that this handler supports */ GPtrArray *verbs; /* of GWin32AppInfoShellVerb */ + + /* AppUserModelID for a UWP application. When this is not NULL, + * this handler launches a UWP application. + * UWP applications are launched using a COM interface and have no commandlines, + * and the verbs will reflect that too. + */ + gunichar2 *uwp_aumid; }; struct _GWin32AppInfoShellVerb { @@ -203,6 +223,11 @@ struct _GWin32AppInfoShellVerb { /* User-friendly (localized) verb name. */ gchar *verb_displayname; + /* %TRUE if this verb is for a UWP app. + * It means that @command, @executable and @dll_function are %NULL. + */ + gboolean is_uwp; + /* shell/verb/command */ gunichar2 *command; @@ -255,6 +280,7 @@ struct _GWin32AppInfoApplication { * key path for the application. * For applications tracked by executable name this is the * basename of the executable. + * For UWP apps this is the AppUserModelID. * 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 @@ -320,6 +346,9 @@ struct _GWin32AppInfoApplication { /* Set to TRUE for applications that are machine-wide defaults (i.e. default * browser) */ gboolean default_app; + + /* Set to TRUE for UWP applications */ + gboolean is_uwp; }; #define G_TYPE_WIN32_APPINFO_URL_SCHEMA (g_win32_appinfo_url_schema_get_type ()) @@ -373,6 +402,7 @@ g_win32_appinfo_handler_dispose (GObject *object) g_clear_object (&handler->key); g_clear_object (&handler->icon); g_clear_pointer (&handler->verbs, g_ptr_array_unref); + g_clear_pointer (&handler->uwp_aumid, g_free); G_OBJECT_CLASS (g_win32_appinfo_handler_parent_class)->dispose (object); } @@ -576,6 +606,11 @@ static GHashTable *fake_apps = NULL; */ static GHashTable *handlers = NULL; +/* Temporary (only exists while the registry is being scanned) table + * that maps GWin32RegistryKey objects (keeps a ref) to owned AUMId wchar strings. + */ +static GHashTable *uwp_handler_table = NULL; + /* Watch this whole subtree */ static GWin32RegistryKey *url_associations_key; @@ -616,6 +651,9 @@ static GWin32RegistryKey *classes_root_key; */ #include "giowin32-private.c" +/* for g_win32_package_parser_enum_packages() */ +#include "gwin32packageparser.h" + static void read_handler_icon (GWin32RegistryKey *key, GIcon **icon_out) @@ -642,6 +680,12 @@ read_handler_icon (GWin32RegistryKey *key, NULL, NULL)) { + /* TODO: For UWP handlers this string is usually in @{...} form, + * see grab_registry_string() below. Right now this + * string is read as-is and the icon would silently fail to load. + * Also, right now handler icon is not used anywhere + * (only app icon is used). + */ if (default_type == G_WIN32_REGISTRY_VALUE_STR && default_value[0] != '\0') *icon_out = g_themed_icon_new (default_value); @@ -689,18 +733,18 @@ compare_verbs (gconstpointer a, if (def != NULL) { if (_wcsicmp (ca->name, def) == 0) - return 1; - else if (_wcsicmp (cb->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; + else if (is_open_ca && !is_open_cb) + return 1; return _wcsicmp (ca->name, cb->name); } @@ -735,7 +779,8 @@ typedef void (*verb_command_func) (gpointer handler_data1, 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); + gchar **return_handler_id_u8_folded, + gunichar2 **return_uwp_aumid); static GWin32AppInfoURLSchema * get_schema_object (const gunichar2 *schema, const gchar *schema_u8, @@ -743,7 +788,8 @@ static GWin32AppInfoURLSchema * get_schema_object (const gunichar2 *sche static GWin32AppInfoHandler * get_handler_object (const gchar *handler_id_u8_folded, GWin32RegistryKey *handler_key, - const gunichar2 *handler_id); + const gunichar2 *handler_id, + const gunichar2 *uwp_aumid); static GWin32AppInfoFileExtension *get_ext_object (const gunichar2 *ext, const gchar *ext_u8, @@ -768,6 +814,20 @@ static void handler_add_verb (gpointer ha gboolean verb_is_preferred, gboolean invent_new_verb_name); +static void process_uwp_verbs (GList *verbs, + const reg_verb *preferred_verb, + const gunichar2 *path_to_progid, + const gunichar2 *progid, + gboolean autoprefer_first_verb, + GWin32AppInfoHandler *handler_rec, + GWin32AppInfoApplication *app); + +static void uwp_handler_add_verb (GWin32AppInfoHandler *handler_rec, + GWin32AppInfoApplication *app, + const gunichar2 *verb, + const gchar *verb_displayname, + gboolean verb_is_preferred); + /* output_size is in *bytes*, not gunichar2s! */ static gboolean build_registry_path (gunichar2 *output, gsize output_size, ...) @@ -858,6 +918,12 @@ _g_win32_registry_key_build_and_new_w (GError **error, ...) * @verbshell_prefix is the subkey of @program_id_key * that contains the verbs. It is "Shell" initially, * but grows with recursive invocations (for subcommands). + * @is_uwp points to a boolean, which + * indicates whether the function is being called for a UWP app. + * It might be switched from %TRUE to %FALSE on return, + * if the application turns out to not to be UWP on closer inspection. + * If the application is already known not to be UWP before the + * call, this pointer can be %NULL instead. * Returns TRUE on success, FALSE on failure. */ static gboolean @@ -865,7 +931,8 @@ get_verbs (GWin32RegistryKey *program_id_key, const reg_verb **preferred_verb, GList **verbs, const gunichar2 *verbname_prefix, - const gunichar2 *verbshell_prefix) + const gunichar2 *verbshell_prefix, + gboolean *is_uwp) { GWin32RegistrySubkeyIter iter; GWin32RegistryKey *key; @@ -930,7 +997,8 @@ get_verbs (GWin32RegistryKey *program_id_key, * Essentially, we're flattening the command tree into a list. */ has_subcommands = FALSE; - if (g_win32_registry_key_get_value_w (subkey, + if ((is_uwp == NULL || !(*is_uwp)) && /* Assume UWP apps don't have subcommands */ + g_win32_registry_key_get_value_w (subkey, NULL, TRUE, L"Subcommands", @@ -940,8 +1008,9 @@ get_verbs (GWin32RegistryKey *program_id_key, 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)); + gboolean dummy = FALSE; + gunichar2 *new_nameprefix = g_new (gunichar2, verbname_prefix_len + name_len + 1 + 1); + gunichar2 *new_shellprefix = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1 + shell_len + 1); 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)); @@ -953,16 +1022,38 @@ get_verbs (GWin32RegistryKey *program_id_key, 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); + has_subcommands = get_verbs (program_id_key, &tmp, verbs, new_nameprefix, new_shellprefix, &dummy); g_free (new_shellprefix); g_free (new_nameprefix); } - g_clear_object (&subkey); - /* Presence of subcommands means that this key itself is not a command-key */ if (has_subcommands) - continue; + { + g_clear_object (&subkey); + continue; + } + + if (is_uwp != NULL && *is_uwp && + !g_win32_registry_key_get_value_w (subkey, + NULL, + TRUE, + L"ActivatableClassId", + &subc_type, + NULL, + NULL, + NULL)) + { + /* We expected a UWP app, but it lacks ActivatableClassId + * on a verb, which means that it does not behave like + * a UWP app should (msedge being an example - it's UWP, + * but has its own launchable exe file and a simple ID), + * so we have to treat it like a normal app. + */ + *is_uwp = FALSE; + } + + g_clear_object (&subkey); /* 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(). @@ -973,11 +1064,11 @@ get_verbs (GWin32RegistryKey *program_id_key, * 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)); + rverb->name = g_new (gunichar2, verbname_prefix_len + name_len + 1); 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)); + rverb->shellpath = g_new (gunichar2, verbshell_prefix_len + 1 + name_len + 1); 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)); @@ -1049,31 +1140,41 @@ get_url_association (const gunichar2 *program_id, const reg_verb *preferred_verb; gchar *handler_id_u8; gchar *handler_id_u8_folded; + gunichar2 *uwp_aumid; + gboolean is_uwp; GWin32RegistryKey *handler_key; if ((handler_id = decide_which_id_to_use (program_id, &handler_key, &handler_id_u8, - &handler_id_u8_folded)) == NULL) + &handler_id_u8_folded, + &uwp_aumid)) == NULL) return; - if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell")) + is_uwp = uwp_aumid != NULL; + + if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp)) { 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 (&uwp_aumid, g_free); return; } + if (!is_uwp && uwp_aumid != NULL) + g_clear_pointer (&uwp_aumid, g_free); + schema_rec = get_schema_object (schema, schema_u8, schema_u8_folded); handler_rec = get_handler_object (handler_id_u8_folded, handler_key, - handler_id); + handler_id, + uwp_aumid); if (is_user_choice || schema_rec->chosen_handler == NULL) g_set_object (&schema_rec->chosen_handler, handler_rec); @@ -1089,18 +1190,29 @@ get_url_association (const gunichar2 *program_id, 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); + if (uwp_aumid == NULL) + process_verbs_commands (g_steal_pointer (&verbs), + preferred_verb, + HKCR, + handler_id, + TRUE, + handler_add_verb, + handler_rec, + app); + else + process_uwp_verbs (g_steal_pointer (&verbs), + preferred_verb, + HKCR, + handler_id, + TRUE, + 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); + g_clear_pointer (&uwp_aumid, g_free); } /* Grabs a file extension association (from HKCR\.ext or similar). @@ -1121,6 +1233,8 @@ get_file_ext (const gunichar2 *program_id, GList *verbs; gchar *handler_id_u8; gchar *handler_id_u8_folded; + gunichar2 *uwp_aumid; + gboolean is_uwp; GWin32RegistryKey *handler_key; GWin32AppInfoFileExtension *file_extn; gchar *file_extension_u8; @@ -1129,7 +1243,8 @@ get_file_ext (const gunichar2 *program_id, if ((handler_id = decide_which_id_to_use (program_id, &handler_key, &handler_id_u8, - &handler_id_u8_folded)) == NULL) + &handler_id_u8_folded, + &uwp_aumid)) == NULL) return; if (!g_utf16_to_utf8_and_fold (file_extension, @@ -1140,12 +1255,15 @@ get_file_ext (const gunichar2 *program_id, 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_pointer (&uwp_aumid, g_free); g_clear_object (&handler_key); return; } - if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell")) + is_uwp = uwp_aumid != NULL; + + if (!get_verbs (handler_key, &preferred_verb, &verbs, L"", L"Shell", &is_uwp)) { g_clear_pointer (&handler_id, g_free); g_clear_pointer (&handler_id_u8, g_free); @@ -1153,15 +1271,20 @@ get_file_ext (const gunichar2 *program_id, g_clear_object (&handler_key); g_clear_pointer (&file_extension_u8, g_free); g_clear_pointer (&file_extension_u8_folded, g_free); + g_clear_pointer (&uwp_aumid, g_free); return; } + if (!is_uwp && uwp_aumid != NULL) + g_clear_pointer (&uwp_aumid, g_free); + file_extn = get_ext_object (file_extension, file_extension_u8, file_extension_u8_folded); handler_rec = get_handler_object (handler_id_u8_folded, handler_key, - handler_id); + handler_id, + uwp_aumid); if (is_user_choice || file_extn->chosen_handler == NULL) g_set_object (&file_extn->chosen_handler, handler_rec); @@ -1179,18 +1302,28 @@ get_file_ext (const gunichar2 *program_id, 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); + if (uwp_aumid == NULL) + process_verbs_commands (g_steal_pointer (&verbs), + preferred_verb, + HKCR, + handler_id, + TRUE, + handler_add_verb, + handler_rec, + app); + else + process_uwp_verbs (g_steal_pointer (&verbs), + preferred_verb, + HKCR, + handler_id, + TRUE, + 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); + g_clear_pointer (&uwp_aumid, g_free); } /* Returns either a @program_id or the string from @@ -1205,12 +1338,15 @@ 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) + gchar **return_handler_id_u8_folded, + gunichar2 **return_uwp_aumid) { GWin32RegistryKey *key; + GWin32RegistryKey *uwp_key; GWin32RegistryValueType val_type; gunichar2 *proxy_id; gunichar2 *return_id; + gunichar2 *uwp_aumid; gboolean got_value; gchar *handler_id_u8; gchar *handler_id_u8_folded; @@ -1219,23 +1355,61 @@ decide_which_id_to_use (const gunichar2 *program_id, if (return_key) *return_key = NULL; - /* Check the proxy first */ + if (return_uwp_aumid) + *return_uwp_aumid = NULL; + key = g_win32_registry_key_get_child_w (classes_root_key, program_id, NULL); if (key == NULL) return NULL; + /* Check for UWP first */ + uwp_aumid = NULL; + uwp_key = g_win32_registry_key_get_child_w (key, L"Application", NULL); + + if (uwp_key != NULL) + { + got_value = g_win32_registry_key_get_value_w (uwp_key, + NULL, + TRUE, + L"AppUserModelID", + &val_type, + (void **) &uwp_aumid, + NULL, + NULL); + if (got_value && val_type != G_WIN32_REGISTRY_VALUE_STR) + g_clear_pointer (&uwp_aumid, g_free); + + /* Other values in the Application key contain useful information + * (description, name, icon), but it's inconvenient to read + * it here (we don't have an app object *yet*). Store the key + * in a table instead, and look at it later. + */ + if (uwp_aumid == NULL) + g_debug ("ProgramID %S looks like a UWP application, but isn't", + program_id); + else + g_hash_table_insert (uwp_handler_table, g_object_ref (uwp_key), g_wcsdup (uwp_aumid, -1)); + + g_object_unref (uwp_key); + } + + /* Then check for proxy */ 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); + + if (uwp_aumid == 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; @@ -1273,8 +1447,13 @@ decide_which_id_to_use (const gunichar2 *program_id, if (return_handler_id_u8) *return_handler_id_u8 = g_steal_pointer (&handler_id_u8); + g_clear_pointer (&handler_id_u8, g_free); if (return_handler_id_u8_folded) *return_handler_id_u8_folded = g_steal_pointer (&handler_id_u8_folded); + g_clear_pointer (&handler_id_u8_folded, g_free); + if (return_uwp_aumid) + *return_uwp_aumid = g_steal_pointer (&uwp_aumid); + g_clear_pointer (&uwp_aumid, g_free); if (return_id == NULL && return_key) *return_key = g_steal_pointer (&key); @@ -1418,6 +1597,75 @@ process_verbs_commands (GList *verbs, g_list_free_full (verbs, reg_verb_free); } +static void +process_uwp_verbs (GList *verbs, + const reg_verb *preferred_verb, + const gunichar2 *path_to_progid, + const gunichar2 *progid, + gboolean autoprefer_first_verb, + GWin32AppInfoHandler *handler_rec, + GWin32AppInfoApplication *app) +{ + GList *i; + + g_assert (verbs != NULL); + + for (i = verbs; i; i = i->next) + { + const reg_verb *verb = (const reg_verb *) i->data; + GWin32RegistryKey *key; + gboolean got_value; + GWin32RegistryValueType val_type; + gunichar2 *acid; + gsize acid_len; + + key = _g_win32_registry_key_build_and_new_w (NULL, path_to_progid, progid, + L"\\", verb->shellpath, NULL); + + if (key == NULL) + { + g_debug ("%S%S\\%S does not exist", + path_to_progid, progid, verb->shellpath); + continue; + } + + got_value = g_win32_registry_key_get_value_w (key, + g_win32_registry_get_os_dirs_w (), + TRUE, + L"ActivatableClassId", + &val_type, + (void **) &acid, + &acid_len, + NULL); + + if (got_value && + val_type == G_WIN32_REGISTRY_VALUE_STR && + acid_len > sizeof (gunichar2)) + { + /* TODO: default value of a shell subkey, if not empty, + * migh contain something like @{Some.Identifier_1234.456.678.789_some_words?ms-resource://Arbitrary.Path/Pointing/Somewhere} + * and it might be possible to turn it into a nice displayname. + */ + uwp_handler_add_verb (handler_rec, + app, + verb->name, + NULL, + (preferred_verb && _wcsicmp (verb->name, preferred_verb->name) == 0) || + (!preferred_verb && autoprefer_first_verb && i == verbs)); + } + else + { + g_debug ("%S%S\\%S does not have an ActivatableClassId string value", + path_to_progid, progid, verb->shellpath); + } + + g_clear_pointer (&acid, g_free); + g_clear_object (&key); + } + + g_list_free_full (verbs, reg_verb_free); +} + /* Looks up a schema object identified by * @schema_u8_folded in the urls hash table. * If such object doesn't exist, @@ -1454,7 +1702,8 @@ get_schema_object (const gunichar2 *schema, static GWin32AppInfoHandler * get_handler_object (const gchar *handler_id_u8_folded, GWin32RegistryKey *handler_key, - const gunichar2 *handler_id) + const gunichar2 *handler_id, + const gunichar2 *uwp_aumid) { GWin32AppInfoHandler *handler_rec; @@ -1464,10 +1713,14 @@ get_handler_object (const gchar *handler_id_u8_folded, return handler_rec; handler_rec = g_object_new (G_TYPE_WIN32_APPINFO_HANDLER, NULL); - handler_rec->key = g_object_ref (handler_key); + if (handler_key) + 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); + if (uwp_aumid) + handler_rec->uwp_aumid = g_wcsdup (uwp_aumid, -1); + if (handler_key) + read_handler_icon (handler_key, &handler_rec->icon); g_hash_table_insert (handlers, g_strdup (handler_id_u8_folded), handler_rec); return handler_rec; @@ -1497,6 +1750,7 @@ handler_add_verb (gpointer handler_data1, shverb->verb_displayname = g_strdup (verb_displayname); shverb->command = g_wcsdup (command_line, -1); shverb->command_utf8 = g_strdup (command_line_utf8); + shverb->is_uwp = FALSE; /* This function is for non-UWP verbs only */ if (app_rec) shverb->app = g_object_ref (app_rec); @@ -1533,7 +1787,7 @@ generate_new_verb_name (GPtrArray *verbs, 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)); + gunichar2 *new_verb_name = g_new (gunichar2, new_verb_name_len); *new_verb = NULL; *new_displayname = NULL; @@ -1636,6 +1890,75 @@ app_add_verb (gpointer handler_data1, g_ptr_array_insert (app_rec->verbs, 0, shverb); } +static void +uwp_app_add_verb (GWin32AppInfoApplication *app_rec, + const gunichar2 *verb, + const gchar *verb_displayname) +{ + GWin32AppInfoShellVerb *shverb; + + _verb_lookup (app_rec->verbs, verb, &shverb); + + if (shverb != NULL) + return; + + shverb = g_object_new (G_TYPE_WIN32_APPINFO_SHELL_VERB, NULL); + shverb->verb_name = g_wcsdup (verb, -1); + shverb->app = g_object_ref (app_rec); + shverb->verb_displayname = g_strdup (verb_displayname); + + shverb->is_uwp = TRUE; + + /* Strictly speaking, this is unnecessary, but + * let's make it clear that UWP verbs have no + * commands and executables. + */ + shverb->command = NULL; + shverb->command_utf8 = NULL; + shverb->executable = NULL; + shverb->executable_basename = NULL; + shverb->executable_folded = NULL; + shverb->dll_function = NULL; + + g_ptr_array_add (app_rec->verbs, shverb); +} + +static void +uwp_handler_add_verb (GWin32AppInfoHandler *handler_rec, + GWin32AppInfoApplication *app, + const gunichar2 *verb, + const gchar *verb_displayname, + gboolean verb_is_preferred) +{ + GWin32AppInfoShellVerb *shverb; + + _verb_lookup (handler_rec->verbs, verb, &shverb); + + 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->is_uwp = TRUE; + + if (app) + shverb->app = g_object_ref (app); + + shverb->command = NULL; + shverb->command_utf8 = NULL; + shverb->executable = NULL; + shverb->executable_basename = NULL; + shverb->executable_folded = NULL; + shverb->dll_function = NULL; + + if (!verb_is_preferred) + g_ptr_array_add (handler_rec->verbs, shverb); + else + g_ptr_array_insert (handler_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, @@ -1899,7 +2222,8 @@ get_app_object (GHashTable *app_hashmap, const gchar *canonical_name_u8, const gchar *canonical_name_folded, gboolean user_specific, - gboolean default_app) + gboolean default_app, + gboolean is_uwp) { GWin32AppInfoApplication *app; @@ -1915,6 +2239,7 @@ get_app_object (GHashTable *app_hashmap, app->no_open_with = FALSE; app->user_specific = user_specific; app->default_app = default_app; + app->is_uwp = is_uwp; g_hash_table_insert (app_hashmap, g_strdup (canonical_name_folded), app); @@ -1950,6 +2275,7 @@ read_capable_app (const gunichar2 *app_key_path, GWin32RegistryKey *associations; const reg_verb *preferred_verb; GList *verbs = NULL; + gboolean verbs_in_root_key = TRUE; appkey = NULL; capabilities = NULL; @@ -1964,11 +2290,14 @@ read_capable_app (const gunichar2 *app_key_path, &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")) + !(get_verbs (appkey, &preferred_verb, &verbs, L"", L"Shell", NULL) || + (verbs_in_root_key = FALSE) || + get_verbs (capabilities, &preferred_verb, &verbs, L"", L"Shell", NULL))) { g_clear_pointer (&canonical_name_u8, g_free); g_clear_pointer (&canonical_name_folded, g_free); g_clear_object (&appkey); + g_clear_object (&capabilities); g_clear_pointer (&app_key_path_u8, g_free); g_clear_pointer (&app_key_path_u8_folded, g_free); @@ -1980,12 +2309,13 @@ read_capable_app (const gunichar2 *app_key_path, canonical_name_u8, canonical_name_folded, user_specific, - default_app); + default_app, + FALSE); 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), + verbs_in_root_key ? app_key_path : g_win32_registry_key_get_path_w (capabilities), FALSE, app_add_verb, app, @@ -2328,7 +2658,7 @@ read_incapable_app (GWin32RegistryKey *incapable_app, GIcon *icon = NULL; GWin32RegistryKey *supported_key; - if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell")) + if (!get_verbs (incapable_app, &preferred_verb, &verbs, L"", L"Shell", NULL)) return; app = get_app_object (apps_by_exe, @@ -2336,6 +2666,7 @@ read_incapable_app (GWin32RegistryKey *incapable_app, app_exe_basename_u8, app_exe_basename_u8_folded, FALSE, + FALSE, FALSE); process_verbs_commands (g_steal_pointer (&verbs), @@ -2743,6 +3074,9 @@ link_handlers_to_unregistered_apps (void) { gsize vi; + if (handler->uwp_aumid != NULL) + continue; + for (vi = 0; vi < handler->verbs->len; vi++) { GWin32AppInfoShellVerb *handler_verb; @@ -2770,6 +3104,9 @@ link_handlers_to_unregistered_apps (void) GWin32AppInfoShellVerb *app_verb; gsize ai; + if (app->is_uwp) + continue; + for (ai = 0; ai < app->verbs->len; ai++) { GWin32PrivateStat app_verb_exec_info; @@ -2820,6 +3157,9 @@ link_handlers_to_unregistered_apps (void) (gpointer *) &appexe_fld_basename, (gpointer *) &app)) { + if (app->is_uwp) + continue; + /* Use basename because apps_by_exe only has basenames */ if (g_strcmp0 (handler_exe_basename, appexe_fld_basename) != 0) continue; @@ -2866,6 +3206,9 @@ link_handlers_to_fake_apps (void) { gsize vi; + if (handler->uwp_aumid != NULL) + continue; + for (vi = 0; vi < handler->verbs->len; vi++) { GWin32AppInfoShellVerb *handler_verb; @@ -2885,6 +3228,7 @@ link_handlers_to_fake_apps (void) handler_verb->executable, handler_verb->executable_folded, FALSE, + FALSE, FALSE); g_clear_pointer (&exename_utf16, g_free); handler_verb->app = g_object_ref (app); @@ -2916,6 +3260,9 @@ link_handlers_to_fake_apps (void) { gsize vi; + if (handler->uwp_aumid != NULL) + continue; + for (vi = 0; vi < handler->verbs->len; vi++) { GWin32AppInfoShellVerb *handler_verb; @@ -2932,6 +3279,7 @@ link_handlers_to_fake_apps (void) handler_verb->command_utf8, command_utf8_folded, FALSE, + FALSE, FALSE); g_clear_pointer (&command_utf8_folded, g_free); handler_verb->app = g_object_ref (app); @@ -2952,6 +3300,404 @@ link_handlers_to_fake_apps (void) } } +static GWin32AppInfoHandler * +find_uwp_handler_for_ext (GWin32AppInfoFileExtension *file_extn, + const gunichar2 *app_user_model_id) +{ + GHashTableIter handler_iter; + gchar *handler_id_fld; + GWin32AppInfoHandler *handler; + + 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 (handler->uwp_aumid == NULL) + continue; + + if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0) + return handler; + } + + return NULL; +} + +static GWin32AppInfoHandler * +find_uwp_handler_for_schema (GWin32AppInfoURLSchema *schema, + const gunichar2 *app_user_model_id) +{ + GHashTableIter handler_iter; + gchar *handler_id_fld; + GWin32AppInfoHandler *handler; + + g_hash_table_iter_init (&handler_iter, schema->handlers); + while (g_hash_table_iter_next (&handler_iter, + (gpointer *) &handler_id_fld, + (gpointer *) &handler)) + { + if (handler->uwp_aumid == NULL) + continue; + + if (_wcsicmp (handler->uwp_aumid, app_user_model_id) == 0) + return handler; + } + + return NULL; +} + +static gboolean +uwp_package_cb (gpointer user_data, + const gunichar2 *full_package_name, + const gunichar2 *package_name, + const gunichar2 *app_user_model_id, + gboolean show_in_applist, + GPtrArray *supported_extgroups, + GPtrArray *supported_protocols) +{ + gint i, i_verb, i_ext; + gint extensions_considered; + GWin32AppInfoApplication *app; + gchar *app_user_model_id_u8; + gchar *app_user_model_id_u8_folded; + GHashTableIter iter; + GWin32AppInfoHandler *ext; + GWin32AppInfoHandler *url; + + if (!g_utf16_to_utf8_and_fold (app_user_model_id, + -1, + &app_user_model_id_u8, + &app_user_model_id_u8_folded)) + return TRUE; + + app = get_app_object (apps_by_id, + app_user_model_id, + app_user_model_id_u8, + app_user_model_id_u8_folded, + TRUE, + FALSE, + TRUE); + + extensions_considered = 0; + + for (i = 0; i < supported_extgroups->len; i++) + { + GWin32PackageExtGroup *grp = (GWin32PackageExtGroup *) g_ptr_array_index (supported_extgroups, i); + + extensions_considered += grp->extensions->len; + + for (i_ext = 0; i_ext < grp->extensions->len; i_ext++) + { + wchar_t *ext = (wchar_t *) g_ptr_array_index (grp->extensions, i_ext); + gchar *ext_u8; + gchar *ext_u8_folded; + GWin32AppInfoFileExtension *file_extn; + GWin32AppInfoHandler *handler_rec; + + if (!g_utf16_to_utf8_and_fold (ext, + -1, + &ext_u8, + &ext_u8_folded)) + continue; + + file_extn = get_ext_object (ext, ext_u8, ext_u8_folded); + g_free (ext_u8); + handler_rec = find_uwp_handler_for_ext (file_extn, app_user_model_id); + + if (handler_rec == NULL) + { + /* Use AppUserModelId as the ID of the new fake handler */ + handler_rec = get_handler_object (app_user_model_id_u8_folded, + NULL, + app_user_model_id, + app_user_model_id); + g_hash_table_insert (file_extn->handlers, + g_strdup (app_user_model_id_u8_folded), + g_object_ref (handler_rec)); + } + + if (file_extn->chosen_handler == NULL) + g_set_object (&file_extn->chosen_handler, handler_rec); + + /* This is somewhat wasteful, but for 100% correct handling + * we need to remember which extensions (handlers) support + * which verbs, and each handler gets its own copy of the + * verb object, since our design is handler-centric, + * not verb-centric. The app also gets a list of verbs, + * but without handlers it would have no idea which + * verbs can be used with which extensions. + */ + for (i_verb = 0; i_verb < grp->verbs->len; i_verb++) + { + wchar_t *verb = NULL; + + verb = (wchar_t *) g_ptr_array_index (grp->verbs, i_verb); + /* *_add_verb() functions are no-ops when a verb already exists, + * so we're free to call them as many times as we want. + */ + uwp_handler_add_verb (handler_rec, + app, + verb, + NULL, + FALSE); + } + + g_hash_table_insert (app->supported_exts, + g_steal_pointer (&ext_u8_folded), + g_object_ref (handler_rec)); + } + } + + g_hash_table_iter_init (&iter, app->supported_exts); + + /* Pile up all handler verbs into the app too, + * for cases when we don't have a ref to a handler. + */ + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &ext)) + { + gint i_hverb; + + if (!ext) + continue; + + for (i_hverb = 0; i_hverb < ext->verbs->len; i_hverb++) + { + GWin32AppInfoShellVerb *handler_verb; + + handler_verb = _verb_idx (ext->verbs, i_hverb); + uwp_app_add_verb (app, handler_verb->verb_name, handler_verb->verb_displayname); + if (handler_verb->app == NULL && handler_verb->is_uwp) + handler_verb->app = g_object_ref (app); + } + } + + if (app->verbs->len == 0 && extensions_considered > 0) + g_warning ("Unexpectedly, UWP app `%S' (AUMId `%s') supports %d extensions but has no verbs", + full_package_name, app_user_model_id_u8, extensions_considered); + + for (i = 0; i < supported_protocols->len; i++) + { + wchar_t *proto = (wchar_t *) g_ptr_array_index (supported_protocols, i); + gchar *proto_u8; + gchar *proto_u8_folded; + GWin32AppInfoURLSchema *schema_rec; + GWin32AppInfoHandler *handler_rec; + + if (!g_utf16_to_utf8_and_fold (proto, + -1, + &proto_u8, + &proto_u8_folded)) + continue; + + schema_rec = get_schema_object (proto, + proto_u8, + proto_u8_folded); + + g_free (proto_u8); + + handler_rec = find_uwp_handler_for_schema (schema_rec, app_user_model_id); + + if (handler_rec == NULL) + { + /* Use AppUserModelId as the ID of the new fake handler */ + handler_rec = get_handler_object (app_user_model_id_u8_folded, + NULL, + app_user_model_id, + app_user_model_id); + + g_hash_table_insert (schema_rec->handlers, + g_strdup (app_user_model_id_u8_folded), + g_object_ref (handler_rec)); + } + + if (schema_rec->chosen_handler == NULL) + g_set_object (&schema_rec->chosen_handler, handler_rec); + + /* Technically, UWP apps don't use verbs for URIs, + * but we only store an app field in verbs, + * so each UWP URI handler has to have one. + * Let's call it "open". + */ + uwp_handler_add_verb (handler_rec, + app, + L"open", + NULL, + TRUE); + + g_hash_table_insert (app->supported_urls, + g_steal_pointer (&proto_u8_folded), + g_object_ref (handler_rec)); + } + + g_hash_table_iter_init (&iter, app->supported_urls); + + while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &url)) + { + gint i_hverb; + + if (!url) + continue; + + for (i_hverb = 0; i_hverb < url->verbs->len; i_hverb++) + { + GWin32AppInfoShellVerb *handler_verb; + + handler_verb = _verb_idx (url->verbs, i_hverb); + uwp_app_add_verb (app, handler_verb->verb_name, handler_verb->verb_displayname); + if (handler_verb->app == NULL && handler_verb->is_uwp) + handler_verb->app = g_object_ref (app); + } + } + + g_free (app_user_model_id_u8); + g_free (app_user_model_id_u8_folded); + + return TRUE; +} + +/* Calls SHLoadIndirectString() in a loop to resolve + * a string in @{...} format (also supports other indirect + * strings, but we aren't using it for those). + * Consumes the input, but may return it unmodified + * (not an indirect string). May return %NULL (the string + * is indirect, but the OS failed to load it). + */ +static gunichar2 * +resolve_string (gunichar2 *at_string) +{ + HRESULT hr; + gunichar2 *result = NULL; + gsize result_size; + /* This value is arbitrary */ + const gsize reasonable_size_limit = 8192; + + if (at_string == NULL || at_string[0] != L'@') + return at_string; + + /* In case of a no-op @at_string will be copied into the output, + * buffer so allocate at least that much. + */ + result_size = wcslen (at_string) + 1; + + while (TRUE) + { + result = g_renew (gunichar2, result, result_size); + /* Since there's no built-in way to detect too small buffer size, + * we do so by putting a sentinel at the end of the buffer. + * If it's 0 (result is always 0-terminated, even if the buffer + * is too small), then try larger buffer. + */ + result[result_size - 1] = 0xff; + /* This string accepts size in characters, not bytes. */ + hr = SHLoadIndirectString (at_string, result, result_size, NULL); + if (!SUCCEEDED (hr)) + { + g_free (result); + g_free (at_string); + return NULL; + } + else if (result[result_size - 1] != 0 || + result_size >= reasonable_size_limit) + { + /* Now that the length is known, allocate the exact amount */ + gunichar2 *copy = g_wcsdup (result, -1); + g_free (result); + g_free (at_string); + return copy; + } + + result_size *= 2; + } + + g_assert_not_reached (); + + return at_string; +} + +static void +grab_registry_string (GWin32RegistryKey *handler_appkey, + const gunichar2 *value_name, + gunichar2 **destination, + gchar **destination_u8) +{ + gunichar2 *value; + gsize value_size; + GWin32RegistryValueType vtype; + const gunichar2 *ms_resource_prefix = L"ms-resource:"; + gsize ms_resource_prefix_len = wcslen (ms_resource_prefix); + + /* Right now this function is not used without destination, + * enforce this. destination_u8 is optional. + */ + g_assert (destination != NULL); + + if (*destination != NULL) + return; + + if (g_win32_registry_key_get_value_w (handler_appkey, + NULL, + TRUE, + value_name, + &vtype, + (void **) &value, + &value_size, + NULL) && + vtype != G_WIN32_REGISTRY_VALUE_STR) + g_clear_pointer (&value, g_free); + + /* There's no way for us to resolve "ms-resource:..." strings */ + if (value != NULL && + value_size >= ms_resource_prefix_len && + memcmp (value, + ms_resource_prefix, + ms_resource_prefix_len * sizeof (gunichar2)) == 0) + g_clear_pointer (&value, g_free); + + if (value == NULL) + return; + + *destination = resolve_string (g_steal_pointer (&value)); + + if (*destination == NULL) + return; + + if (destination_u8) + *destination_u8 = g_utf16_to_utf8 (*destination, -1, NULL, NULL, NULL); +} + +static void +read_uwp_handler_info (void) +{ + GHashTableIter iter; + GWin32RegistryKey *handler_appkey; + gunichar2 *aumid; + + g_hash_table_iter_init (&iter, uwp_handler_table); + + while (g_hash_table_iter_next (&iter, (gpointer *) &handler_appkey, (gpointer *) &aumid)) + { + gchar *aumid_u8_folded; + GWin32AppInfoApplication *app; + + if (!g_utf16_to_utf8_and_fold (aumid, + -1, + NULL, + &aumid_u8_folded)) + continue; + + app = g_hash_table_lookup (apps_by_id, aumid_u8_folded); + g_clear_pointer (&aumid_u8_folded, g_free); + + if (app == NULL) + continue; + + grab_registry_string (handler_appkey, L"ApplicationDescription", &app->description, &app->description_u8); + grab_registry_string (handler_appkey, L"ApplicationName", &app->localized_pretty_name, &app->localized_pretty_name_u8); + /* TODO: ApplicationIcon value (usually also @{...}) resolves into + * an image (PNG only?) with implicit multiple variants (scale, size, etc). + */ + } +} static void update_registry_data (void) @@ -2963,7 +3709,8 @@ update_registry_data (void) GWin32RegistryKey *url_associations; GWin32RegistryKey *file_exts; GWin32RegistryKey *classes_root; - DWORD collect_start, collect_end, alloc_end, capable_end, url_end, ext_end, exeapp_end, classes_end, postproc_end; + DWORD collect_start, collect_end, alloc_end, capable_end, url_end, ext_end, exeapp_end, classes_end, uwp_end, postproc_end; + GError *error = NULL; url_associations = g_win32_registry_key_new_w (L"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations", @@ -3009,6 +3756,8 @@ update_registry_data (void) g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); handlers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); + uwp_handler_table = + g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_free); alloc_end = GetTickCount (); for (i = 0; i < priority_capable_apps_keys->len; i++) @@ -3033,6 +3782,16 @@ update_registry_data (void) exeapp_end = GetTickCount (); read_classes (classes_root); classes_end = GetTickCount (); + + if (!g_win32_package_parser_enum_packages (uwp_package_cb, NULL, &error)) + { + g_debug ("Unable to get UWP apps: %s", error->message); + g_clear_error (&error); + } + + read_uwp_handler_info (); + + uwp_end = GetTickCount (); link_handlers_to_unregistered_apps (); link_handlers_to_fake_apps (); postproc_end = GetTickCount (); @@ -3044,6 +3803,7 @@ update_registry_data (void) "Reading extension assocs: %lums\n" "Reading exe-only apps:...... %lums\n" "Reading classes: %lums\n" + "Reading UWP apps: %lums\n" "Postprocessing:..............%lums\n" "TOTAL: %lums", collect_end - collect_start, @@ -3053,7 +3813,8 @@ update_registry_data (void) ext_end - url_end, exeapp_end - ext_end, classes_end - exeapp_end, - postproc_end - classes_end, + uwp_end - classes_end, + postproc_end - uwp_end, postproc_end - collect_start); g_clear_object (&classes_root); @@ -3062,6 +3823,7 @@ update_registry_data (void) g_ptr_array_free (capable_apps_keys, TRUE); g_ptr_array_free (user_capable_apps_keys, TRUE); g_ptr_array_free (priority_capable_apps_keys, TRUE); + g_hash_table_unref (uwp_handler_table); return; } @@ -3251,17 +4013,11 @@ gio_win32_appinfo_init (gboolean do_wait) if (!do_wait) return; - /* If any of the keys had a change, then we've already initiated - * a tree re-build in keys_updated(). Just wait for it to finish. + /* Previously, we checked each of the watched keys here. + * Now we just look at the update counter, because each key + * has a change callback keys_updated, which increments this counter. */ - if ((url_associations_key && g_win32_registry_key_has_changed (url_associations_key)) || - (file_exts_key && g_win32_registry_key_has_changed (file_exts_key)) || - (user_clients_key && g_win32_registry_key_has_changed (user_clients_key)) || - (system_clients_key && g_win32_registry_key_has_changed (system_clients_key)) || - (applications_key && g_win32_registry_key_has_changed (applications_key)) || - (user_registered_apps_key && g_win32_registry_key_has_changed (user_registered_apps_key)) || - (system_registered_apps_key && g_win32_registry_key_has_changed (system_registered_apps_key)) || - (classes_root_key && g_win32_registry_key_has_changed (classes_root_key))) + if (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0) { g_mutex_lock (&gio_win32_appinfo_mutex); while (g_atomic_int_get (&gio_win32_appinfo_update_counter) > 0) @@ -3345,7 +4101,7 @@ g_win32_app_info_new_from_app (GWin32AppInfoApplication *app, i += 1; } - new_info->supported_types = g_malloc (sizeof (gchar *) * (i + 1)); + new_info->supported_types = g_new (gchar *, i + 1); i = 0; g_hash_table_iter_init (&iter, new_info->app->supported_exts); @@ -3392,7 +4148,7 @@ g_win32_app_info_dup (GAppInfo *appinfo) for (i = 0; info->supported_types[i]; i++) break; - new_info->supported_types = g_malloc (sizeof (gchar *) * (i + 1)); + new_info->supported_types = g_new (gchar *, i + 1); for (i = 0; info->supported_types[i]; i++) new_info->supported_types[i] = g_strdup (info->supported_types[i]); @@ -3461,7 +4217,9 @@ g_win32_app_info_get_name (GAppInfo *appinfo) { GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); - if (info->app && info->app->canonical_name_u8) + if (info->app && info->app->pretty_name_u8) + return info->app->pretty_name_u8; + else if (info->app && info->app->canonical_name_u8) return info->app->canonical_name_u8; else return P_("Unnamed"); @@ -3502,7 +4260,7 @@ g_win32_app_info_get_executable (GAppInfo *appinfo) if (info->app == NULL) return NULL; - if (info->app->verbs->len > 0) + if (info->app->verbs->len > 0 && !info->app->is_uwp) return _verb_idx (info->app->verbs, 0)->executable; return NULL; @@ -3516,7 +4274,7 @@ g_win32_app_info_get_commandline (GAppInfo *appinfo) if (info->app == NULL) return NULL; - if (info->app->verbs->len > 0) + if (info->app->verbs->len > 0 && !info->app->is_uwp) return _verb_idx (info->app->verbs, 0)->command_utf8; return NULL; @@ -3912,8 +4670,51 @@ get_appath_for_exe (const gchar *exe_basename) static gboolean +g_win32_app_info_launch_uwp_internal (GWin32AppInfo *info, + gboolean for_files, + IShellItemArray *items, + GWin32AppInfoShellVerb *shverb, + GError **error) +{ + DWORD pid; + IApplicationActivationManager* paam = NULL; + gboolean result = TRUE; + HRESULT hr; + + hr = CoCreateInstance (&CLSID_ApplicationActivationManager, NULL, CLSCTX_INPROC_SERVER, &IID_IApplicationActivationManager, (void **) &paam); + if (FAILED (hr)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to create ApplicationActivationManager: 0x%lx", hr); + return FALSE; + } + + if (items == NULL) + hr = IApplicationActivationManager_ActivateApplication (paam, (const wchar_t *) info->app->canonical_name, NULL, AO_NONE, &pid); + else if (for_files) + hr = IApplicationActivationManager_ActivateForFile (paam, (const wchar_t *) info->app->canonical_name, items, shverb->verb_name, &pid); + else + hr = IApplicationActivationManager_ActivateForProtocol (paam, (const wchar_t *) info->app->canonical_name, items, &pid); + + if (FAILED (hr)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "The app %s failed to launch: 0x%lx", + g_win32_appinfo_application_get_some_name (info->app), hr); + result = FALSE; + } + + IApplicationActivationManager_Release (paam); + + return result; +} + + +static gboolean g_win32_app_info_launch_internal (GWin32AppInfo *info, - GList *objs, + GList *objs, /* non-UWP only */ + gboolean for_files, /* UWP only */ + IShellItemArray *items, /* UWP only */ GAppLaunchContext *launch_context, GSpawnFlags spawn_flags, GError **error) @@ -3929,15 +4730,10 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, g_return_val_if_fail (info->app != NULL, FALSE); argv = NULL; - - if (launch_context) - envp = g_app_launch_context_get_environment (launch_context); - else - envp = g_get_environ (); - shverb = NULL; - if (info->handler != NULL && + if (!info->app->is_uwp && + info->handler != NULL && info->handler->verbs->len > 0) shverb = _verb_idx (info->handler->verbs, 0); else if (info->app->verbs->len > 0) @@ -3945,7 +4741,7 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, if (shverb == NULL) { - if (info->handler == NULL) + if (info->app->is_uwp || 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)); @@ -3958,6 +4754,18 @@ g_win32_app_info_launch_internal (GWin32AppInfo *info, return FALSE; } + if (info->app->is_uwp) + return g_win32_app_info_launch_uwp_internal (info, + for_files, + items, + shverb, + error); + + if (launch_context) + envp = g_app_launch_context_get_environment (launch_context); + else + envp = g_get_environ (); + g_assert (shverb->command_utf8 != NULL); command = shverb->command_utf8; apppath = get_appath_for_exe (shverb->executable_basename); @@ -4105,6 +4913,95 @@ g_win32_app_info_supports_files (GAppInfo *appinfo) } +static IShellItemArray * +make_item_array (gboolean for_files, + GList *files_or_uris, + GError **error) +{ + ITEMIDLIST **item_ids; + IShellItemArray *items; + GList *p; + gsize count; + gsize i; + HRESULT hr; + + count = g_list_length (files_or_uris); + + items = NULL; + item_ids = g_new (ITEMIDLIST*, count); + + for (i = 0, p = files_or_uris; p != NULL; p = p->next, i++) + { + wchar_t *file_or_uri_utf16; + + if (!for_files) + file_or_uri_utf16 = g_utf8_to_utf16 ((gchar *) p->data, -1, NULL, NULL, error); + else + file_or_uri_utf16 = g_utf8_to_utf16 (g_file_peek_path (G_FILE (p->data)), -1, NULL, NULL, error); + + if (file_or_uri_utf16 == NULL) + break; + + if (for_files) + { + wchar_t *c; + gsize len; + gsize len_tail; + + len = wcslen (file_or_uri_utf16); + /* Filenames *MUST* use single backslashes, else the call + * will fail. First convert all slashes to backslashes, + * then remove duplicates. + */ + for (c = file_or_uri_utf16; for_files && *c != 0; c++) + { + if (*c == L'/') + *c = L'\\'; + } + for (len_tail = 0, c = &file_or_uri_utf16[len - 1]; + for_files && c > file_or_uri_utf16; + c--, len_tail++) + { + if (c[0] != L'\\' || c[-1] != L'\\') + continue; + + memmove (&c[-1], &c[0], len_tail * sizeof (wchar_t)); + } + } + + hr = SHParseDisplayName (file_or_uri_utf16, NULL, &item_ids[i], 0, NULL); + g_free (file_or_uri_utf16); + + if (FAILED (hr)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "File or URI `%S' cannot be parsed by SHParseDisplayName: 0x%lx", file_or_uri_utf16, hr); + break; + } + } + + if (i == count) + { + hr = SHCreateShellItemArrayFromIDLists (count, (const ITEMIDLIST **) item_ids, &items); + if (FAILED (hr)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "SHCreateShellItemArrayFromIDLists() failed: 0x%lx", hr); + items = NULL; + } + } + + count = i; + + for (i = 0; i < count; i++) + CoTaskMemFree (item_ids[i]); + + g_free (item_ids); + + return items; +} + + static gboolean g_win32_app_info_launch_uris (GAppInfo *appinfo, GList *uris, @@ -4114,6 +5011,26 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, gboolean res = FALSE; gboolean do_files; GList *objs; + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + + if (info->app != NULL && info->app->is_uwp) + { + IShellItemArray *items = NULL; + + if (uris) + { + items = make_item_array (FALSE, uris, error); + if (items == NULL) + return res; + } + + res = g_win32_app_info_launch_internal (info, NULL, FALSE, items, launch_context, 0, error); + + if (items != NULL) + IShellItemArray_Release (items); + + return res; + } do_files = g_win32_app_info_supports_files (appinfo); @@ -4142,8 +5059,10 @@ g_win32_app_info_launch_uris (GAppInfo *appinfo, objs = g_list_reverse (objs); - res = g_win32_app_info_launch_internal (G_WIN32_APP_INFO (appinfo), + res = g_win32_app_info_launch_internal (info, objs, + FALSE, + NULL, launch_context, G_SPAWN_SEARCH_PATH, error); @@ -4162,6 +5081,26 @@ g_win32_app_info_launch (GAppInfo *appinfo, gboolean res = FALSE; gboolean do_uris; GList *objs; + GWin32AppInfo *info = G_WIN32_APP_INFO (appinfo); + + if (info->app != NULL && info->app->is_uwp) + { + IShellItemArray *items = NULL; + + if (files) + { + items = make_item_array (TRUE, files, error); + if (items == NULL) + return res; + } + + res = g_win32_app_info_launch_internal (info, NULL, TRUE, items, launch_context, 0, error); + + if (items != NULL) + IShellItemArray_Release (items); + + return res; + } do_uris = g_win32_app_info_supports_uris (appinfo); @@ -4181,8 +5120,10 @@ g_win32_app_info_launch (GAppInfo *appinfo, objs = g_list_reverse (objs); - res = g_win32_app_info_launch_internal (G_WIN32_APP_INFO (appinfo), + res = g_win32_app_info_launch_internal (info, objs, + TRUE, + NULL, launch_context, G_SPAWN_SEARCH_PATH, error); diff --git a/gio/gwin32file-sync-stream.c b/gio/gwin32file-sync-stream.c new file mode 100755 index 000000000..bc3b60694 --- /dev/null +++ b/gio/gwin32file-sync-stream.c @@ -0,0 +1,508 @@ +/* gwin32file-sync-stream.c - a simple IStream implementation + * + * Copyright 2020 Руслан Ижбулатов + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +/* A COM object that implements an IStream backed by a file HANDLE. + * Works just like `SHCreateStreamOnFileEx()`, but does not + * support locking, and doesn't force us to link to libshlwapi. + * Only supports synchronous access. + */ +#include "config.h" +#define COBJMACROS +#define INITGUID +#include <windows.h> + +#include "gwin32file-sync-stream.h" + +static HRESULT STDMETHODCALLTYPE _file_sync_stream_query_interface (IStream *self_ptr, + REFIID ref_interface_guid, + LPVOID *output_object_ptr); +static ULONG STDMETHODCALLTYPE _file_sync_stream_release (IStream *self_ptr); +static ULONG STDMETHODCALLTYPE _file_sync_stream_add_ref (IStream *self_ptr); + +static HRESULT STDMETHODCALLTYPE _file_sync_stream_read (IStream *self_ptr, + void *output_data, + ULONG bytes_to_read, + ULONG *output_bytes_read); + +static HRESULT STDMETHODCALLTYPE _file_sync_stream_write (IStream *self_ptr, + const void *data, + ULONG bytes_to_write, + ULONG *output_bytes_written); + + +static HRESULT STDMETHODCALLTYPE _file_sync_stream_clone (IStream *self_ptr, + IStream **output_clone_ptr); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_commit (IStream *self_ptr, + DWORD commit_flags); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_copy_to (IStream *self_ptr, + IStream *output_stream, + ULARGE_INTEGER bytes_to_copy, + ULARGE_INTEGER *output_bytes_read, + ULARGE_INTEGER *output_bytes_written); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_lock_region (IStream *self_ptr, + ULARGE_INTEGER lock_offset, + ULARGE_INTEGER lock_bytes, + DWORD lock_type); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_revert (IStream *self_ptr); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_seek (IStream *self_ptr, + LARGE_INTEGER move_distance, + DWORD origin, + ULARGE_INTEGER *output_new_position); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_set_size (IStream *self_ptr, + ULARGE_INTEGER new_size); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_stat (IStream *self_ptr, + STATSTG *output_stat, + DWORD flags); +static HRESULT STDMETHODCALLTYPE _file_sync_stream_unlock_region (IStream *self_ptr, + ULARGE_INTEGER lock_offset, + ULARGE_INTEGER lock_bytes, + DWORD lock_type); + +static void _file_sync_stream_free (GWin32FileSyncStream *self); + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_query_interface (IStream *self_ptr, + REFIID ref_interface_guid, + LPVOID *output_object_ptr) +{ + *output_object_ptr = NULL; + + if (IsEqualGUID (ref_interface_guid, &IID_IUnknown)) + { + IUnknown_AddRef ((IUnknown *) self_ptr); + *output_object_ptr = self_ptr; + return S_OK; + } + else if (IsEqualGUID (ref_interface_guid, &IID_IStream)) + { + IStream_AddRef (self_ptr); + *output_object_ptr = self_ptr; + return S_OK; + } + + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE +_file_sync_stream_add_ref (IStream *self_ptr) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + + return ++self->ref_count; +} + +static ULONG STDMETHODCALLTYPE +_file_sync_stream_release (IStream *self_ptr) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + + int ref_count = --self->ref_count; + + if (ref_count == 0) + _file_sync_stream_free (self); + + return ref_count; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_read (IStream *self_ptr, + void *output_data, + ULONG bytes_to_read, + ULONG *output_bytes_read) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + DWORD bytes_read; + + if (!ReadFile (self->file_handle, output_data, bytes_to_read, &bytes_read, NULL)) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + if (output_bytes_read) + *output_bytes_read = bytes_read; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_write (IStream *self_ptr, + const void *data, + ULONG bytes_to_write, + ULONG *output_bytes_written) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + DWORD bytes_written; + + if (!WriteFile (self->file_handle, data, bytes_to_write, &bytes_written, NULL)) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + if (output_bytes_written) + *output_bytes_written = bytes_written; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_seek (IStream *self_ptr, + LARGE_INTEGER move_distance, + DWORD origin, + ULARGE_INTEGER *output_new_position) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + LARGE_INTEGER new_position; + DWORD move_method; + + switch (origin) + { + case STREAM_SEEK_SET: + move_method = FILE_BEGIN; + break; + case STREAM_SEEK_CUR: + move_method = FILE_CURRENT; + break; + case STREAM_SEEK_END: + move_method = FILE_END; + break; + default: + return E_INVALIDARG; + } + + if (!SetFilePointerEx (self->file_handle, move_distance, &new_position, move_method)) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + (*output_new_position).QuadPart = new_position.QuadPart; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_set_size (IStream *self_ptr, + ULARGE_INTEGER new_size) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + FILE_END_OF_FILE_INFO info; + + info.EndOfFile.QuadPart = new_size.QuadPart; + + if (SetFileInformationByHandle (self->file_handle, FileEndOfFileInfo, &info, sizeof (info))) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_copy_to (IStream *self_ptr, + IStream *output_stream, + ULARGE_INTEGER bytes_to_copy, + ULARGE_INTEGER *output_bytes_read, + ULARGE_INTEGER *output_bytes_written) +{ + ULARGE_INTEGER counter; + ULARGE_INTEGER written_counter; + ULARGE_INTEGER read_counter; + + counter.QuadPart = bytes_to_copy.QuadPart; + written_counter.QuadPart = 0; + read_counter.QuadPart = 0; + + while (counter.QuadPart > 0) + { + HRESULT hr; + ULONG bytes_read; + ULONG bytes_written; + ULONG bytes_index; +#define _INTERNAL_BUFSIZE 1024 + BYTE buffer[_INTERNAL_BUFSIZE]; +#undef _INTERNAL_BUFSIZE + ULONG buffer_size = sizeof (buffer); + ULONG to_read = buffer_size; + + if (counter.QuadPart < buffer_size) + to_read = (ULONG) counter.QuadPart; + + /* Because MS SDK has a function IStream_Read() with 3 arguments */ + hr = self_ptr->lpVtbl->Read (self_ptr, buffer, to_read, &bytes_read); + if (!SUCCEEDED (hr)) + return hr; + + read_counter.QuadPart += bytes_read; + + if (bytes_read == 0) + break; + + bytes_index = 0; + + while (bytes_index < bytes_read) + { + /* Because MS SDK has a function IStream_Write() with 3 arguments */ + hr = output_stream->lpVtbl->Write (output_stream, &buffer[bytes_index], bytes_read - bytes_index, &bytes_written); + if (!SUCCEEDED (hr)) + return hr; + + if (bytes_written == 0) + return __HRESULT_FROM_WIN32 (ERROR_WRITE_FAULT); + + bytes_index += bytes_written; + written_counter.QuadPart += bytes_written; + } + } + + if (output_bytes_read) + output_bytes_read->QuadPart = read_counter.QuadPart; + if (output_bytes_written) + output_bytes_written->QuadPart = written_counter.QuadPart; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_commit (IStream *self_ptr, + DWORD commit_flags) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + + if (!FlushFileBuffers (self->file_handle)) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_revert (IStream *self_ptr) +{ + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_lock_region (IStream *self_ptr, + ULARGE_INTEGER lock_offset, + ULARGE_INTEGER lock_bytes, + DWORD lock_type) +{ + return STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_unlock_region (IStream *self_ptr, + ULARGE_INTEGER lock_offset, + ULARGE_INTEGER lock_bytes, + DWORD lock_type) +{ + return STG_E_INVALIDFUNCTION; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_stat (IStream *self_ptr, + STATSTG *output_stat, + DWORD flags) +{ + GWin32FileSyncStream *self = (GWin32FileSyncStream *) self_ptr; + BOOL get_name = FALSE; + FILE_BASIC_INFO bi; + FILE_STANDARD_INFO si; + + if (output_stat == NULL) + return STG_E_INVALIDPOINTER; + + switch (flags) + { + case STATFLAG_DEFAULT: + get_name = TRUE; + break; + case STATFLAG_NONAME: + get_name = FALSE; + break; + default: + return STG_E_INVALIDFLAG; + } + + if (!GetFileInformationByHandleEx (self->file_handle, FileBasicInfo, &bi, sizeof (bi)) || + !GetFileInformationByHandleEx (self->file_handle, FileStandardInfo, &si, sizeof (si))) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + output_stat->type = STGTY_STREAM; + output_stat->mtime.dwLowDateTime = bi.LastWriteTime.LowPart; + output_stat->mtime.dwHighDateTime = bi.LastWriteTime.HighPart; + output_stat->ctime.dwLowDateTime = bi.CreationTime.LowPart; + output_stat->ctime.dwHighDateTime = bi.CreationTime.HighPart; + output_stat->atime.dwLowDateTime = bi.LastAccessTime.LowPart; + output_stat->atime.dwHighDateTime = bi.LastAccessTime.HighPart; + output_stat->grfLocksSupported = 0; + memset (&output_stat->clsid, 0, sizeof (CLSID)); + output_stat->grfStateBits = 0; + output_stat->reserved = 0; + output_stat->cbSize.QuadPart = si.EndOfFile.QuadPart; + output_stat->grfMode = self->stgm_mode; + + if (get_name) + { + DWORD tries; + wchar_t *buffer; + + /* Nothing in the documentation guarantees that the name + * won't change between two invocations (one - to get the + * buffer size, the other - to fill the buffer). + * Re-try up to 5 times in case the required buffer size + * doesn't match. + */ + for (tries = 5; tries > 0; tries--) + { + DWORD buffer_size; + DWORD buffer_size2; + DWORD error; + + buffer_size = GetFinalPathNameByHandleW (self->file_handle, NULL, 0, 0); + + if (buffer_size == 0) + { + DWORD error = GetLastError (); + return __HRESULT_FROM_WIN32 (error); + } + + buffer = CoTaskMemAlloc (buffer_size); + buffer[buffer_size - 1] = 0; + buffer_size2 = GetFinalPathNameByHandleW (self->file_handle, buffer, buffer_size, 0); + + if (buffer_size2 < buffer_size) + break; + + error = GetLastError (); + CoTaskMemFree (buffer); + if (buffer_size2 == 0) + return __HRESULT_FROM_WIN32 (error); + } + + if (tries == 0) + return __HRESULT_FROM_WIN32 (ERROR_BAD_LENGTH); + + output_stat->pwcsName = buffer; + } + else + output_stat->pwcsName = NULL; + + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE +_file_sync_stream_clone (IStream *self_ptr, + IStream **output_clone_ptr) +{ + return E_NOTIMPL; +} + +static IStreamVtbl _file_sync_stream_vtbl = { + _file_sync_stream_query_interface, + _file_sync_stream_add_ref, + _file_sync_stream_release, + _file_sync_stream_read, + _file_sync_stream_write, + _file_sync_stream_seek, + _file_sync_stream_set_size, + _file_sync_stream_copy_to, + _file_sync_stream_commit, + _file_sync_stream_revert, + _file_sync_stream_lock_region, + _file_sync_stream_unlock_region, + _file_sync_stream_stat, + _file_sync_stream_clone, +}; + +static void +_file_sync_stream_free (GWin32FileSyncStream *self) +{ + if (self->owns_handle) + CloseHandle (self->file_handle); + + g_free (self); +} + +/** + * g_win32_file_sync_stream_new: + * @handle: a Win32 HANDLE for a file. + * @owns_handle: %TRUE if newly-created stream owns the handle + * (and closes it when destroyed) + * @stgm_mode: a combination of [STGM constants](https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants) + * that specify the mode with which the stream + * is opened. + * @output_hresult: (out) (optional): a HRESULT from the internal COM calls. + * Will be `S_OK` on success. + * + * Creates an IStream object backed by a HANDLE. + * + * @stgm_mode should match the mode of the @handle, otherwise the stream might + * attempt to perform operations that the @handle does not allow. The implementation + * itself ignores these flags completely, they are only used to report + * the mode of the stream to third parties. + * + * The stream only does synchronous access and will never return `E_PENDING` on I/O. + * + * The returned stream object should be treated just like any other + * COM object, and released via `IUnknown_Release()`. + * its elements have been unreffed with g_object_unref(). + * + * Returns: (nullable) (transfer full): a new IStream object on success, %NULL on failure. + **/ +IStream * +g_win32_file_sync_stream_new (HANDLE file_handle, + gboolean owns_handle, + DWORD stgm_mode, + HRESULT *output_hresult) +{ + GWin32FileSyncStream *new_stream; + IStream *result; + HRESULT hr; + + new_stream = g_new0 (GWin32FileSyncStream, 1); + new_stream->self.lpVtbl = &_file_sync_stream_vtbl; + + hr = IUnknown_QueryInterface ((IUnknown *) new_stream, &IID_IStream, (void **) &result); + if (!SUCCEEDED (hr)) + { + g_free (new_stream); + + if (output_hresult) + *output_hresult = hr; + + return NULL; + } + + new_stream->stgm_mode = stgm_mode; + new_stream->file_handle = file_handle; + new_stream->owns_handle = owns_handle; + + if (output_hresult) + *output_hresult = S_OK; + + return result; +} diff --git a/gio/gwin32file-sync-stream.h b/gio/gwin32file-sync-stream.h new file mode 100755 index 000000000..8e7f5fd59 --- /dev/null +++ b/gio/gwin32file-sync-stream.h @@ -0,0 +1,44 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2020 Руслан Ижбулатов <lrn1986@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ +#ifndef __G_WIN32_FILE_SYNC_STREAM_H__ +#define __G_WIN32_FILE_SYNC_STREAM_H__ + +#include <gio/gio.h> + +#ifdef G_PLATFORM_WIN32 + +typedef struct _GWin32FileSyncStream GWin32FileSyncStream; + +struct _GWin32FileSyncStream +{ + IStream self; + ULONG ref_count; + HANDLE file_handle; + gboolean owns_handle; + DWORD stgm_mode; +}; + +IStream *g_win32_file_sync_stream_new (HANDLE file_handle, + gboolean owns_handle, + DWORD stgm_mode, + HRESULT *output_hresult); + +#endif /* G_PLATFORM_WIN32 */ + +#endif /* __G_WIN32_FILE_SYNC_STREAM_H__ */
\ No newline at end of file diff --git a/gio/gwin32mount.c b/gio/gwin32mount.c index 5b4847a9d..83d8695a1 100644 --- a/gio/gwin32mount.c +++ b/gio/gwin32mount.c @@ -131,6 +131,9 @@ _g_win32_mount_new (GVolumeMonitor *volume_monitor, { GWin32Mount *mount; const gchar *drive = path; //fixme + WCHAR *drive_utf16; + + drive_utf16 = g_utf8_to_utf16 (drive, -1, NULL, NULL, NULL); #if 0 /* No volume for mount: Ignore internal things */ @@ -141,7 +144,7 @@ _g_win32_mount_new (GVolumeMonitor *volume_monitor, mount = g_object_new (G_TYPE_WIN32_MOUNT, NULL); mount->volume_monitor = volume_monitor != NULL ? g_object_ref (volume_monitor) : NULL; mount->mount_path = g_strdup (path); - mount->drive_type = GetDriveType (drive); + mount->drive_type = GetDriveTypeW (drive_utf16); mount->can_eject = FALSE; /* TODO */ mount->name = _win32_get_displayname (drive); @@ -151,6 +154,9 @@ _g_win32_mount_new (GVolumeMonitor *volume_monitor, if (volume != NULL) _g_win32_volume_set_mount (volume, mount); #endif + + g_free (drive_utf16); + return mount; } diff --git a/gio/gwin32packageparser.c b/gio/gwin32packageparser.c new file mode 100755 index 000000000..745f1f522 --- /dev/null +++ b/gio/gwin32packageparser.c @@ -0,0 +1,815 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2020 Руслан Ижбулатов <lrn1986@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ + +/* Queries the system (Windows 8 or newer) for the list + * of UWP packages, parses their manifests and invokes + * a user-provided callback with the needed application + * info. + */ + +#include "config.h" + +#define COBJMACROS +#define INITGUID +#include <windows.h> +#include <winstring.h> +#include <roapi.h> +#include <stdio.h> +#include <shlwapi.h> + +#include "gwin32api-storage.h" +#include "gwin32api-misc.h" +#include "gwin32api-iterator.h" +#include "gwin32api-package.h" + +#include <xmllite.h> + +#include <glib.h> + +#include "gwin32file-sync-stream.h" +#include "gwin32packageparser.h" + +#ifdef G_WINAPI_ONLY_APP +#define LoadedRoActivateInstance RoActivateInstance +#define LoadedWindowsCreateStringReference WindowsCreateStringReference +#define LoadedWindowsDeleteString WindowsDeleteString +#define sax_WindowsGetStringRawBuffer WindowsGetStringRawBuffer +#define LoadedWindowsGetStringRawBuffer WindowsGetStringRawBuffer +#define sax_CreateXmlReader CreateXmlReader +#else +typedef HRESULT (WINAPI *RoActivateInstance_func)(HSTRING activatableClassId, IInspectable **instance); +typedef HRESULT (WINAPI *WindowsCreateStringReference_func)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); +typedef HRESULT (WINAPI *WindowsDeleteString_func)(HSTRING string); +typedef PCWSTR (WINAPI *WindowsGetStringRawBuffer_func)(HSTRING string, UINT32 *length); +typedef HRESULT (STDAPICALLTYPE *CreateXmlReader_func)(REFIID riid, void **ppvObject, IMalloc *pMalloc); +#define sax_WindowsGetStringRawBuffer sax->WindowsGetStringRawBuffer +#define sax_CreateXmlReader sax->CreateXmlReader +#endif + +static gssize +g_utf16_len (const gunichar2 *str) +{ + gssize result; + + for (result = 0; str[0] != 0; str++, result++) + ; + + return result; +} + +static gunichar2 * +g_wcsdup (const gunichar2 *str, gssize str_len) +{ + gssize str_size; + + g_return_val_if_fail (str != NULL, NULL); + + if (str_len == -1) + str_len = g_utf16_len (str); + + g_assert (str_len <= G_MAXSIZE / sizeof (gunichar2) - 1); + str_size = (str_len + 1) * sizeof (gunichar2); + + return g_memdup (str, str_size); +} + +static BOOL +WIN32_FROM_HRESULT (HRESULT hresult, + DWORD *win32_error_code) +{ + if ((hresult & 0xFFFF0000) == MAKE_HRESULT (SEVERITY_ERROR, FACILITY_WIN32, 0) || + hresult == S_OK) + { + *win32_error_code = HRESULT_CODE (hresult); + return TRUE; + } + + return FALSE; +} + +static GIOErrorEnum +gio_error_from_hresult (HRESULT hresult) +{ + DWORD error; + + if (WIN32_FROM_HRESULT (hresult, &error)) + return g_io_error_from_errno (error); + + return G_IO_ERROR_FAILED; +} + +static void +free_extgroup (GWin32PackageExtGroup *g) +{ + g_ptr_array_unref (g->verbs); + g_ptr_array_unref (g->extensions); + g_free (g); +} + +struct _xml_sax_state +{ +#ifndef G_WINAPI_ONLY_APP + WindowsGetStringRawBuffer_func WindowsGetStringRawBuffer; + CreateXmlReader_func CreateXmlReader; +#endif + + GWin32PackageParserCallback callback; + gpointer user_data; + + const wchar_t *manifest_filename; + gsize package_index; + const wchar_t *wcs_full_name; + const wchar_t *wcs_name; + HSTRING package_family; + + gboolean applist; + gboolean exit_early; + + UINT64 in_package; + UINT64 in_applications; + UINT64 in_application; + UINT64 in_extensions; + UINT64 in_extension_protocol; + UINT64 in_extension_fta; + UINT64 in_fta_group; + UINT64 in_sfp; + UINT64 in_filetype; + UINT64 in_sv; + GPtrArray *supported_extensions; + GPtrArray *supported_protocols; + GPtrArray *supported_verbs; + GPtrArray *supported_extgroups; + wchar_t *application_usermodelid; +}; + +static gboolean parse_manifest_file (struct _xml_sax_state *sax); +static gboolean xml_parser_iteration (struct _xml_sax_state *sax, + IXmlReader *xml_reader); +static gboolean xml_parser_get_current_state (struct _xml_sax_state *sax, + IXmlReader *xml_reader, + const wchar_t **local_name, + const wchar_t **prefix, + const wchar_t **value); + +gboolean +g_win32_package_parser_enum_packages (GWin32PackageParserCallback callback, + gpointer user_data, + GError **error) +{ + gboolean result = TRUE; + HRESULT hr; + HSTRING packagemanager_name = NULL; + HSTRING_HEADER packageanager_name_header; + const wchar_t *packman_id = L"Windows.Management.Deployment.PackageManager"; + IInspectable *ii_pm = NULL; + IPackageManager *pm = NULL; + IIterable *packages_iterable = NULL; + IIterator *packages_iterator = NULL; + CHAR has_current; + gsize package_index; + const wchar_t *bslash_appmanifest = L"\\AppxManifest.xml"; + struct _xml_sax_state sax_stack; + struct _xml_sax_state *sax = &sax_stack; + +#ifndef G_WINAPI_ONLY_APP + HMODULE xmllite = NULL; + HMODULE combase = NULL; + HMODULE winrt = NULL; + RoActivateInstance_func LoadedRoActivateInstance; + WindowsCreateStringReference_func LoadedWindowsCreateStringReference; + WindowsDeleteString_func LoadedWindowsDeleteString; + WindowsGetStringRawBuffer_func LoadedWindowsGetStringRawBuffer; + CreateXmlReader_func LoadedCreateXmlReader; + + winrt = LoadLibraryW (L"api-ms-win-core-winrt-l1-1-0.dll"); + if (winrt == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to load api-ms-win-core-winrt-l1-1-0.dll"); + goto cleanup; + } + + combase = LoadLibraryW (L"combase.dll"); + if (combase == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to load combase.dll"); + goto cleanup; + } + + xmllite = LoadLibraryW (L"xmllite.dll"); + if (xmllite == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "Failed to load xmllite.dll"); + goto cleanup; + } + + LoadedCreateXmlReader = (CreateXmlReader_func) GetProcAddress (xmllite, "CreateXmlReader"); + if (LoadedCreateXmlReader == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "CreateXmlReader entry point is not found in xmllite.dll"); + goto cleanup; + } + + LoadedRoActivateInstance = (RoActivateInstance_func) GetProcAddress (winrt, "RoActivateInstance"); + if (LoadedRoActivateInstance == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "RoActivateInstance entry point is not found in api-ms-win-core-winrt-l1-1-0.dll"); + goto cleanup; + } + + LoadedWindowsCreateStringReference = (WindowsCreateStringReference_func) GetProcAddress (combase, "WindowsCreateStringReference"); + if (LoadedWindowsCreateStringReference == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "WindowsCreateStringReference entry point is not found in combase.dll"); + goto cleanup; + } + + LoadedWindowsDeleteString = (WindowsDeleteString_func) GetProcAddress (combase, "WindowsDeleteString"); + if (LoadedWindowsDeleteString == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "WindowsDeleteString entry point is not found in combase.dll"); + goto cleanup; + } + + LoadedWindowsGetStringRawBuffer = (WindowsGetStringRawBuffer_func) GetProcAddress (combase, "WindowsGetStringRawBuffer"); + if (LoadedWindowsGetStringRawBuffer == NULL) + { + result = FALSE; + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (GetLastError ()), + "WindowsGetStringRawBuffer entry point is not found in combase.dll"); + goto cleanup; + } +#endif + + /* This essentially locks current GLib thread into apartment COM model. */ + hr = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED); + /* Can return S_FALSE if COM is already initialized, + * which is not an error, and we still need to uninitialize after that. + */ + if (hr != S_OK && hr != S_FALSE) + { + result = FALSE; + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "CoInitializeEx(COINIT_APARTMENTTHREADED) failed with code 0x%lx", hr); + goto cleanup; + } + +#define canned_com_error_handler(function_name_literal, where_to_go) \ + do \ + { \ + if (FAILED (hr)) \ + { \ + result = FALSE; \ + g_set_error (error, G_IO_ERROR, gio_error_from_hresult (hr), \ + function_name_literal " failed with code 0x%lx", hr); \ + goto where_to_go; \ + } \ + } while (0) + + hr = LoadedWindowsCreateStringReference (packman_id, wcslen (packman_id), &packageanager_name_header, &packagemanager_name); + canned_com_error_handler ("WindowsCreateStringReference()", cleanup); + + hr = LoadedRoActivateInstance (packagemanager_name, &ii_pm); + canned_com_error_handler ("RoActivateInstance()", cleanup); + + hr = IInspectable_QueryInterface (ii_pm, &IID_IPackageManager, (void**) &pm); + canned_com_error_handler ("IInspectable_QueryInterface()", cleanup); + + hr = IPackageManager_FindPackagesByUserSecurityId (pm, 0, &packages_iterable); + canned_com_error_handler ("IPackageManager_FindPackagesByUserSecurityId()", cleanup); + + hr = IIterable_First (packages_iterable, &packages_iterator); + canned_com_error_handler ("IIterable_First()", cleanup); + + hr = IIterator_get_HasCurrent (packages_iterator, &has_current); + canned_com_error_handler ("IIterator_get_HasCurrent()", cleanup); + + for (package_index = 0; SUCCEEDED (hr) && has_current; package_index++) + { + IUnknown *item = NULL; + IPackage *ipackage = NULL; + IPackageId *ipackageid = NULL; + IUnknown *package_install_location = NULL; + IStorageItem *storage_item = NULL; + HSTRING path = NULL; + HSTRING name = NULL; + HSTRING full_name = NULL; + HSTRING package_family = NULL; + size_t manifest_filename_size; + const wchar_t *wcs_path; + const wchar_t *wcs_full_name; + const wchar_t *wcs_name; + wchar_t *manifest_filename = NULL; + +#define canned_com_error_handler_pkg(function_name_literal, where_to_go) \ + do \ + { \ + if (FAILED (hr)) \ + { \ + result = FALSE; \ + g_set_error (error, G_IO_ERROR, gio_error_from_hresult (hr), \ + function_name_literal " for package #%zu failed with code 0x%lx", package_index, hr); \ + goto where_to_go; \ + } \ + } while (0) + + hr = IIterator_get_Current (packages_iterator, &item); + canned_com_error_handler_pkg ("IIterator_get_Current()", package_cleanup); + + hr = IUnknown_QueryInterface (item, &IID_IPackage, (void **) &ipackage); + canned_com_error_handler_pkg ("IUnknown_QueryInterface(IID_IPackage)", package_cleanup); + + hr = IPackage_get_Id (ipackage, &ipackageid); + canned_com_error_handler_pkg ("IPackage_get_Id()", package_cleanup); + + hr = IPackageId_get_FullName (ipackageid, &full_name); + canned_com_error_handler_pkg ("IPackageId_get_FullName()", package_cleanup); + + hr = IPackageId_get_Name (ipackageid, &name); + canned_com_error_handler_pkg ("IPackageId_get_Name()", package_cleanup); + + wcs_full_name = LoadedWindowsGetStringRawBuffer (full_name, NULL); + wcs_name = LoadedWindowsGetStringRawBuffer (name, NULL); + +#define canned_com_error_handler_pkg_named(function_name_literal, where_to_go) \ + do \ + { \ + if (FAILED (hr)) \ + { \ + result = FALSE; \ + g_set_error (error, G_IO_ERROR, gio_error_from_hresult (hr), \ + function_name_literal " for package #%zu (`%S') failed with code 0x%lx", package_index, wcs_full_name, hr); \ + goto where_to_go; \ + } \ + } while (0) + + hr = IPackage_get_InstalledLocation (ipackage, &package_install_location); + canned_com_error_handler_pkg_named ("IPackage_get_InstalledLocation()", package_cleanup); + + hr = IUnknown_QueryInterface (package_install_location, &IID_IStorageItem, (void **) &storage_item); + canned_com_error_handler_pkg_named ("IUnknown_QueryInterface(IID_IStorageItem)", package_cleanup); + + hr = IPackageId_get_FamilyName (ipackageid, &package_family); + canned_com_error_handler_pkg_named ("IPackageId_get_FamilyName()", package_cleanup); + + hr = IStorageItem_get_Path (storage_item, &path); + canned_com_error_handler_pkg_named ("IStorageItem_get_Path()", package_cleanup); + + wcs_path = LoadedWindowsGetStringRawBuffer (path, NULL); + manifest_filename_size = wcslen (wcs_path) + wcslen (bslash_appmanifest); + manifest_filename = g_new (wchar_t, manifest_filename_size + 1); + memcpy (manifest_filename, wcs_path, manifest_filename_size * sizeof (wchar_t)); + memcpy (&manifest_filename[wcslen (wcs_path)], bslash_appmanifest, (wcslen (bslash_appmanifest) + 1) * sizeof (wchar_t)); + + memset (sax, 0, sizeof (*sax)); + sax->callback = callback; + sax->user_data = user_data; + sax->manifest_filename = manifest_filename; + sax->package_index = package_index; + sax->wcs_full_name = wcs_full_name; + sax->wcs_name = wcs_name; + sax->package_family = package_family; + sax->applist = TRUE; + sax->exit_early = FALSE; +#ifndef G_WINAPI_ONLY_APP + sax->CreateXmlReader = LoadedCreateXmlReader; + sax->WindowsGetStringRawBuffer = LoadedWindowsGetStringRawBuffer; +#endif + /* Result isn't checked. If we fail to parse the manifest, + * just try the next package, no need to bail out. + */ + parse_manifest_file (sax); + + hr = IIterator_MoveNext (packages_iterator, &has_current); + canned_com_error_handler_pkg_named ("IIterator_MoveNext()", package_cleanup); + +#undef canned_com_error_handler_pkg_named +#undef canned_com_error_handler_pkg +#undef canned_com_error_handler + + package_cleanup: + g_clear_pointer (&manifest_filename, g_free); + + if (path) + LoadedWindowsDeleteString (path); + if (storage_item) + (void) IStorageItem_Release (storage_item); + if (package_install_location) + (void) IUnknown_Release (package_install_location); + if (ipackage) + (void) IPackage_Release (ipackage); + if (item) + (void) IUnknown_Release (item); + + if (package_family) + LoadedWindowsDeleteString (package_family); + if (name) + LoadedWindowsDeleteString (name); + if (full_name) + LoadedWindowsDeleteString (full_name); + + if (ipackageid) + (void) IPackageId_Release (ipackageid); + if (sax->exit_early) + break; + } + + cleanup: + if (packages_iterator) + (void) IIterator_Release (packages_iterator); + if (packages_iterable) + (void) IIterable_Release (packages_iterable); + if (pm) + (void) IPackageManager_Release (pm); + if (ii_pm) + (void) IInspectable_Release (ii_pm); + + CoUninitialize (); + +#ifndef G_WINAPI_ONLY_APP + if (xmllite) + (void) FreeLibrary (xmllite); + if (combase) + (void) FreeLibrary (combase); + if (winrt) + (void) FreeLibrary (winrt); +#endif + + return result; +} + +static gboolean +parse_manifest_file (struct _xml_sax_state *sax) +{ + HRESULT hr; + HANDLE file_handle = INVALID_HANDLE_VALUE; + IStream *file_stream = NULL; + gboolean result; + IXmlReader *xml_reader; + + file_handle = CreateFileW (sax->manifest_filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + { + g_warning ("Failed to open application manifest `%S' for package #%zu (`%S'): error code 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, GetLastError ()); + return FALSE; + } + + file_stream = g_win32_file_sync_stream_new (file_handle, TRUE, STGM_READ | STGM_SHARE_DENY_WRITE, &hr); + if (file_stream == NULL) + { + g_warning ("Failed to create an IStream for application manifest `%S' for package #%zu (`%S'): HRESULT 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, hr); + CloseHandle (file_handle); + return FALSE; + } + + /* file_stream owns it now */ + file_handle = NULL; + + hr = sax_CreateXmlReader (&IID_IXmlReader, (void **) &xml_reader, NULL); + /* Slightly incorrect - xml reader is not created for any particular file, + * in theory we could re-use the same xml reader instance for all files. + */ + if (FAILED (hr)) + { + g_warning ("CreateXmlReader() for application manifest `%S' for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, hr); + (void) IStream_Release (file_stream); + return FALSE; + } + + hr = IXmlReader_SetInput (xml_reader, (IUnknown *) file_stream); + if (FAILED (hr)) + { + g_warning ("IXmlReader_SetInput() for application manifest `%S' for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, hr); + (void) IXmlReader_Release (xml_reader); + (void) IStream_Release (file_stream); + return FALSE; + } + + sax->supported_extensions = g_ptr_array_new_full (0, (GDestroyNotify) g_free); + sax->supported_protocols = g_ptr_array_new_full (0, (GDestroyNotify) g_free); + sax->supported_verbs = g_ptr_array_new_full (0, (GDestroyNotify) g_free); + sax->supported_extgroups = g_ptr_array_new_full (0, (GDestroyNotify) free_extgroup); + + result = TRUE; + + while (!sax->exit_early && result && !IXmlReader_IsEOF (xml_reader)) + result = xml_parser_iteration (sax, xml_reader); + + g_clear_pointer (&sax->application_usermodelid, g_free); + g_clear_pointer (&sax->supported_extensions, g_ptr_array_unref); + g_clear_pointer (&sax->supported_verbs, g_ptr_array_unref); + g_clear_pointer (&sax->supported_extgroups, g_ptr_array_unref); + g_clear_pointer (&sax->supported_protocols, g_ptr_array_unref); + + (void) IXmlReader_Release (xml_reader); + (void) IStream_Release (file_stream); + + return result; +} + +static gboolean +xml_parser_get_current_state (struct _xml_sax_state *sax, + IXmlReader *xml_reader, + const wchar_t **local_name, + const wchar_t **prefix, + const wchar_t **value) +{ + HRESULT hr; + UINT xml_line_number; + UINT xml_line_position; + + hr = IXmlReader_GetLineNumber (xml_reader, &xml_line_number); + if (FAILED (hr)) + { + g_warning ("IXmlReader_GetLineNumber() for application manifest `%S' for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, hr); + return FALSE; + } + + hr = IXmlReader_GetLinePosition (xml_reader, &xml_line_position); + if (FAILED (hr)) + { + g_warning ("IXmlReader_GetLinePosition() for application manifest `%S' for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, hr); + return FALSE; + } + + hr = IXmlReader_GetLocalName (xml_reader, local_name, NULL); + if (FAILED (hr)) + { + g_warning ("IXmlReader_GetLocalName() for application manifest `%S':%u (column %u) for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, xml_line_number, xml_line_position, sax->package_index, sax->wcs_full_name, hr); + return FALSE; + } + + hr = IXmlReader_GetPrefix (xml_reader, prefix, NULL); + if (FAILED (hr)) + { + g_warning ("IXmlReader_GetPrefix() for application manifest `%S':%u (column %u) for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, xml_line_number, xml_line_position, sax->package_index, sax->wcs_full_name, hr); + return FALSE; + } + + hr = IXmlReader_GetValue (xml_reader, value, NULL); + if (FAILED (hr)) + { + g_warning ("IXmlReader_GetValue() for application manifest `%S':%u (column %u) for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, xml_line_number, xml_line_position, sax->package_index, sax->wcs_full_name, hr); + return FALSE; + } + + return TRUE; +} + +static gboolean +xml_parser_iteration (struct _xml_sax_state *sax, + IXmlReader *xml_reader) +{ + HRESULT hr; + XmlNodeType xml_node_type; + const wchar_t *local_name; + const wchar_t *prefix; + const wchar_t *value; + BOOL is_visual_elements = FALSE; + BOOL is_extension = FALSE; + BOOL is_protocol = FALSE; + BOOL is_empty; + BOOL is_application = FALSE; + BOOL is_verb = FALSE; + + hr = IXmlReader_Read (xml_reader, &xml_node_type); + if (FAILED (hr)) + { + g_warning ("IXmlReader_Read() for application manifest `%S' for package #%zu (`%S') failed with HRESULT 0x%lx", + sax->manifest_filename, sax->package_index, sax->wcs_full_name, hr); + return FALSE; + } + + if (!xml_parser_get_current_state (sax, xml_reader, &local_name, &prefix, &value)) + return FALSE; + + switch (xml_node_type) + { + case XmlNodeType_Element: + is_empty = IXmlReader_IsEmptyElement (xml_reader); + g_assert (local_name != NULL); + + if (!is_empty && + _wcsicmp (local_name, L"Package") == 0 && + prefix[0] == 0) + sax->in_package += 1; + else if (!is_empty && + sax->in_package == 1 && + _wcsicmp (local_name, L"Applications") == 0 && + prefix[0] == 0) + sax->in_applications += 1; + else if (!is_empty && + sax->in_applications == 1 && + _wcsicmp (local_name, L"Application") == 0 && + prefix[0] == 0) + { + sax->in_application += 1; + is_application = TRUE; + sax->applist = TRUE; + g_clear_pointer (&sax->application_usermodelid, g_free); + } + else if (sax->in_application == 1 && + _wcsicmp (local_name, L"VisualElements") == 0 && + (_wcsicmp (prefix, L"uap") == 0 || _wcsicmp (prefix, L"uap3") == 0)) + is_visual_elements = TRUE; + else if (!is_empty && + sax->in_application == 1 && + _wcsicmp (local_name, L"Extensions") == 0 && + prefix[0] == 0) + sax->in_extensions += 1; + else if (!is_empty && + sax->in_application == 1 && + _wcsicmp (local_name, L"Extension") == 0 && + _wcsicmp (prefix, L"uap") == 0) + is_extension = TRUE; + else if (sax->in_extension_protocol == 1 && + _wcsicmp (local_name, L"Protocol") == 0 && + _wcsicmp (prefix, L"uap") == 0) + is_protocol = TRUE; + else if (!is_empty && + sax->in_extension_fta == 1 && + _wcsicmp (local_name, L"FileTypeAssociation") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_fta_group += 1; + else if (!is_empty && + sax->in_fta_group == 1 && + _wcsicmp (local_name, L"SupportedFileTypes") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_sfp += 1; + else if (!is_empty && + sax->in_fta_group == 1 && + _wcsicmp (local_name, L"SupportedVerbs") == 0 && + _wcsicmp (prefix, L"uap2") == 0) + sax->in_sv += 1; + else if (!is_empty && + sax->in_sfp == 1 && + _wcsicmp (local_name, L"FileType") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_filetype += 1; + else if (!is_empty && + sax->in_sv == 1 && + _wcsicmp (local_name, L"Verb") == 0 && + _wcsicmp (prefix, L"uap3") == 0) + is_verb = TRUE; + + hr = IXmlReader_MoveToFirstAttribute (xml_reader); + while (hr == S_OK) + { + if (!xml_parser_get_current_state (sax, xml_reader, &local_name, &prefix, &value)) + return FALSE; + + g_assert (local_name != NULL); + g_assert (value != NULL); + g_assert (prefix != NULL); + + if (is_application && + sax->application_usermodelid == NULL && + _wcsicmp (local_name, L"Id") == 0) + { + size_t id_len = 0; + size_t value_len = wcslen (value); + const wchar_t *wcs_package_family; + size_t wcs_package_family_len; + + wcs_package_family = sax_WindowsGetStringRawBuffer (sax->package_family, NULL); + wcs_package_family_len = wcslen (wcs_package_family); + id_len += wcs_package_family_len + 1 + value_len; + sax->application_usermodelid = g_new (wchar_t, id_len + 1); + /* AppUserModelId = <family>!<id> */ + memcpy (&sax->application_usermodelid[0], wcs_package_family, wcs_package_family_len * sizeof (wchar_t)); + memcpy (&sax->application_usermodelid[wcs_package_family_len], L"!", sizeof (wchar_t)); + memcpy (&sax->application_usermodelid[wcs_package_family_len + 1], value, (value_len + 1) * sizeof (wchar_t)); + } + else if (is_visual_elements && + _wcsicmp (local_name, L"AppListEntry") == 0 && + _wcsicmp (value, L"none") == 0) + sax->applist = FALSE; + else if (is_extension && + _wcsicmp (local_name, L"Category") == 0 && + _wcsicmp (value, L"windows.protocol") == 0) + sax->in_extension_protocol += 1; + else if (is_extension && + _wcsicmp (local_name, L"Category") == 0 && + _wcsicmp (value, L"windows.fileTypeAssociation") == 0) + sax->in_extension_fta += 1; + else if (is_protocol && + _wcsicmp (local_name, L"Name") == 0) + g_ptr_array_add (sax->supported_protocols, g_wcsdup (value, -1)); + else if (is_verb && + _wcsicmp (local_name, L"Id") == 0) + g_ptr_array_add (sax->supported_verbs, g_wcsdup (value, -1)); + + hr = IXmlReader_MoveToNextAttribute (xml_reader); + } + break; + case XmlNodeType_Text: + g_assert (value != NULL); + + if (sax->in_filetype && value[0] != 0) + g_ptr_array_add (sax->supported_extensions, g_wcsdup (value, -1)); + break; + case XmlNodeType_EndElement: + g_assert (local_name != NULL); + + if (_wcsicmp (local_name, L"Package") == 0 && + prefix[0] == 0) + sax->in_package -= 1; + else if (sax->in_package == 1 && + _wcsicmp (local_name, L"Applications") == 0 && + prefix[0] == 0) + sax->in_applications -= 1; + else if (sax->in_application == 1 && + _wcsicmp (local_name, L"Extensions") == 0 && + prefix[0] == 0) + sax->in_extensions -= 1; + else if (sax->in_extension_protocol == 1 && + _wcsicmp (local_name, L"Extension") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_extension_protocol -= 1; + else if (sax->in_extension_fta == 1 && + _wcsicmp (local_name, L"Extension") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_extension_fta -= 1; + else if (sax->in_fta_group == 1 && + _wcsicmp (local_name, L"SupportedFileTypes") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_sfp -= 1; + else if (sax->in_sfp == 1 && + _wcsicmp (local_name, L"FileType") == 0 && + _wcsicmp (prefix, L"uap") == 0) + sax->in_filetype -= 1; + else if (sax->in_fta_group == 1 && + _wcsicmp (local_name, L"SupportedVerbs") == 0 && + _wcsicmp (prefix, L"uap2") == 0) + sax->in_sv -= 1; + else if (sax->in_applications == 1 && + _wcsicmp (local_name, L"Application") == 0 && + prefix[0] == 0) + { + if (sax->application_usermodelid != NULL) + sax->exit_early = !sax->callback (sax->user_data, sax->wcs_full_name, sax->wcs_name, + sax->application_usermodelid, sax->applist, + sax->supported_extgroups, sax->supported_protocols); + g_clear_pointer (&sax->supported_extgroups, g_ptr_array_unref); + g_clear_pointer (&sax->supported_protocols, g_ptr_array_unref); + sax->supported_protocols = g_ptr_array_new_full (0, (GDestroyNotify) g_free); + sax->supported_extgroups = g_ptr_array_new_full (0, (GDestroyNotify) free_extgroup); + sax->in_application -= 1; + } + else if (sax->in_extension_fta == 1 && + _wcsicmp (local_name, L"FileTypeAssociation") == 0 && + _wcsicmp (prefix, L"uap") == 0) + { + GWin32PackageExtGroup *new_group = g_new0 (GWin32PackageExtGroup, 1); + new_group->extensions = g_steal_pointer (&sax->supported_extensions); + sax->supported_extensions = g_ptr_array_new_full (0, (GDestroyNotify) g_free); + new_group->verbs = g_steal_pointer (&sax->supported_verbs); + sax->supported_verbs = g_ptr_array_new_full (0, (GDestroyNotify) g_free); + g_ptr_array_add (sax->supported_extgroups, new_group); + sax->in_fta_group -= 1; + } + break; + default: + break; + } + + return TRUE; +}
\ No newline at end of file diff --git a/gio/gwin32packageparser.h b/gio/gwin32packageparser.h new file mode 100755 index 000000000..f55e30c3f --- /dev/null +++ b/gio/gwin32packageparser.h @@ -0,0 +1,48 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2020 Руслан Ижбулатов <lrn1986@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + */ +#ifndef __G_WIN32_PACKAGE_PARSER_H__ +#define __G_WIN32_PACKAGE_PARSER_H__ + +#include <gio/gio.h> + +#ifdef G_PLATFORM_WIN32 + +typedef struct _GWin32PackageExtGroup GWin32PackageExtGroup; + +struct _GWin32PackageExtGroup +{ + GPtrArray *verbs; + GPtrArray *extensions; +}; + +typedef gboolean (*GWin32PackageParserCallback)(gpointer user_data, + const gunichar2 *full_package_name, + const gunichar2 *package_name, + const gunichar2 *app_user_model_id, + gboolean show_in_applist, + GPtrArray *supported_extgroups, + GPtrArray *supported_protocols); + +gboolean g_win32_package_parser_enum_packages (GWin32PackageParserCallback callback, + gpointer user_data, + GError **error); + +#endif /* G_PLATFORM_WIN32 */ + +#endif /* __G_WIN32_PACKAGE_PARSER_H__ */
\ No newline at end of file diff --git a/gio/gwin32registrykey.c b/gio/gwin32registrykey.c index 6b24fdd90..29895217d 100644 --- a/gio/gwin32registrykey.c +++ b/gio/gwin32registrykey.c @@ -2484,7 +2484,7 @@ g_win32_registry_key_watch (GWin32RegistryKey *key, if (g_once_init_enter (&nt_notify_change_multiple_keys)) { NtNotifyChangeMultipleKeysFunc func; - HMODULE ntdll = GetModuleHandle ("ntdll.dll"); + HMODULE ntdll = GetModuleHandleW (L"ntdll.dll"); if (ntdll != NULL) func = (NtNotifyChangeMultipleKeysFunc) GetProcAddress (ntdll, "NtNotifyChangeMultipleKeys"); diff --git a/gio/gwin32volumemonitor.c b/gio/gwin32volumemonitor.c index 83d6d3260..c6657a357 100644 --- a/gio/gwin32volumemonitor.c +++ b/gio/gwin32volumemonitor.c @@ -68,13 +68,13 @@ get_viewable_logical_drives (void) DWORD no_drives; gboolean hklm_present = FALSE; - if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, - "Software\\Microsoft\\Windows\\" - "CurrentVersion\\Policies\\Explorer", - 0, KEY_READ, &key) == ERROR_SUCCESS) + if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, + L"Software\\Microsoft\\Windows\\" + L"CurrentVersion\\Policies\\Explorer", + 0, KEY_READ, &key) == ERROR_SUCCESS) { - if (RegQueryValueEx (key, "NoDrives", NULL, &var_type, - (LPBYTE) &no_drives, &no_drives_size) == ERROR_SUCCESS) + if (RegQueryValueExW (key, L"NoDrives", NULL, &var_type, + (LPBYTE) &no_drives, &no_drives_size) == ERROR_SUCCESS) { /* We need the bits that are set in viewable_drives, and * unset in no_drives. @@ -88,13 +88,13 @@ get_viewable_logical_drives (void) /* If the key is present in HKLM then the one in HKCU should be ignored */ if (!hklm_present) { - if (RegOpenKeyEx (HKEY_CURRENT_USER, - "Software\\Microsoft\\Windows\\" - "CurrentVersion\\Policies\\Explorer", - 0, KEY_READ, &key) == ERROR_SUCCESS) + if (RegOpenKeyExW (HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\" + L"CurrentVersion\\Policies\\Explorer", + 0, KEY_READ, &key) == ERROR_SUCCESS) { - if (RegQueryValueEx (key, "NoDrives", NULL, &var_type, - (LPBYTE) &no_drives, &no_drives_size) == ERROR_SUCCESS) + if (RegQueryValueExW (key, L"NoDrives", NULL, &var_type, + (LPBYTE) &no_drives, &no_drives_size) == ERROR_SUCCESS) { viewable_drives = viewable_drives & ~no_drives; } diff --git a/gio/inotify/inotify-kernel.c b/gio/inotify/inotify-kernel.c index 05cdcb1aa..1a30fc84d 100644 --- a/gio/inotify/inotify-kernel.c +++ b/gio/inotify/inotify-kernel.c @@ -372,8 +372,8 @@ ik_source_new (gboolean (* callback) (ik_event_t *event)) { static GSourceFuncs source_funcs = { NULL, NULL, - ik_source_dispatch - /* should have a finalize, but it will never happen */ + ik_source_dispatch, + NULL, NULL, NULL }; InotifyKernelSource *iks; GSource *source; diff --git a/gio/meson.build b/gio/meson.build index f79d22053..e0a190cd6 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -12,7 +12,7 @@ gnetworking_h_conf = configuration_data() gnetworking_h_nameser_compat_include = '' -if host_system != 'windows' and not host_system.contains('android') +if host_system not in ['windows', 'android'] # Don't check for C_IN on Android since it does not define it in public # headers, we define it ourselves wherever necessary if not cc.compiles('''#include <sys/types.h> @@ -148,7 +148,7 @@ if host_system != 'windows' endif -if host_system.contains('android') +if host_system == 'android' # struct ip_mreq_source definition is broken on Android NDK <= r16 # See https://bugzilla.gnome.org/show_bug.cgi?id=740791 if not cc.compiles('''#include <netinet/in.h> @@ -430,12 +430,16 @@ else cc.find_library('dnsapi'), iphlpapi_dep, winsock2] + platform_deps += uwp_gio_deps + win32_sources += files( 'gwin32registrykey.c', 'gwin32mount.c', 'gwin32volumemonitor.c', 'gwin32inputstream.c', 'gwin32outputstream.c', + 'gwin32file-sync-stream.c', + 'gwin32packageparser.c', 'gwin32networkmonitor.c', 'gwin32networkmonitor.h', 'gwin32notificationbackend.c', @@ -908,6 +912,7 @@ gio_tool_sources = [ 'gio-tool-cat.c', 'gio-tool-copy.c', 'gio-tool-info.c', + 'gio-tool-launch.c', 'gio-tool-list.c', 'gio-tool-mime.c', 'gio-tool-mkdir.c', diff --git a/gio/tests/socket.c b/gio/tests/socket.c index d6eecdc38..683866ede 100644 --- a/gio/tests/socket.c +++ b/gio/tests/socket.c @@ -18,6 +18,7 @@ #include <gio/gio.h> +#include <gio/gcredentialsprivate.h> #ifdef G_OS_UNIX #include <errno.h> #include <sys/wait.h> @@ -1898,6 +1899,216 @@ test_read_write (gconstpointer user_data) g_object_unref (client); } +#if G_CREDENTIALS_SUPPORTED +static gpointer client_setup_thread (gpointer user_data); + +static void +test_credentials_tcp_client (void) +{ + const GSocketFamily family = G_SOCKET_FAMILY_IPV4; + IPTestData *data; + GError *error = NULL; + GSocket *client; + GSocketAddress *addr; + GCredentials *creds; + + data = create_server (family, echo_server_thread, FALSE, &error); + if (error != NULL) + { + gchar *message = g_strdup_printf ("Failed to create server: %s", error->message); + g_test_skip (message); + g_free (message); + g_clear_error (&error); + return; + } + + addr = g_socket_get_local_address (data->server, &error); + g_assert_no_error (error); + + client = g_socket_new (family, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + g_socket_set_blocking (client, TRUE); + g_socket_set_timeout (client, 1); + + g_socket_connect (client, addr, NULL, &error); + g_assert_no_error (error); + g_object_unref (addr); + + creds = g_socket_get_credentials (client, &error); + if (creds != NULL) + { + gchar *str = g_credentials_to_string (creds); + g_print ("Supported on this OS: %s\n", str); + g_free (str); + g_clear_object (&creds); + } + else + { + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_print ("Unsupported on this OS: %s\n", error->message); + g_clear_error (&error); + } + + g_socket_close (client, &error); + g_assert_no_error (error); + + g_thread_join (data->thread); + + g_socket_close (data->server, &error); + g_assert_no_error (error); + + g_object_unref (data->server); + g_object_unref (client); + + g_slice_free (IPTestData, data); +} + +static void +test_credentials_tcp_server (void) +{ + const GSocketFamily family = G_SOCKET_FAMILY_IPV4; + IPTestData *data; + GSocket *server; + GError *error = NULL; + GSocketAddress *addr = NULL; + GInetAddress *iaddr = NULL; + GSocket *sock = NULL; + GCredentials *creds; + + data = g_slice_new0 (IPTestData); + data->family = family; + data->server = server = g_socket_new (family, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + if (error != NULL) + goto skip; + + g_socket_set_blocking (server, TRUE); + + iaddr = g_inet_address_new_loopback (family); + addr = g_inet_socket_address_new (iaddr, 0); + + if (!g_socket_bind (server, addr, TRUE, &error)) + goto skip; + + if (!g_socket_listen (server, &error)) + goto skip; + + data->thread = g_thread_new ("client", client_setup_thread, data); + + sock = g_socket_accept (server, NULL, &error); + g_assert_no_error (error); + + creds = g_socket_get_credentials (sock, &error); + if (creds != NULL) + { + gchar *str = g_credentials_to_string (creds); + g_print ("Supported on this OS: %s\n", str); + g_free (str); + g_clear_object (&creds); + } + else + { + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_print ("Unsupported on this OS: %s\n", error->message); + g_clear_error (&error); + } + + goto beach; + +skip: + { + gchar *message = g_strdup_printf ("Failed to create server: %s", error->message); + g_test_skip (message); + g_free (message); + + goto beach; + } +beach: + { + g_clear_error (&error); + + g_clear_object (&sock); + g_clear_object (&addr); + g_clear_object (&iaddr); + + g_clear_pointer (&data->thread, g_thread_join); + g_clear_object (&data->server); + g_clear_object (&data->client); + + g_slice_free (IPTestData, data); + } +} + +static gpointer +client_setup_thread (gpointer user_data) +{ + IPTestData *data = user_data; + GSocketAddress *addr; + GSocket *client; + GError *error = NULL; + + addr = g_socket_get_local_address (data->server, &error); + g_assert_no_error (error); + + data->client = client = g_socket_new (data->family, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + g_socket_set_blocking (client, TRUE); + g_socket_set_timeout (client, 1); + + g_socket_connect (client, addr, NULL, &error); + g_assert_no_error (error); + + g_object_unref (addr); + + return NULL; +} + +#ifdef G_OS_UNIX +static void +test_credentials_unix_socketpair (void) +{ + gint fds[2]; + gint status; + GSocket *sock; + GError *error = NULL; + GCredentials *creds; + + status = socketpair (PF_UNIX, SOCK_STREAM, 0, fds); + g_assert_cmpint (status, ==, 0); + + sock = g_socket_new_from_fd (fds[0], &error); + + creds = g_socket_get_credentials (sock, &error); + if (creds != NULL) + { + gchar *str = g_credentials_to_string (creds); + g_print ("Supported on this OS: %s\n", str); + g_free (str); + g_clear_object (&creds); + } + else + { + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_print ("Unsupported on this OS: %s\n", error->message); + g_clear_error (&error); + } + + g_object_unref (sock); + close (fds[1]); +} +#endif +#endif + int main (int argc, char *argv[]) @@ -1954,6 +2165,13 @@ main (int argc, test_read_write); g_test_add_data_func ("/socket/read_writev", GUINT_TO_POINTER (TRUE), test_read_write); +#if G_CREDENTIALS_SUPPORTED + g_test_add_func ("/socket/credentials/tcp_client", test_credentials_tcp_client); + g_test_add_func ("/socket/credentials/tcp_server", test_credentials_tcp_server); +#ifdef G_OS_UNIX + g_test_add_func ("/socket/credentials/unix_socketpair", test_credentials_unix_socketpair); +#endif +#endif return g_test_run(); } diff --git a/gio/win32/gwinhttpvfs.c b/gio/win32/gwinhttpvfs.c index 3dfac259e..03feaf983 100644 --- a/gio/win32/gwinhttpvfs.c +++ b/gio/win32/gwinhttpvfs.c @@ -39,20 +39,20 @@ static void lookup_funcs (void) { HMODULE winhttp = NULL; - char winhttp_dll[MAX_PATH + 100]; + WCHAR winhttp_dll[MAX_PATH + 100]; int n; if (lookup_done) return; - n = GetSystemDirectory (winhttp_dll, MAX_PATH); + n = GetSystemDirectoryW (winhttp_dll, MAX_PATH); if (n > 0 && n < MAX_PATH) { - if (winhttp_dll[n-1] != '\\' && - winhttp_dll[n-1] != '/') - strcat (winhttp_dll, "\\"); - strcat (winhttp_dll, "winhttp.dll"); - winhttp = LoadLibrary (winhttp_dll); + if (winhttp_dll[n-1] != L'\\' && + winhttp_dll[n-1] != L'/') + wcscat (winhttp_dll, L"\\"); + wcscat (winhttp_dll, L"winhttp.dll"); + winhttp = LoadLibraryW (winhttp_dll); } if (winhttp != NULL) |