Fix UNC paths resolution on Windows

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.
This commit is contained in:
Mike Gelfand 2018-01-24 23:10:21 +03:00
parent 6da6629887
commit 3106675261
3 changed files with 55 additions and 11 deletions

View 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)
{

View 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;

View 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);