diff --git a/src/api/exceptions.cc b/src/api/exceptions.cc index 871fe78de95154..088bbaff86a2a6 100644 --- a/src/api/exceptions.cc +++ b/src/api/exceptions.cc @@ -8,6 +8,7 @@ #include "v8.h" #include +#include namespace node { @@ -20,6 +21,33 @@ using v8::Object; using v8::String; using v8::Value; +static Local StringFromPath(Isolate* isolate, std::string_view path) { + auto to_v8 = [&](std::string_view s) { + return String::NewFromUtf8(isolate, + s.data(), + v8::NewStringType::kNormal, + static_cast(s.size())) + .ToLocalChecked(); + }; + +#ifdef _WIN32 + constexpr std::string_view kUncPrefix = "\\\\?\\UNC\\"; + constexpr std::string_view kLongPrefix = "\\\\?\\"; + + if (path.starts_with(kUncPrefix)) { + return String::Concat(isolate, + FIXED_ONE_BYTE_STRING(isolate, "\\\\"), + to_v8(path.substr(kUncPrefix.size()))); + } + + if (path.starts_with(kLongPrefix)) { + return to_v8(path.substr(kLongPrefix.size())); + } +#endif + + return to_v8(path); +} + Local ErrnoException(Isolate* isolate, int errorno, const char* syscall, @@ -42,7 +70,7 @@ Local ErrnoException(Isolate* isolate, Local path_string; if (path != nullptr) { // FIXME(bnoordhuis) It's questionable to interpret the file path as UTF-8. - path_string = String::NewFromUtf8(isolate, path).ToLocalChecked(); + path_string = StringFromPath(isolate, std::string_view(path)); } if (path_string.IsEmpty() == false) { @@ -72,22 +100,6 @@ Local ErrnoException(Isolate* isolate, return e; } -static Local StringFromPath(Isolate* isolate, const char* path) { -#ifdef _WIN32 - if (strncmp(path, "\\\\?\\UNC\\", 8) == 0) { - return String::Concat( - isolate, - FIXED_ONE_BYTE_STRING(isolate, "\\\\"), - String::NewFromUtf8(isolate, path + 8).ToLocalChecked()); - } else if (strncmp(path, "\\\\?\\", 4) == 0) { - return String::NewFromUtf8(isolate, path + 4).ToLocalChecked(); - } -#endif - - return String::NewFromUtf8(isolate, path).ToLocalChecked(); -} - - Local UVException(Isolate* isolate, int errorno, const char* syscall, @@ -114,7 +126,7 @@ Local UVException(Isolate* isolate, js_msg = String::Concat(isolate, js_msg, js_syscall); if (path != nullptr) { - js_path = StringFromPath(isolate, path); + js_path = StringFromPath(isolate, std::string_view(path)); js_msg = String::Concat(isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, " '")); @@ -124,7 +136,7 @@ Local UVException(Isolate* isolate, } if (dest != nullptr) { - js_dest = StringFromPath(isolate, dest); + js_dest = StringFromPath(isolate, std::string_view(dest)); js_msg = String::Concat( isolate, js_msg, FIXED_ONE_BYTE_STRING(isolate, " -> '")); diff --git a/src/node_file.cc b/src/node_file.cc index d7926d9a07a3a6..9b0885af909003 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -1728,7 +1728,7 @@ static void RmSync(const FunctionCallbackInfo& args) { } // On Windows path::c_str() returns wide char, convert to std::string first. - std::string file_path_str = file_path.string(); + std::string file_path_str = ConvertPathToUTF8(file_path); const char* path_c_str = file_path_str.c_str(); #ifdef _WIN32 int permission_denied_error = EPERM; @@ -1737,17 +1737,17 @@ static void RmSync(const FunctionCallbackInfo& args) { #endif // !_WIN32 if (error == std::errc::operation_not_permitted) { - std::string message = "Operation not permitted: " + file_path_str; + std::string message = "Operation not permitted: "; return env->ThrowErrnoException(EPERM, "rm", message.c_str(), path_c_str); } else if (error == std::errc::directory_not_empty) { - std::string message = "Directory not empty: " + file_path_str; + std::string message = "Directory not empty: "; return env->ThrowErrnoException( ENOTEMPTY, "rm", message.c_str(), path_c_str); } else if (error == std::errc::not_a_directory) { - std::string message = "Not a directory: " + file_path_str; + std::string message = "Not a directory: "; return env->ThrowErrnoException(ENOTDIR, "rm", message.c_str(), path_c_str); } else if (error == std::errc::permission_denied) { - std::string message = "Permission denied: " + file_path_str; + std::string message = "Permission denied: "; return env->ThrowErrnoException( permission_denied_error, "rm", message.c_str(), path_c_str); } diff --git a/test/parallel/test-fs-rmSync-special-char-additional-error.js b/test/parallel/test-fs-rmSync-special-char-additional-error.js new file mode 100644 index 00000000000000..f362d1e79ee727 --- /dev/null +++ b/test/parallel/test-fs-rmSync-special-char-additional-error.js @@ -0,0 +1,25 @@ +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const path = require('node:path'); + +tmpdir.refresh(); + +const file = path.join(tmpdir.path, '速_file'); +fs.writeFileSync(file, 'x'); + +// Treat that file as if it were a directory so the error message +// includes the path treated as a directory, not the file. +const badPath = path.join(file, 'child'); + +// Attempt to delete the directory which should now fail +try { + fs.rmSync(badPath, { recursive: true }); +} catch (err) { + // Verify that the error is due to the path being treated as a directory + assert.strictEqual(err.code, 'ENOTDIR'); + assert.strictEqual(err.path, badPath); + assert(err.message.includes(badPath), 'Error message should include the path treated as a directory'); +}