Skip to content

Commit c584f74

Browse files
committed
src: use CP_UTF8 for wide file names on win32
`src/node_modules.cc` needs to be consistent with `src/node_file.cc` in how it translates the utf8 strings to `std::wstring` otherwise we might end up in situation where we can read the source code of imported package from disk, but fail to recognize that it is an ESM (or CJS) and cause runtime errors. This type of error is possible on Windows when the path contains unicode characters and "Language for non-Unicode programs" is set to "Chinese (Traditional, Taiwan)". See: nodejs#58768 PR-URL: nodejs#60575 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com> Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent c611ea6 commit c584f74

File tree

4 files changed

+86
-68
lines changed

4 files changed

+86
-68
lines changed

src/node_file.cc

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,42 +3056,6 @@ static void GetFormatOfExtensionlessFile(
30563056
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
30573057
}
30583058

3059-
#ifdef _WIN32
3060-
#define BufferValueToPath(str) \
3061-
std::filesystem::path(ConvertToWideString(str.ToString(), CP_UTF8))
3062-
3063-
std::string ConvertWideToUTF8(const std::wstring& wstr) {
3064-
if (wstr.empty()) return std::string();
3065-
3066-
int size_needed = WideCharToMultiByte(CP_UTF8,
3067-
0,
3068-
&wstr[0],
3069-
static_cast<int>(wstr.size()),
3070-
nullptr,
3071-
0,
3072-
nullptr,
3073-
nullptr);
3074-
std::string strTo(size_needed, 0);
3075-
WideCharToMultiByte(CP_UTF8,
3076-
0,
3077-
&wstr[0],
3078-
static_cast<int>(wstr.size()),
3079-
&strTo[0],
3080-
size_needed,
3081-
nullptr,
3082-
nullptr);
3083-
return strTo;
3084-
}
3085-
3086-
#define PathToString(path) ConvertWideToUTF8(path.wstring());
3087-
3088-
#else // _WIN32
3089-
3090-
#define BufferValueToPath(str) std::filesystem::path(str.ToStringView());
3091-
#define PathToString(path) path.native();
3092-
3093-
#endif // _WIN32
3094-
30953059
static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
30963060
Environment* env = Environment::GetCurrent(args);
30973061
Isolate* isolate = env->isolate();
@@ -3104,15 +3068,15 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
31043068
THROW_IF_INSUFFICIENT_PERMISSIONS(
31053069
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
31063070

3107-
auto src_path = BufferValueToPath(src);
3071+
auto src_path = src.ToPath();
31083072

31093073
BufferValue dest(isolate, args[1]);
31103074
CHECK_NOT_NULL(*dest);
31113075
ToNamespacedPath(env, &dest);
31123076
THROW_IF_INSUFFICIENT_PERMISSIONS(
31133077
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
31143078

3115-
auto dest_path = BufferValueToPath(dest);
3079+
auto dest_path = dest.ToPath();
31163080
bool dereference = args[2]->IsTrue();
31173081
bool recursive = args[3]->IsTrue();
31183082

@@ -3141,8 +3105,8 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
31413105
(src_status.type() == std::filesystem::file_type::directory) ||
31423106
(dereference && src_status.type() == std::filesystem::file_type::symlink);
31433107

3144-
auto src_path_str = PathToString(src_path);
3145-
auto dest_path_str = PathToString(dest_path);
3108+
auto src_path_str = ConvertPathToUTF8(src_path);
3109+
auto dest_path_str = ConvertPathToUTF8(dest_path);
31463110

31473111
if (!error_code) {
31483112
// Check if src and dest are identical.
@@ -3237,7 +3201,7 @@ static bool CopyUtimes(const std::filesystem::path& src,
32373201
uv_fs_t req;
32383202
auto cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
32393203

3240-
auto src_path_str = PathToString(src);
3204+
auto src_path_str = ConvertPathToUTF8(src);
32413205
int result = uv_fs_stat(nullptr, &req, src_path_str.c_str(), nullptr);
32423206
if (is_uv_error(result)) {
32433207
env->ThrowUVException(result, "stat", nullptr, src_path_str.c_str());
@@ -3248,7 +3212,7 @@ static bool CopyUtimes(const std::filesystem::path& src,
32483212
const double source_atime = s->st_atim.tv_sec + s->st_atim.tv_nsec / 1e9;
32493213
const double source_mtime = s->st_mtim.tv_sec + s->st_mtim.tv_nsec / 1e9;
32503214

3251-
auto dest_file_path_str = PathToString(dest);
3215+
auto dest_file_path_str = ConvertPathToUTF8(dest);
32523216
int utime_result = uv_fs_utime(nullptr,
32533217
&req,
32543218
dest_file_path_str.c_str(),
@@ -3383,7 +3347,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
33833347
std::error_code error;
33843348
for (auto dir_entry : std::filesystem::directory_iterator(src)) {
33853349
auto dest_file_path = dest / dir_entry.path().filename();
3386-
auto dest_str = PathToString(dest);
3350+
auto dest_str = ConvertPathToUTF8(dest);
33873351

33883352
if (dir_entry.is_symlink()) {
33893353
if (verbatim_symlinks) {
@@ -3446,7 +3410,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo<Value>& args) {
34463410
}
34473411
} else if (std::filesystem::is_regular_file(dest_file_path)) {
34483412
if (!dereference || (!force && error_on_exist)) {
3449-
auto dest_file_path_str = PathToString(dest_file_path);
3413+
auto dest_file_path_str = ConvertPathToUTF8(dest_file_path);
34503414
env->ThrowStdErrException(
34513415
std::make_error_code(std::errc::file_exists),
34523416
"cp",

src/node_modules.cc

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -291,22 +291,24 @@ const BindingData::PackageConfig* BindingData::TraverseParent(
291291

292292
// Stop the search when the process doesn't have permissions
293293
// to walk upwards
294-
if (is_permissions_enabled &&
295-
!env->permission()->is_granted(
296-
env,
297-
permission::PermissionScope::kFileSystemRead,
298-
current_path.generic_string())) [[unlikely]] {
299-
return nullptr;
294+
if (is_permissions_enabled) {
295+
if (!env->permission()->is_granted(
296+
env,
297+
permission::PermissionScope::kFileSystemRead,
298+
ConvertGenericPathToUTF8(current_path))) [[unlikely]] {
299+
return nullptr;
300+
}
300301
}
301302

302303
// Check if the path ends with `/node_modules`
303-
if (current_path.generic_string().ends_with("/node_modules")) {
304+
if (current_path.filename() == "node_modules") {
304305
return nullptr;
305306
}
306307

307308
auto package_json_path = current_path / "package.json";
309+
308310
auto package_json =
309-
GetPackageJSON(realm, package_json_path.string(), nullptr);
311+
GetPackageJSON(realm, ConvertPathToUTF8(package_json_path), nullptr);
310312
if (package_json != nullptr) {
311313
return package_json;
312314
}
@@ -328,20 +330,12 @@ void BindingData::GetNearestParentPackageJSONType(
328330

329331
ToNamespacedPath(realm->env(), &path_value);
330332

331-
std::string path_value_str = path_value.ToString();
333+
auto path = path_value.ToPath();
334+
332335
if (slashCheck) {
333-
path_value_str.push_back(kPathSeparator);
336+
path /= "";
334337
}
335338

336-
std::filesystem::path path;
337-
338-
#ifdef _WIN32
339-
std::wstring wide_path = ConvertToWideString(path_value_str, GetACP());
340-
path = std::filesystem::path(wide_path);
341-
#else
342-
path = std::filesystem::path(path_value_str);
343-
#endif
344-
345339
auto package_json = TraverseParent(realm, path);
346340

347341
if (package_json == nullptr) {

src/util-inl.h

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,19 +678,71 @@ inline bool IsWindowsBatchFile(const char* filename) {
678678
return !extension.empty() && (extension == "cmd" || extension == "bat");
679679
}
680680

681-
inline std::wstring ConvertToWideString(const std::string& str,
682-
UINT code_page) {
681+
inline std::wstring ConvertUTF8ToWideString(const std::string& str) {
683682
int size_needed = MultiByteToWideChar(
684-
code_page, 0, &str[0], static_cast<int>(str.size()), nullptr, 0);
683+
CP_UTF8, 0, &str[0], static_cast<int>(str.size()), nullptr, 0);
685684
std::wstring wstrTo(size_needed, 0);
686-
MultiByteToWideChar(code_page,
685+
MultiByteToWideChar(CP_UTF8,
687686
0,
688687
&str[0],
689688
static_cast<int>(str.size()),
690689
&wstrTo[0],
691690
size_needed);
692691
return wstrTo;
693692
}
693+
694+
std::string ConvertWideStringToUTF8(const std::wstring& wstr) {
695+
if (wstr.empty()) return std::string();
696+
697+
int size_needed = WideCharToMultiByte(CP_UTF8,
698+
0,
699+
&wstr[0],
700+
static_cast<int>(wstr.size()),
701+
nullptr,
702+
0,
703+
nullptr,
704+
nullptr);
705+
std::string strTo(size_needed, 0);
706+
WideCharToMultiByte(CP_UTF8,
707+
0,
708+
&wstr[0],
709+
static_cast<int>(wstr.size()),
710+
&strTo[0],
711+
size_needed,
712+
nullptr,
713+
nullptr);
714+
return strTo;
715+
}
716+
717+
template <typename T, size_t kStackStorageSize>
718+
std::filesystem::path MaybeStackBuffer<T, kStackStorageSize>::ToPath() const {
719+
std::wstring wide_path = ConvertUTF8ToWideString(ToString());
720+
return std::filesystem::path(wide_path);
721+
}
722+
723+
std::string ConvertPathToUTF8(const std::filesystem::path& path) {
724+
return ConvertWideStringToUTF8(path.wstring());
725+
}
726+
727+
std::string ConvertGenericPathToUTF8(const std::filesystem::path& path) {
728+
return ConvertWideStringToUTF8(path.generic_wstring());
729+
}
730+
731+
#else // _WIN32
732+
733+
template <typename T, size_t kStackStorageSize>
734+
std::filesystem::path MaybeStackBuffer<T, kStackStorageSize>::ToPath() const {
735+
return std::filesystem::path(ToStringView());
736+
}
737+
738+
std::string ConvertPathToUTF8(const std::filesystem::path& path) {
739+
return path.native();
740+
}
741+
742+
std::string ConvertGenericPathToUTF8(const std::filesystem::path& path) {
743+
return path.generic_string();
744+
}
745+
694746
#endif // _WIN32
695747

696748
} // namespace node

src/util.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ class MaybeStackBuffer {
506506
inline std::basic_string_view<T> ToStringView() const {
507507
return {out(), length()};
508508
}
509+
// This can only be used if the buffer contains path data in UTF8
510+
inline std::filesystem::path ToPath() const;
509511

510512
private:
511513
size_t length_;
@@ -1011,9 +1013,15 @@ class JSONOutputStream final : public v8::OutputStream {
10111013
// Returns true if OS==Windows and filename ends in .bat or .cmd,
10121014
// case insensitive.
10131015
inline bool IsWindowsBatchFile(const char* filename);
1014-
inline std::wstring ConvertToWideString(const std::string& str, UINT code_page);
1016+
inline std::wstring ConvertUTF8ToWideString(const std::string& str);
1017+
inline std::string ConvertWideStringToUTF8(const std::wstring& wstr);
1018+
10151019
#endif // _WIN32
10161020

1021+
inline std::filesystem::path ConvertUTF8ToPath(const std::string& str);
1022+
inline std::string ConvertPathToUTF8(const std::filesystem::path& path);
1023+
inline std::string ConvertGenericPathToUTF8(const std::filesystem::path& path);
1024+
10171025
} // namespace node
10181026

10191027
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

0 commit comments

Comments
 (0)