Imported Upstream version 2.66.6
[platform/upstream/glib.git] / gio / gwin32registrykey.c
index 096e96c..2eb67da 100644 (file)
@@ -5,7 +5,7 @@
  * 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 of the License, or (at your option) any later version.
+ * 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
@@ -28,6 +28,8 @@
 #include <ntstatus.h>
 #include <winternl.h>
 
+#include "gstrfuncsprivate.h"
+
 #ifndef _WDMDDK_
 typedef enum _KEY_INFORMATION_CLASS {
   KeyBasicInformation,
@@ -89,18 +91,18 @@ typedef NTSTATUS
                          PULONG                result_size);
 
 typedef NTSTATUS
-(* NtNotifyChangeMultipleKeysFunc)(HANDLE             key_handle,
-                                   ULONG              subkey_count,
-                                   POBJECT_ATTRIBUTES subkeys,
-                                   HANDLE             event,
-                                   PIO_APC_ROUTINE    apc_routine,
-                                   PVOID              apc_closure,
-                                   PIO_STATUS_BLOCK   status_block,
-                                   ULONG              filter,
-                                   BOOLEAN            watch_tree,
-                                   PVOID              buffer,
-                                   ULONG              buffer_size,
-                                   BOOLEAN            async);
+(NTAPI * NtNotifyChangeMultipleKeysFunc)(HANDLE             key_handle,
+                                         ULONG              subkey_count,
+                                         POBJECT_ATTRIBUTES subkeys,
+                                         HANDLE             event,
+                                         PIO_APC_ROUTINE    apc_routine,
+                                         PVOID              apc_closure,
+                                         PIO_STATUS_BLOCK   status_block,
+                                         ULONG              filter,
+                                         BOOLEAN            watch_tree,
+                                         PVOID              buffer,
+                                         ULONG              buffer_size,
+                                         BOOLEAN            async);
 
 static NtQueryKeyFunc nt_query_key = NULL;
 static NtNotifyChangeMultipleKeysFunc nt_notify_change_multiple_keys = NULL;
@@ -125,16 +127,34 @@ typedef enum
   G_WIN32_REGISTRY_UPDATED_PATH = 1,
 } GWin32RegistryKeyUpdateFlag;
 
+static gsize
+g_utf16_len (const gunichar2 *str)
+{
+  gsize result;
+
+  for (result = 0; str[0] != 0; str++, result++)
+    ;
+
+  return result;
+}
+
 static gunichar2 *
