diff --git a/src/common-tests/file_system_tests.cpp b/src/common-tests/file_system_tests.cpp index d7fc87ba1..be65b2466 100644 --- a/src/common-tests/file_system_tests.cpp +++ b/src/common-tests/file_system_tests.cpp @@ -11,10 +11,18 @@ TEST(FileSystem, GetWin32Path) ASSERT_EQ(FileSystem::GetWin32Path("test.txt"), L"test.txt"); ASSERT_EQ(FileSystem::GetWin32Path("D:\\test.txt"), L"\\\\?\\D:\\test.txt"); ASSERT_EQ(FileSystem::GetWin32Path("C:\\foo"), L"\\\\?\\C:\\foo"); + ASSERT_EQ(FileSystem::GetWin32Path("C:\\foo\\bar\\..\\baz"), L"\\\\?\\C:\\foo\\baz"); ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\baz"), L"\\\\?\\UNC\\foo\\bar\\baz"); + ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\baz\\sub\\.."), L"\\\\?\\UNC\\foo\\bar\\baz"); ASSERT_EQ(FileSystem::GetWin32Path("ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), L"ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"); - ASSERT_EQ(FileSystem::GetWin32Path("C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), L"\\\\?\\C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"); - ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), L"\\\\?\\UNC\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"); + ASSERT_EQ(FileSystem::GetWin32Path("C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), + L"\\\\?\\C:\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"); + ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"), + L"\\\\?\\UNC\\foo\\bar\\ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱"); + ASSERT_EQ(FileSystem::GetWin32Path("C:\\ŻąłóРстуぬね\\のはen🍪\\⟑η∏☉ⴤ\\..\\ℹ︎∩₲ ₱⟑♰⫳🐱"), + L"\\\\?\\C:\\ŻąłóРстуぬね\\のはen🍪\\ℹ︎∩₲ ₱⟑♰⫳🐱"); + ASSERT_EQ(FileSystem::GetWin32Path("\\\\foo\\bar\\ŻąłóРстуぬねのはen🍪\\⟑η∏☉ⴤ\\..\\ℹ︎∩₲ ₱⟑♰⫳🐱"), + L"\\\\?\\UNC\\foo\\bar\\ŻąłóРстуぬねのはen🍪\\ℹ︎∩₲ ₱⟑♰⫳🐱"); } #endif diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index 9a65cbdd8..ef218c2e0 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -26,6 +26,8 @@ #if defined(_WIN32) #include "windows_headers.h" +#include +#include #include #include #include @@ -225,32 +227,50 @@ void Path::RemoveLengthLimits(std::string* path) bool FileSystem::GetWin32Path(std::wstring* dest, std::string_view str) { - const bool absolute = Path::IsAbsolute(str); - const bool unc = IsUNCPath(str); - const size_t skip = unc ? 2 : 0; + // Just convert to wide if it's a relative path, MAX_PATH still applies. + if (!Path::IsAbsolute(str)) + return StringUtil::UTF8StringToWideString(*dest, str); - dest->clear(); - if (str.empty()) - return true; - - int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data() + skip, static_cast(str.length() - skip), nullptr, 0); - if (wlen <= 0) + // PathCchCanonicalizeEx() thankfully takes care of everything. + // But need to widen the string first, avoid the stack allocation. + int wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.length()), nullptr, 0); + if (wlen <= 0) [[unlikely]] return false; - // Can't fix up non-absolute paths. Hopefully they don't go past MAX_PATH. - if (absolute) - dest->append(unc ? L"\\\\?\\UNC\\" : L"\\\\?\\"); - - const size_t start = dest->size(); - dest->resize(start + static_cast(wlen)); - - wlen = MultiByteToWideChar(CP_UTF8, 0, str.data() + skip, static_cast(str.length() - skip), dest->data() + start, - wlen); - if (wlen <= 0) + // So copy it to a temp wide buffer first. + wchar_t* wstr_buf = static_cast(_malloca(sizeof(wchar_t) * (static_cast(wlen) + 1))); + wlen = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast(str.length()), wstr_buf, wlen); + if (wlen <= 0) [[unlikely]] + { + _freea(wstr_buf); return false; + } - dest->resize(start + static_cast(wlen)); - return true; + // And use PathCchCanonicalizeEx() to fix up any non-direct elements. + wstr_buf[wlen] = '\0'; + dest->resize(std::max(static_cast(wlen) + (IsUNCPath(str) ? 9 : 5), 16)); + for (;;) + { + const HRESULT hr = + PathCchCanonicalizeEx(dest->data(), dest->size(), wstr_buf, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH); + if (SUCCEEDED(hr)) + { + dest->resize(std::wcslen(dest->data())); + _freea(wstr_buf); + return true; + } + else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + dest->resize(dest->size() * 2); + continue; + } + else [[unlikely]] + { + Log_ErrorFmt("PathCchCanonicalizeEx() returned {:08X}", static_cast(hr)); + _freea(wstr_buf); + return false; + } + } } std::wstring FileSystem::GetWin32Path(std::string_view str)