[glib/fix-gnulib-msvc-isnan: 29/37] GWin32RegistryKey: add MUI capabilities to get_value()
- From: Chun-wei Fan <fanchunwei src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/fix-gnulib-msvc-isnan: 29/37] GWin32RegistryKey: add MUI capabilities to get_value()
- Date: Tue, 9 Jun 2020 10:23:36 +0000 (UTC)
commit af7cb369c4c760684d81791e0cd85c88e228b983
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date: Sun Jan 26 23:28:19 2020 +0000
GWin32RegistryKey: add MUI capabilities to get_value()
An extra argument to g_win32_registry_key_get_value_w() and
g_win32_registry_key_get_value() indicates that RegLoadMUIStringW()
should be used instead of RegQueryValueExW(). It only works on
strings, and automatically resolves resource strings (the ones
that start with "@").
The extra argument is needed to find resource DLLs that are only
specified by their relative name.
gio/gwin32appinfo.c | 20 ++++
gio/gwin32registrykey.c | 310 +++++++++++++++++++++++++++++++++++++++++++++---
gio/gwin32registrykey.h | 12 +-
glib.supp | 10 ++
4 files changed, 334 insertions(+), 18 deletions(-)
---
diff --git a/gio/gwin32appinfo.c b/gio/gwin32appinfo.c
index 446d88e52..829e8c481 100644
--- a/gio/gwin32appinfo.c
+++ b/gio/gwin32appinfo.c
@@ -601,6 +601,7 @@ read_handler_icon (GWin32RegistryKey *proxy_key,
gchar *default_value;
if (g_win32_registry_key_get_value (icon_key,
+ NULL,
TRUE,
"",
&default_type,
@@ -763,6 +764,7 @@ follow_class_chain_to_handler (const gunichar2 *program_id,
if (key != NULL)
{
got_value = g_win32_registry_key_get_value_w (key,
+ NULL,
TRUE,
L"",
&val_type,
@@ -799,6 +801,7 @@ follow_class_chain_to_handler (const gunichar2 *program_id,
return FALSE;
got_value = g_win32_registry_key_get_value_w (key,
+ NULL,
TRUE,
L"",
&val_type,
@@ -826,6 +829,7 @@ follow_class_chain_to_handler (const gunichar2 *program_id,
}
got_value = g_win32_registry_key_get_value_w (key,
+ NULL,
TRUE,
L"",
&val_type,
@@ -890,6 +894,7 @@ get_url_association (const gunichar2 *schema)
schema_rec = g_hash_table_lookup (urls, schema_folded);
if (!g_win32_registry_key_get_value_w (user_choice,
+ NULL,
TRUE,
L"Progid",
&val_type,
@@ -1054,6 +1059,7 @@ get_file_ext (const gunichar2 *ext)
if (user_choice != NULL)
{
if (g_win32_registry_key_get_value_w (user_choice,
+ NULL,
TRUE,
L"Progid",
&val_type,
@@ -1336,6 +1342,7 @@ collect_capable_apps_from_clients (GPtrArray *capable_apps,
continue;
if (g_win32_registry_key_get_value_w (system_client_type,
+ NULL,
TRUE,
L"",
&default_type,
@@ -1591,6 +1598,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
shell_open_command = NULL;
success = g_win32_registry_key_get_value_w (shell_open_command_key,
+ NULL,
TRUE,
L"",
&vtype,
@@ -1655,6 +1663,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
fallback_friendly_name = NULL;
success = g_win32_registry_key_get_value_w (appkey,
+ NULL,
TRUE,
L"",
&vtype,
@@ -1678,6 +1687,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
friendly_name = NULL;
success = g_win32_registry_key_get_value_w (capabilities,
+ NULL,
TRUE,
L"LocalizedString",
&vtype,
@@ -1703,6 +1713,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
description = NULL;
success = g_win32_registry_key_get_value_w (capabilities,
+ NULL,
TRUE,
L"ApplicationDescription",
&vtype,
@@ -1731,6 +1742,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
if (default_icon_key != NULL)
{
success = g_win32_registry_key_get_value_w (default_icon_key,
+ NULL,
TRUE,
L"",
&vtype,
@@ -1747,6 +1759,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
if (icon_source == NULL)
{
success = g_win32_registry_key_get_value_w (capabilities,
+ NULL,
TRUE,
L"ApplicationIcon",
&vtype,
@@ -1767,6 +1780,7 @@ read_capable_app (gunichar2 *input_app_key_path, gboolean user_specific, gboolea
narrow_application_name = NULL;
success = g_win32_registry_key_get_value_w (capabilities,
+ NULL,
TRUE,
L"ApplicationName",
&vtype,
@@ -2218,6 +2232,7 @@ read_exeapps (void)
if (shell_open_command_key != NULL)
{
success = g_win32_registry_key_get_value_w (shell_open_command_key,
+ NULL,
TRUE,
L"",
&vtype,
@@ -2237,6 +2252,7 @@ read_exeapps (void)
friendly_app_name = NULL;
success = g_win32_registry_key_get_value_w (incapable_app,
+ NULL,
TRUE,
L"FriendlyAppName",
&vtype,
@@ -2251,6 +2267,7 @@ read_exeapps (void)
no_open_with = FALSE;
success = g_win32_registry_key_get_value_w (incapable_app,
+ NULL,
TRUE,
L"NoOpenWith",
&vtype,
@@ -2272,6 +2289,7 @@ read_exeapps (void)
{
success =
g_win32_registry_key_get_value_w (default_icon_key,
+ NULL,
TRUE,
L"",
&vtype,
@@ -2590,6 +2608,7 @@ read_class_url (GWin32RegistryKey *classes_root,
return;
success = g_win32_registry_key_get_value_w (class_key,
+ NULL,
TRUE,
L"URL Protocol",
&vtype,
@@ -3980,6 +3999,7 @@ get_appath_for_exe (gunichar2 *exe_basename)
return NULL;
got_value = g_win32_registry_key_get_value_w (apppath_key,
+ NULL,
TRUE,
L"Path",
&val_type,
diff --git a/gio/gwin32registrykey.c b/gio/gwin32registrykey.c
index c19fede4e..e37953f9a 100644
--- a/gio/gwin32registrykey.c
+++ b/gio/gwin32registrykey.c
@@ -1838,9 +1838,117 @@ g_win32_registry_key_get_path_w (GWin32RegistryKey *key)
return key->priv->absolute_path_w;
}
+/**
+ * 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;
+ }
+
+ 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 +1962,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 +2002,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 +2020,39 @@ 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 mui_dll_dirs_index;
+
+ mui_dll_dirs_count = g_strv_length ((gchar **) mui_dll_dirs);
+
+ if (mui_dll_dirs_count > 0)
+ {
+ mui_dll_dirs_utf16 = g_new0 (gunichar2 *, mui_dll_dirs_count + 1);
+
+ for (mui_dll_dirs_count = 0, mui_dll_dirs_index = 0;
+ mui_dll_dirs[mui_dll_dirs_index] != NULL;
+ mui_dll_dirs_index++)
+ {
+ mui_dll_dirs_utf16[mui_dll_dirs_count] = g_utf8_to_utf16 (mui_dll_dirs[mui_dll_dirs_index],
+ -1, NULL, NULL, NULL);
+ if (mui_dll_dirs_utf16[mui_dll_dirs_count] != NULL)
+ mui_dll_dirs_count += 1;
+ }
+ }
+ }
+ 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 +2061,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 +2115,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,8 +2217,6 @@ 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.
*
@@ -1968,12 +2226,30 @@ g_win32_registry_key_get_value (GWin32RegistryKey *key,
* termination cannot be checked and fixed unless the data is retreived
* 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 +2276,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 +2309,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)
{
diff --git a/gio/gwin32registrykey.h b/gio/gwin32registrykey.h
index 52ccd5c0f..28b57a843 100644
--- a/gio/gwin32registrykey.h
+++ b/gio/gwin32registrykey.h
@@ -239,8 +239,9 @@ gboolean g_win32_registry_value_iter_get_data_w (GWin32RegistryValu
gsize
*value_data_size,
GError **error);
-GLIB_AVAILABLE_IN_2_46
+GLIB_AVAILABLE_IN_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,
@@ -248,8 +249,9 @@ gboolean g_win32_registry_key_get_value (GWin32RegistryKey
gsize
*value_data_size,
GError **error);
-GLIB_AVAILABLE_IN_2_46
+GLIB_AVAILABLE_IN_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,
@@ -276,6 +278,12 @@ gboolean g_win32_registry_key_has_changed (GWin32RegistryKey
GLIB_AVAILABLE_IN_2_46
void g_win32_registry_key_erase_change_indicator (GWin32RegistryKey *key);
+GLIB_AVAILABLE_IN_2_66
+const gunichar2 * const *g_win32_registry_get_os_dirs_w (void);
+
+GLIB_AVAILABLE_IN_2_66
+const gchar * const *g_win32_registry_get_os_dirs (void);
+
G_END_DECLS
#endif /* G_PLATFORM_WIN32 */
diff --git a/glib.supp b/glib.supp
index a59a9b905..83c30f9b1 100644
--- a/glib.supp
+++ b/glib.supp
@@ -887,6 +887,16 @@
fun:g_dbus_error_quark
}
+# g_win32_registry_get_os_dirs_w*() caches an array of strings that is allocated only once.
+{
+ g_win32_registry_get_os_dirs
+ Memcheck:Leak
+ match-leak-kinds:reachable,definite
+ fun:malloc
+ ...
+ fun:g_win32_registry_get_os_dirs*
+}
+
# Thread-private data allocated once per thread
{
g_private_set_alloc0
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]