-g_wcsdup (const gunichar2 *str,
-          gssize           str_size)
+g_wcsdup (const gunichar2 *str, gssize str_len)
 {
-  if (str_size == -1)
-    {
-      str_size = wcslen (str) + 1;
-      str_size *= sizeof (gunichar2);
-    }
-  return g_memdup (str, str_size);
+  gsize str_len_unsigned;
+  gsize str_size;
+
+  g_return_val_if_fail (str != NULL, NULL);
+
+  if (str_len < 0)
+    str_len_unsigned = g_utf16_len (str);
+  else
+    str_len_unsigned = (gsize) str_len;
+
+  g_assert (str_len_unsigned <= G_MAXSIZE / sizeof (gunichar2) - 1);
+  str_size = (str_len_unsigned + 1) * sizeof (gunichar2);
+
+  return g_memdup2 (str, str_size);
 }
 
 /**
@@ -247,7 +267,7 @@ g_win32_registry_value_iter_copy (const GWin32RegistryValueIter *iter)
   new_iter->value_name_size = iter->value_name_size;
 
   if (iter->value_data != NULL)
-    new_iter->value_data = g_memdup (iter->value_data, iter->value_data_size);
+    new_iter->value_data = g_memdup2 (iter->value_data, iter->value_data_size);
 
   new_iter->value_data_size = iter->value_data_size;
 
@@ -268,8 +288,8 @@ g_win32_registry_value_iter_copy (const GWin32RegistryValueIter *iter)
   new_iter->value_data_expanded_charsize = iter->value_data_expanded_charsize;
 
   if (iter->value_data_expanded_u8 != NULL)
-    new_iter->value_data_expanded_u8 = g_memdup (iter->value_data_expanded_u8,
-                                                 iter->value_data_expanded_charsize);
+    new_iter->value_data_expanded_u8 = g_memdup2 (iter->value_data_expanded_u8,
+                                                  iter->value_data_expanded_charsize);
 
   new_iter->value_data_expanded_u8_size = iter->value_data_expanded_charsize;
 
@@ -1839,8 +1859,118 @@ g_win32_registry_key_get_path_w (GWin32RegistryKey *key)
 }
 
 /**
+ * g_win32_registry_get_os_dirs_w:
+ *
+ * Returns a list of directories for DLL lookups.
+ * Can be used with g_win32_registry_key_get_value_w().
+ *
+ * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of UTF-16 strings.
+ *
+ * Since: 2.66
+ */
+const gunichar2 * const *
+g_win32_registry_get_os_dirs_w (void)
+{
+  static gunichar2 **mui_os_dirs = NULL;
+
+  if (g_once_init_enter (&mui_os_dirs))
+    {
+      gunichar2 **new_mui_os_dirs;
+      gunichar2 *system32 = NULL;
+      gunichar2 *syswow64 = NULL;
+      UINT buffer_size;
+      gsize array_index = 0;
+
+      buffer_size = GetSystemWow64DirectoryW (NULL, 0);
+
+      if (buffer_size > 0)
+        {
+          UINT copied;
+          syswow64 = g_malloc (buffer_size * sizeof (gunichar2));
+          copied = GetSystemWow64DirectoryW (syswow64, buffer_size);
+          if (copied <= 0)
+            g_clear_pointer (&syswow64, g_free);
+        }
+
+      buffer_size = GetSystemDirectoryW (NULL, 0);
+
+      if (buffer_size > 0)
+        {
+          UINT copied;
+          system32 = g_malloc (buffer_size * sizeof (gunichar2));
+          copied = GetSystemDirectoryW (system32, buffer_size);
+          if (copied <= 0)
+            g_clear_pointer (&system32, g_free);
+        }
+
+      new_mui_os_dirs = g_new0 (gunichar2 *, 3);
+
+      if (system32 != NULL)
+        new_mui_os_dirs[array_index++] = system32;
+
+      if (syswow64 != NULL)
+        new_mui_os_dirs[array_index++] = syswow64;
+
+      new_mui_os_dirs[array_index++] = NULL;
+
+      g_once_init_leave (&mui_os_dirs, new_mui_os_dirs);
+    }
+
+  return (const gunichar2 * const *) mui_os_dirs;
+}
+
+/**
+ * g_win32_registry_get_os_dirs:
+ *
+ * Returns a list of directories for DLL lookups.
+ * Can be used with g_win32_registry_key_get_value().
+ *
+ * Returns: (array zero-terminated=1) (transfer none): a %NULL-terminated array of UTF-8 strings.
+ *
+ * Since: 2.66
+ */
+const gchar * const *
+g_win32_registry_get_os_dirs (void)
+{
+  static gchar **mui_os_dirs = NULL;
+
+  if (g_once_init_enter (&mui_os_dirs))
+    {
+      gchar **new_mui_os_dirs;
+      gsize array_index;
+      gsize new_array_index;
+      const gunichar2 * const *mui_os_dirs_utf16 = g_win32_registry_get_os_dirs_w ();
+
+      for (array_index = 0; mui_os_dirs_utf16[array_index] != NULL; array_index++)
+        ;
+
+      new_mui_os_dirs = g_new0 (gchar *, array_index + 1);
+
+      for (array_index = 0, new_array_index = 0;
+           mui_os_dirs_utf16[array_index] != NULL;
+           array_index++)
+        {
+          new_mui_os_dirs[new_array_index] = g_utf16_to_utf8 (mui_os_dirs_utf16[array_index],
+                                                              -1, NULL, NULL, NULL);
+          if (new_mui_os_dirs[new_array_index] != NULL)
+            new_array_index += 1;
+          else
+            g_critical ("Failed to convert to a system directory #%zu to UTF-8", array_index);
+        }
+
+      g_once_init_leave (&mui_os_dirs, new_mui_os_dirs);
+    }
+
+  return (const gchar * const *) mui_os_dirs;
+}
+
+/**
  * g_win32_registry_key_get_value:
  * @key: (in) (transfer none): a #GWin32RegistryKey
+ * @mui_dll_dirs: (in) (transfer none) (array zero-terminated=1) (optional): a %NULL-terminated
+ *     array of directory names where the OS
+ *     should look for a DLL indicated in a MUI string, if the
+ *     DLL path in the string is not absolute
  * @auto_expand: (in) %TRUE to automatically expand G_WIN32_REGISTRY_VALUE_EXPAND_STR
  *     to G_WIN32_REGISTRY_VALUE_STR.
  * @value_name: (in) (transfer none): name of the value to get (in UTF-8).
@@ -1854,12 +1984,32 @@ g_win32_registry_key_get_path_w (GWin32RegistryKey *key)
  * Get data from a value of a key. String data is guaranteed to be
  * appropriately terminated and will be in UTF-8.
  *
+ * When not %NULL, @mui_dll_dirs indicates that `RegLoadMUIStringW()` API
+ * should be used instead of the usual `RegQueryValueExW()`. This implies
+ * that the value being queried is of type `REG_SZ` or `REG_EXPAND_SZ` (if it is not, the function
+ * falls back to `RegQueryValueExW()`), and that this string must undergo special processing
+ * (see [`SHLoadIndirectString()` documentation](https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) for an explanation on what
+ * kinds of strings are processed) to get the result.
+ *
+ * If no specific MUI DLL directories need to be used, pass
+ * the return value of g_win32_registry_get_os_dirs() as @mui_dll_dirs
+ * (as an bonus, the value from g_win32_registry_get_os_dirs()
+ * does not add any extra UTF8->UTF16 conversion overhead).
+ *
+ * @auto_expand works with @mui_dll_dirs, but only affects the processed
+ * string, making it somewhat useless. The unprocessed string is always expanded
+ * internally, if its type is `REG_EXPAND_SZ` - there is no need to enable
+ * @auto_expand for this to work.
+ *
+ * The API for this function changed in GLib 2.66 to add the @mui_dll_dirs argument.
+ *
  * Returns: %TRUE on success, %FALSE on failure.
  *
- * Since: 2.46
+ * Since: 2.66
  **/
 gboolean
 g_win32_registry_key_get_value (GWin32RegistryKey        *key,
+                                const gchar * const      *mui_dll_dirs,
                                 gboolean                  auto_expand,
                                 const gchar              *value_name,
                                 GWin32RegistryValueType  *value_type,
@@ -1874,6 +2024,9 @@ g_win32_registry_key_get_value (GWin32RegistryKey        *key,
   gchar *value_data_u8;
   gsize value_data_u8_len;
   gboolean result;
+  gsize mui_dll_dirs_count;
+  gunichar2 **mui_dll_dirs_utf16;
+  const gchar * const *mui_os_dirs;
 
   g_return_val_if_fail (G_IS_WIN32_REGISTRY_KEY (key), FALSE);
   g_return_val_if_fail (value_name != NULL, FALSE);
@@ -1889,7 +2042,48 @@ g_win32_registry_key_get_value (GWin32RegistryKey        *key,
   if (value_name_w == NULL)
     return FALSE;
 
+  mui_dll_dirs_utf16 = NULL;
+  mui_os_dirs = g_win32_registry_get_os_dirs ();
+
+  if (mui_dll_dirs != NULL &&
+      mui_dll_dirs != mui_os_dirs)
+    {
+      gsize i;
+
+      mui_dll_dirs_count = g_strv_length ((gchar **) mui_dll_dirs);
+      mui_dll_dirs_utf16 = g_new0 (gunichar2 *, mui_dll_dirs_count + 1);
+
+      for (i = 0; mui_dll_dirs[i] != NULL; i++)
+        {
+          mui_dll_dirs_utf16[i] = g_utf8_to_utf16 (mui_dll_dirs[i], -1, NULL, NULL, error);
+
+          if (mui_dll_dirs_utf16[i] == NULL)
+            break;
+        }
+
+      if (mui_dll_dirs[i] != NULL)
+        {
+          g_prefix_error (error,
+                          "A mui_dll_dirs string #%zu `%s' failed to convert: ",
+                          i, mui_dll_dirs[i]);
+
+          for (i = 0; i < mui_dll_dirs_count; i++)
+            g_free (mui_dll_dirs_utf16[i]);
+
+          g_free (mui_dll_dirs_utf16);
+          g_free (value_name_w);
+
+          return FALSE;
+        }
+    }
+  else if (mui_dll_dirs != NULL &&
+           mui_dll_dirs == mui_os_dirs)
+    {
+      mui_dll_dirs_utf16 = (gunichar2 **) g_win32_registry_get_os_dirs_w ();
+    }
+
   result = g_win32_registry_key_get_value_w (key,
+                                             (const gunichar2 * const *) mui_dll_dirs_utf16,
                                              auto_expand,
                                              value_name_w,
                                              &value_type_g,
@@ -1898,6 +2092,14 @@ g_win32_registry_key_get_value (GWin32RegistryKey        *key,
                                              error);
 
   g_free (value_name_w);
+  if (mui_dll_dirs_utf16 != NULL &&
+      mui_dll_dirs != mui_os_dirs)
+    {
+      gsize array_index;
+      for (array_index = 0; mui_dll_dirs_utf16[array_index] != NULL; array_index++)
+        g_free (mui_dll_dirs_utf16[array_index]);
+      g_free (mui_dll_dirs_utf16);
+    }
 
   if (!result)
     return FALSE;
@@ -1944,9 +2146,98 @@ g_win32_registry_key_get_value (GWin32RegistryKey        *key,
   return TRUE;
 }
 
+/* A wrapper that calls either RegQueryValueExW() or
+ * RegLoadMUIStringW() depending on the value of the
+ * last argument.
+ * Apart from the extra argument, the function behaves
+ * just like RegQueryValueExW(), with a few caveats.
+ */
+static LSTATUS
+MuiRegQueryValueExW (HKEY                     hKey,
+                     LPCWSTR                  lpValueName,
+                     LPDWORD                  lpReserved,
+                     LPDWORD                  lpType,
+                     LPBYTE                   lpData,
+                     LPDWORD                  lpcbData,
+                     const gunichar2 * const *mui_dll_dirs)
+{
+  gsize dir_index;
+  LSTATUS result = ERROR_PATH_NOT_FOUND;
+  DWORD bufsize;
+  DWORD data_size;
+  PVOID old_value;
+
+  if (mui_dll_dirs == NULL)
+    return RegQueryValueExW (hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
+
+  bufsize = 0;
+
+  if (lpcbData != NULL)
+    bufsize = *lpcbData;
+
+  if (mui_dll_dirs[0] != NULL)
+    {
+      /* Optimization: check that the value actually exists,
+       * before we start trying different mui dll dirs
+       */
+      result = RegQueryValueExW (hKey, lpValueName, NULL, NULL, NULL, 0);
+
+      if (result == ERROR_FILE_NOT_FOUND)
+        return result;
+    }
+
+  Wow64DisableWow64FsRedirection (&old_value);
+
+  /* Try with NULL dir first */
+  result = RegLoadMUIStringW (hKey,
+                              lpValueName,
+                              (wchar_t *) lpData,
+                              bufsize,
+                              &data_size,
+                              0,
+                              NULL);
+
+  /* Not a MUI value, load normally */
+  if (result == ERROR_INVALID_DATA)
+    {
+      Wow64RevertWow64FsRedirection (old_value);
+
+      return RegQueryValueExW (hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
+    }
+
+  for (dir_index = 0;
+       result == ERROR_FILE_NOT_FOUND &&
+       mui_dll_dirs[dir_index] != NULL;
+       dir_index++)
+    result = RegLoadMUIStringW (hKey,
+                                lpValueName,
+                                (wchar_t *) lpData,
+                                bufsize,
+                                &data_size,
+                                0,
+                                mui_dll_dirs[dir_index]);
+
+  Wow64RevertWow64FsRedirection (old_value);
+
+  if (lpcbData != NULL &&
+      result == ERROR_MORE_DATA)
+    *lpcbData = data_size;
+
+  if (lpType != NULL &&
+      result != ERROR_INVALID_DATA &&
+      result != ERROR_FILE_NOT_FOUND)
+    *lpType = REG_SZ;
+
+  return result;
+}
+
 /**
  * g_win32_registry_key_get_value_w:
  * @key: (in) (transfer none): a #GWin32RegistryKey
+ * @mui_dll_dirs: (in) (transfer none) (array zero-terminated=1) (optional): a %NULL-terminated
+ *     array of directory names where the OS
+ *     should look for a DLL indicated in a MUI string, if the
+ *     DLL path in the string is not absolute
  * @auto_expand: (in) %TRUE to automatically expand G_WIN32_REGISTRY_VALUE_EXPAND_STR
  *     to G_WIN32_REGISTRY_VALUE_STR.
  * @value_name: (in) (transfer none): name of the value to get (in UTF-16).
@@ -1957,23 +2248,39 @@ g_win32_registry_key_get_value (GWin32RegistryKey        *key,
  *   by @value_data.
  * @error: (nullable): a pointer to %NULL #GError, or %NULL
  *
- * Get data from a value of a key.
- *
  * Get data from a value of a key. String data is guaranteed to be
  * appropriately terminated and will be in UTF-16.
  *
  * When calling with value_data == NULL (to get data size without getting
  * the data itself) remember that returned size corresponds to possibly
  * unterminated string data (if value is some kind of string), because
- * termination cannot be checked and fixed unless the data is retreived
+ * termination cannot be checked and fixed unless the data is retrieved
  * too.
  *
+ * When not %NULL, @mui_dll_dirs indicates that `RegLoadMUIStringW()` API
+ * should be used instead of the usual `RegQueryValueExW()`. This implies
+ * that the value being queried is of type `REG_SZ` or `REG_EXPAND_SZ` (if it is not, the function
+ * falls back to `RegQueryValueExW()`), and that this string must undergo special processing
+ * (see [`SHLoadIndirectString()` documentation](https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-shloadindirectstring) for an explanation on what
+ * kinds of strings are processed) to get the result.
+ *
+ * If no specific MUI DLL directories need to be used, pass
+ * the return value of g_win32_registry_get_os_dirs_w() as @mui_dll_dirs.
+ *
+ * @auto_expand works with @mui_dll_dirs, but only affects the processed
+ * string, making it somewhat useless. The unprocessed string is always expanded
+ * internally, if its type is `REG_EXPAND_SZ` - there is no need to enable
+ * @auto_expand for this to work.
+ *
+ * The API for this function changed in GLib 2.66 to add the @mui_dll_dirs argument.
+ *
  * Returns: %TRUE on success, %FALSE on failure.
  *
- * Since: 2.46
+ * Since: 2.66
  **/
 gboolean
 g_win32_registry_key_get_value_w (GWin32RegistryKey        *key,
+                                  const gunichar2 * const  *mui_dll_dirs,
                                   gboolean                  auto_expand,
                                   const gunichar2          *value_name,
                                   GWin32RegistryValueType  *value_type,
@@ -2000,12 +2307,13 @@ g_win32_registry_key_get_value_w (GWin32RegistryKey        *key,
                         value_data_size != NULL, FALSE);
 
   req_value_data_size = 0;
-  status = RegQueryValueExW (key->priv->handle,
-                             value_name,
-                             NULL,
-                             &value_type_w,
-                             NULL,
-                             &req_value_data_size);
+  status = MuiRegQueryValueExW (key->priv->handle,
+                                value_name,
+                                NULL,
+                                &value_type_w,
+                                NULL,
+                                &req_value_data_size,
+                                mui_dll_dirs);
 
   if (status != ERROR_MORE_DATA && status != ERROR_SUCCESS)
     {
@@ -2032,12 +2340,13 @@ g_win32_registry_key_get_value_w (GWin32RegistryKey        *key,
 
   req_value_data = g_malloc (req_value_data_size + sizeof (gunichar2) * 2);
   req_value_data_size2 = req_value_data_size;
-  status = RegQueryValueExW (key->priv->handle,
-                             value_name,
-                             NULL,
-                             &value_type_w2,
-                             (gpointer) req_value_data,
-                             &req_value_data_size2);
+  status = MuiRegQueryValueExW (key->priv->handle,
+                                value_name,
+                                NULL,
+                                &value_type_w2,
+                                (gpointer) req_value_data,
+                                &req_value_data_size2,
+                                mui_dll_dirs);
 
   if (status != ERROR_SUCCESS)
     {
@@ -2334,8 +2643,6 @@ g_win32_registry_key_set_property (GObject      *object,
   switch (prop_id)
     {
     case PROP_PATH:
-      g_assert (priv->absolute_path_w == NULL);
-      g_assert (priv->absolute_path == NULL);
       path = g_value_get_string (value);
 
       if (path == NULL)
@@ -2346,20 +2653,21 @@ g_win32_registry_key_set_property (GObject      *object,
       if (path_w == NULL)
         break;
 
-      g_free (priv->absolute_path_w);
-      g_free (priv->absolute_path);
+      /* Construct only */
+      g_assert (priv->absolute_path_w == NULL);
+      g_assert (priv->absolute_path == NULL);
       priv->absolute_path_w = path_w;
       priv->absolute_path = g_value_dup_string (value);
       break;
 
     case PROP_PATH_UTF16:
-      g_assert (priv->absolute_path_w == NULL);
-      g_assert (priv->absolute_path == NULL);
       path_w = (gunichar2 *) g_value_get_pointer (value);
 
       if (path_w == NULL)
         break;
 
+      /* Construct only */
+      g_assert (priv->absolute_path_w == NULL);
       priv->absolute_path_w = g_wcsdup (path_w, -1);
       break;