diff --git a/libtransmission/file-win32.c b/libtransmission/file-win32.c index 49531a228..90db9a508 100644 --- a/libtransmission/file-win32.c +++ b/libtransmission/file-win32.c @@ -141,6 +141,60 @@ is_valid_path (const char * path) return strpbrk (path, "<>:\"|?*") == NULL; } +static wchar_t * +path_to_native_path_ex (const char * path, + int extra_chars_after, + int * real_result_size) +{ + /* 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 */ + + const wchar_t local_prefix[] = { '\\', '\\', '?', '\\' }; + const wchar_t unc_prefix[] = { '\\', '\\', '?', '\\', 'U', 'N', 'C', '\\' }; + + const bool is_relative = tr_sys_path_is_relative (path); + const bool is_unc = is_unc_path (path); + + /* `-2` for UNC since we overwrite existing prefix slashes */ + const int extra_chars_before = is_relative ? 0 : (is_unc ? ARRAYSIZE (unc_prefix) - 2 + : ARRAYSIZE (local_prefix)); + + /* TODO (?): assert (!is_relative); */ + + wchar_t * const wide_path = tr_win32_utf8_to_native_ex (path, -1, extra_chars_before, + extra_chars_after, real_result_size); + if (wide_path == NULL) + return NULL; + + /* Relative paths cannot be used with "\\?\" prefixes. This also means that relative paths are + limited to ~260 chars... but we should rarely work with relative paths in the first place */ + if (!is_relative) + { + if (is_unc) + /* UNC path: "\\server\share" -> "\\?\UNC\server\share" */ + memcpy (wide_path, unc_prefix, sizeof (unc_prefix)); + else + /* Local path: "C:" -> "\\?\C:" */ + memcpy (wide_path, local_prefix, sizeof (local_prefix)); + } + + /* Automatic '/' to '\' conversion is disabled for "\\?\"-prefixed paths */ + wchar_t * p = wide_path + extra_chars_before; + while ((p = wcschr (p, L'/')) != NULL) + *p++ = L'\\'; + + if (real_result_size != NULL) + *real_result_size += extra_chars_before; + + return wide_path; +} + +static wchar_t * +path_to_native_path (const char * path) +{ + return path_to_native_path_ex (path, 0, NULL); +} + static tr_sys_file_t open_file (const char * path, DWORD access, @@ -153,7 +207,7 @@ open_file (const char * path, assert (path != NULL); - wide_path = tr_win32_utf8_to_native (path, -1); + wide_path = path_to_native_path (path); if (wide_path != NULL) ret = CreateFileW (wide_path, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -182,15 +236,10 @@ create_dir (const char * path, (void) permissions; - wide_path = tr_win32_utf8_to_native (path, -1); + wide_path = path_to_native_path (path); if ((flags & TR_SYS_DIR_CREATE_PARENTS) != 0) { - /* For some reason SHCreateDirectoryEx has issues with forward slashes */ - wchar_t * p = wide_path; - while ((p = wcschr (p, L'/')) != NULL) - *p++ = L'\\'; - error_code = SHCreateDirectoryExW (NULL, wide_path, NULL); ret = error_code == ERROR_SUCCESS; } @@ -275,7 +324,7 @@ tr_sys_path_exists (const char * path, assert (path != NULL); - wide_path = tr_win32_utf8_to_native (path, -1); + wide_path = path_to_native_path (path); if (wide_path != NULL) { @@ -319,7 +368,7 @@ tr_sys_path_get_info (const char * path, assert (path != NULL); assert (info != NULL); - wide_path = tr_win32_utf8_to_native (path, -1); + wide_path = path_to_native_path (path); if ((flags & TR_SYS_PATH_NO_FOLLOW) == 0) { @@ -392,11 +441,11 @@ tr_sys_path_is_same (const char * path1, assert (path1 != NULL); assert (path2 != NULL); - wide_path1 = tr_win32_utf8_to_native (path1, -1); + wide_path1 = path_to_native_path (path1); if (wide_path1 == NULL) goto fail; - wide_path2 = tr_win32_utf8_to_native (path2, -1); + wide_path2 = path_to_native_path (path2); if (wide_path2 == NULL) goto fail; @@ -444,7 +493,7 @@ tr_sys_path_resolve (const char * path, assert (path != NULL); - wide_path = tr_win32_utf8_to_native (path, -1); + wide_path = path_to_native_path (path); if (wide_path == NULL) goto fail; @@ -565,8 +614,8 @@ tr_sys_path_rename (const char * src_path, assert (src_path != NULL); assert (dst_path != NULL); - wide_src_path = tr_win32_utf8_to_native (src_path, -1); - wide_dst_path = tr_win32_utf8_to_native (dst_path, -1); + wide_src_path = path_to_native_path (src_path); + wide_dst_path = path_to_native_path (dst_path); if (wide_src_path != NULL && wide_dst_path != NULL) { @@ -608,7 +657,7 @@ tr_sys_path_remove (const char * path, assert (path != NULL); - wide_path = tr_win32_utf8_to_native (path, -1); + wide_path = path_to_native_path (path); if (wide_path != NULL) { @@ -1159,6 +1208,7 @@ tr_sys_dir_open (const char * path, tr_error ** error) { tr_sys_dir_t ret; + int pattern_size; #ifndef __clang__ /* Clang gives "static_assert expression is not an integral constant expression" error */ @@ -1168,14 +1218,12 @@ tr_sys_dir_open (const char * path, assert (path != NULL); ret = tr_new (struct tr_sys_dir_win32, 1); - ret->pattern = tr_win32_utf8_to_native_ex (path, -1, 2); + ret->pattern = path_to_native_path_ex (path, 2, &pattern_size); if (ret->pattern != NULL) { - const size_t pattern_size = wcslen (ret->pattern); ret->pattern[pattern_size + 0] = L'\\'; ret->pattern[pattern_size + 1] = L'*'; - ret->pattern[pattern_size + 2] = L'\0'; ret->find_handle = INVALID_HANDLE_VALUE; ret->utf8_name = NULL; diff --git a/libtransmission/utils.c b/libtransmission/utils.c index d02bac882..ecab4a786 100644 --- a/libtransmission/utils.c +++ b/libtransmission/utils.c @@ -1080,27 +1080,35 @@ wchar_t * tr_win32_utf8_to_native (const char * text, int text_size) { - return tr_win32_utf8_to_native_ex (text, text_size, 0); + return tr_win32_utf8_to_native_ex (text, text_size, 0, 0, NULL); } wchar_t * tr_win32_utf8_to_native_ex (const char * text, int text_size, - int extra_chars) + int extra_chars_before, + int extra_chars_after, + int * real_result_size) { wchar_t * ret = NULL; int size; + if (text_size == -1) + text_size = strlen (text); + size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, NULL, 0); if (size == 0) goto fail; - ret = tr_new (wchar_t, size + extra_chars + 1); - size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, ret, size); + ret = tr_new (wchar_t, size + extra_chars_before + extra_chars_after + 1); + size = MultiByteToWideChar (CP_UTF8, 0, text, text_size, ret + extra_chars_before, size); if (size == 0) goto fail; - ret[size] = L'\0'; + ret[size + extra_chars_before + extra_chars_after] = L'\0'; + + if (real_result_size != NULL) + *real_result_size = size; return ret; diff --git a/libtransmission/utils.h b/libtransmission/utils.h index 833ecea85..70a8d4108 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -200,7 +200,9 @@ wchar_t * tr_win32_utf8_to_native (const char * text, int text_size); wchar_t * tr_win32_utf8_to_native_ex (const char * text, int text_size, - int extra_chars); + int extra_chars_before, + int extra_chars_after, + int * real_result_size); char * tr_win32_format_message (uint32_t code); void tr_win32_make_args_utf8 (int * argc,