]> granicus.if.org Git - transmission/commitdiff
Fix UNC paths resolution on Windows
authorMike Gelfand <mikedld@mikedld.com>
Wed, 24 Jan 2018 20:10:21 +0000 (23:10 +0300)
committerMike Gelfand <mikedld@mikedld.com>
Wed, 24 Jan 2018 20:45:08 +0000 (23:45 +0300)
While resolved paths always contain the `\\?\` prefix, it's not always
correct to strip only those 4 chars. In case of UNC paths, the prefix
is actually a bit longer (`\\?\UNC\`) and needs to be replaced with `\\`
instead.

Failing to do so results in invalid paths, e.g. `\\Host\Share\File` becomes
`UNC\Host\Share\File` which totally wrong.

libtransmission/file-win32.c
libtransmission/utils.c
libtransmission/utils.h

index 30acebd46a7494b2d28a05d77b0978634948d1cd..83e3fbaf66c5326adb934dece6c3d87485b530ca 100644 (file)
@@ -34,6 +34,9 @@ struct tr_sys_dir_win32
     char* utf8_name;
 };
 
+static wchar_t const native_local_path_prefix[] = { '\\', '\\', '?', '\\' };
+static wchar_t const native_unc_path_prefix[] = { '\\', '\\', '?', '\\', 'U', 'N', 'C', '\\' };
+
 static void set_system_error(tr_error** error, DWORD code)
 {
     char* message;
@@ -146,14 +149,12 @@ static wchar_t* path_to_native_path_ex(char const* path, int extra_chars_after,
     /* Extending maximum path length limit up to ~32K. See "Naming Files, Paths, and Namespaces"
        (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx) for more info */
 
-    wchar_t const local_prefix[] = { '\\', '\\', '?', '\\' };
-    wchar_t const unc_prefix[] = { '\\', '\\', '?', '\\', 'U', 'N', 'C', '\\' };
-
     bool const is_relative = tr_sys_path_is_relative(path);
     bool const is_unc = is_unc_path(path);
 
     /* `-2` for UNC since we overwrite existing prefix slashes */
-    int const extra_chars_before = is_relative ? 0 : (is_unc ? TR_N_ELEMENTS(unc_prefix) - 2 : TR_N_ELEMENTS(local_prefix));
+    int const extra_chars_before = is_relative ? 0 : (is_unc ? TR_N_ELEMENTS(native_unc_path_prefix) - 2 :
+        TR_N_ELEMENTS(native_local_path_prefix));
 
     /* TODO (?): TR_ASSERT(!is_relative); */
 
@@ -171,12 +172,12 @@ static wchar_t* path_to_native_path_ex(char const* path, int extra_chars_after,
         if (is_unc)
         {
             /* UNC path: "\\server\share" -> "\\?\UNC\server\share" */
-            memcpy(wide_path, unc_prefix, sizeof(unc_prefix));
+            memcpy(wide_path, native_unc_path_prefix, sizeof(native_unc_path_prefix));
         }
         else
         {
             /* Local path: "C:" -> "\\?\C:" */
-            memcpy(wide_path, local_prefix, sizeof(local_prefix));
+            memcpy(wide_path, native_local_path_prefix, sizeof(native_local_path_prefix));
         }
     }
 
@@ -201,6 +202,30 @@ static wchar_t* path_to_native_path(char const* path)
     return path_to_native_path_ex(path, 0, NULL);
 }
 
+static char* native_path_to_path(wchar_t const* wide_path)
+{
+    if (wide_path == NULL)
+    {
+        return NULL;
+    }
+
+    bool const is_unc = wcsncmp(wide_path, native_unc_path_prefix, TR_N_ELEMENTS(native_unc_path_prefix)) == 0;
+    bool const is_local = !is_unc && wcsncmp(wide_path, native_local_path_prefix, TR_N_ELEMENTS(native_local_path_prefix)) == 0;
+
+    size_t const skip_chars = is_unc ? TR_N_ELEMENTS(native_unc_path_prefix) :
+        (is_local ? TR_N_ELEMENTS(native_local_path_prefix) : 0);
+
+    char* const path = tr_win32_native_to_utf8_ex(wide_path + skip_chars, -1, is_unc ? 2 : 0, 0, NULL);
+
+    if (is_unc && path != NULL)
+    {
+        path[0] = '\\';
+        path[1] = '\\';
+    }
+
+    return path;
+}
+
 static tr_sys_file_t open_file(char const* path, DWORD access, DWORD disposition, DWORD flags, tr_error** error)
 {
     TR_ASSERT(path != NULL);
@@ -541,8 +566,9 @@ char* tr_sys_path_resolve(char const* path, tr_error** error)
         goto fail;
     }
 
-    /* Resolved path always begins with "\\?\", so skip those first four chars. */
-    ret = tr_win32_native_to_utf8(wide_ret + 4, -1);
+    TR_ASSERT(wcsncmp(wide_ret, L"\\\\?\\", 4) == 0);
+
+    ret = native_path_to_path(wide_ret);
 
     if (ret != NULL)
     {
index ae6e606659e851738eec0bba7ce8d18926f8e0fc..fbfc43fa7c65b88f8c668925b9ad6b95276df550 100644 (file)
@@ -1236,10 +1236,21 @@ char* tr_utf8clean(char const* str, size_t max_len)
 #ifdef _WIN32
 
 char* tr_win32_native_to_utf8(wchar_t const* text, int text_size)
+{
+    return tr_win32_native_to_utf8_ex(text, text_size, 0, 0, NULL);
+}
+
+char* tr_win32_native_to_utf8_ex(wchar_t const* text, int text_size, int extra_chars_before, int extra_chars_after,
+    int* real_result_size)
 {
     char* ret = NULL;
     int size;
 
+    if (text_size == -1)
+    {
+        text_size = wcslen(text);
+    }
+
     size = WideCharToMultiByte(CP_UTF8, 0, text, text_size, NULL, 0, NULL, NULL);
 
     if (size == 0)
@@ -1247,15 +1258,20 @@ char* tr_win32_native_to_utf8(wchar_t const* text, int text_size)
         goto fail;
     }
 
-    ret = tr_new(char, size + 1);
-    size = WideCharToMultiByte(CP_UTF8, 0, text, text_size, ret, size, NULL, NULL);
+    ret = tr_new(char, size + extra_chars_before + extra_chars_after + 1);
+    size = WideCharToMultiByte(CP_UTF8, 0, text, text_size, ret + extra_chars_before, size, NULL, NULL);
 
     if (size == 0)
     {
         goto fail;
     }
 
-    ret[size] = '\0';
+    ret[size + extra_chars_before + extra_chars_after] = '\0';
+
+    if (real_result_size != NULL)
+    {
+        *real_result_size = size;
+    }
 
     return ret;
 
index 2b0f98b6c32c7675193142dc3a9314dfb5c68e51..9d72fabedf15cddd78fc2dcdf353aae2454d40d4 100644 (file)
@@ -115,6 +115,8 @@ char* tr_utf8clean(char const* str, size_t len) TR_GNUC_MALLOC;
 #ifdef _WIN32
 
 char* tr_win32_native_to_utf8(wchar_t const* text, int text_size);
+char* tr_win32_native_to_utf8_ex(wchar_t const* text, int text_size, int extra_chars_before, int extra_chars_after,
+    int* real_result_size);
 wchar_t* tr_win32_utf8_to_native(char const* text, int text_size);
 wchar_t* tr_win32_utf8_to_native_ex(char const* text, int text_size, int extra_chars_before, int extra_chars_after,
     int* real_result_size);