From 5dbfbcdcac0ca97d5aa2784b18ded9d747f620f8 Mon Sep 17 00:00:00 2001 From: Mulham Fetna <76975497+molhamfetnah@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:20:45 +0300 Subject: [PATCH] Merge pull request #28935 from molhamfetnah:fix/21960-unicode-temp-path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core: fix Unicode temp path handling on Windows (fix #21960) #28935 ## Summary Fixes getCacheDirectoryForDownloads and related temp file functions failing when the user home directory contains Unicode characters like C:\\Users\\ใƒ†ใ‚นใƒˆ\\AppData\\Local\\Temp. ## Root Cause On Windows, OpenCV was using ANSI versions of GetTempPath and GetTempFileName which fail with Unicode paths. ## Fix Replace with wide-character GetTempPathW plus UTF-8 conversion: - modules/core/src/utils/filesystem.cpp - getCacheDirectory for cache paths - modules/core/src/system.cpp - temporary file creation - modules/ts/src/ts_gtest.cpp - test stream capture ## Testing The fix has been verified compiles. Manual testing on Windows with Unicode username required. ## Risk Low: Uses same output format, just different encoding path. Existing ASCII functionality preserved. --- Fixes opencv/opencv#21960 --- modules/core/src/system.cpp | 13 ++++++-- modules/core/src/utils/filesystem.cpp | 13 ++++++-- modules/ts/src/ts_gtest.cpp | 45 ++++++++++++++++++++------- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index 185f7516f2..9eb9d9f94c 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -1118,12 +1118,19 @@ String tempfile( const char* suffix ) #else // Use GUID-based naming to avoid race condition with GetTempFileNameA // See issue #19648 - char temp_dir2[MAX_PATH] = { 0 }; + wchar_t temp_dir2_w[MAX_PATH] = { 0 }; if (temp_dir.empty()) { - ::GetTempPathA(sizeof(temp_dir2), temp_dir2); - temp_dir = std::string(temp_dir2); + ::GetTempPathW(sizeof(temp_dir2_w)/sizeof(wchar_t), temp_dir2_w); + // Convert from UTF-16 to UTF-8 to support Unicode paths + int len = WideCharToMultiByte(CP_UTF8, 0, temp_dir2_w, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, temp_dir2_w, -1, utf8_buf.data(), len, NULL, NULL); + temp_dir = std::string(utf8_buf.data()); + } } GUID g; diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp index da07782d73..dc21b15700 100644 --- a/modules/core/src/utils/filesystem.cpp +++ b/modules/core/src/utils/filesystem.cpp @@ -438,11 +438,18 @@ cv::String getCacheDirectory(const char* sub_directory_name, const char* configu { cv::String default_cache_path; #ifdef _WIN32 - char tmp_path_buf[MAX_PATH+1] = {0}; - DWORD res = GetTempPath(MAX_PATH, tmp_path_buf); + wchar_t tmp_path_buf_w[MAX_PATH+1] = {0}; + DWORD res = GetTempPathW(MAX_PATH, tmp_path_buf_w); if (res > 0 && res <= MAX_PATH) { - default_cache_path = tmp_path_buf; + // Convert from UTF-16 to UTF-8 to support Unicode paths + int len = WideCharToMultiByte(CP_UTF8, 0, tmp_path_buf_w, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, tmp_path_buf_w, -1, utf8_buf.data(), len, NULL, NULL); + default_cache_path = std::string(utf8_buf.data()); + } } #elif defined __ANDROID__ // no defaults diff --git a/modules/ts/src/ts_gtest.cpp b/modules/ts/src/ts_gtest.cpp index d3752a5fe4..faef082c99 100644 --- a/modules/ts/src/ts_gtest.cpp +++ b/modules/ts/src/ts_gtest.cpp @@ -10452,20 +10452,41 @@ class CapturedStream { // The ctor redirects the stream to a temporary file. explicit CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { # if GTEST_OS_WINDOWS - char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT - char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT + wchar_t temp_dir_path_w[MAX_PATH + 1] = { L'\0' }; + wchar_t temp_file_path_w[MAX_PATH + 1] = { L'\0' }; - ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); - const UINT success = ::GetTempFileNameA(temp_dir_path, - "gtest_redir", + ::GetTempPathW(sizeof(temp_dir_path_w)/sizeof(wchar_t), temp_dir_path_w); + // Convert to UTF-8 to support Unicode paths + int len = WideCharToMultiByte(CP_UTF8, 0, temp_dir_path_w, -1, NULL, 0, NULL, NULL); + std::string temp_dir_path; + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, temp_dir_path_w, -1, utf8_buf.data(), len, NULL, NULL); + temp_dir_path = std::string(utf8_buf.data()); + } + const UINT success = ::GetTempFileNameW(temp_dir_path_w, + L"gtest_redir", 0, // Generate unique file name. - temp_file_path); - GTEST_CHECK_(success != 0) - << "Unable to create a temporary file in " << temp_dir_path; - const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); - GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " - << temp_file_path; - filename_ = temp_file_path; + temp_file_path_w); + // Convert filename back to UTF-8 + std::string temp_file_path; + int captured_fd = -1; + if (success != 0) + { + len = WideCharToMultiByte(CP_UTF8, 0, temp_file_path_w, -1, NULL, 0, NULL, NULL); + if (len > 0) + { + std::vector utf8_buf(len); + WideCharToMultiByte(CP_UTF8, 0, temp_file_path_w, -1, utf8_buf.data(), len, NULL, NULL); + temp_file_path = std::string(utf8_buf.data()); + } + // Create file and get fd for redirect + captured_fd = _open(temp_file_path.c_str(), _O_CREAT | _O_WRONLY | _O_TRUNC, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " + << temp_file_path; + filename_ = temp_file_path; + } # else // There's no guarantee that a test has write access to the current // directory, so we create the temporary file in the /tmp directory