Skip to content

Commit a06bb51

Browse files
Archkonaduh95
authored andcommitted
src: use UTF-8 for task runner filesystem paths
Use ConvertPathToUTF8() instead of path::string() when passing filesystem paths to Node and libuv interfaces. This prevents paths containing characters outside the active Windows code page from being corrupted or rejected. Signed-off-by: Archkon <[email protected]> PR-URL: #64868 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Stefan Stojanovic <[email protected]>
1 parent 3481ea3 commit a06bb51

2 files changed

Lines changed: 55 additions & 12 deletions

File tree

‎src/node_task_runner.cc‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ void ProcessRunner::SetEnvironmentVariables() {
127127
// Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to
128128
// indicate which package.json is being processed.
129129
env_vars_.push_back("NODE_RUN_PACKAGE_JSON_PATH=" +
130-
package_json_path_.string());
130+
ConvertPathToUTF8(package_json_path_));
131131

132132
env_ = std::unique_ptr<char*[]>(new char*[env_vars_.size() + 1]);
133133
options_.env = env_.get();
@@ -211,7 +211,7 @@ void ProcessRunner::OnExit(int64_t exit_status, int term_signal) {
211211

212212
void ProcessRunner::Run() {
213213
// keeps the string alive until destructor
214-
cwd_ = package_json_path_.parent_path().string();
214+
cwd_ = ConvertPathToUTF8(package_json_path_.parent_path());
215215
options_.cwd = cwd_.c_str();
216216
if (int r = uv_spawn(loop_, &process_, &options_)) {
217217
fprintf(stderr, "Error: %s\n", uv_strerror(r));
@@ -233,14 +233,14 @@ FindPackageJson(const std::filesystem::path& cwd) {
233233
// Append "path/node_modules/.bin" to the env var, if it is a directory.
234234
auto node_modules_bin = directory_path / "node_modules" / ".bin";
235235
if (std::filesystem::is_directory(node_modules_bin)) {
236-
path_env_var += node_modules_bin.string() + env_var_separator;
236+
path_env_var += ConvertPathToUTF8(node_modules_bin) + env_var_separator;
237237
}
238238

239239
if (raw_content.empty()) {
240240
package_json_path = directory_path / "package.json";
241241
// This is required for Windows because std::filesystem::path::c_str()
242242
// returns wchar_t* on Windows, and char* on other platforms.
243-
std::string contents = package_json_path.string();
243+
std::string contents = ConvertPathToUTF8(package_json_path);
244244
USE(ReadFileSync(&raw_content, contents.c_str()) > 0);
245245
}
246246
}
@@ -263,7 +263,7 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
263263
if (!package_json.has_value()) {
264264
fprintf(stderr,
265265
"Can't find package.json for directory %s\n",
266-
cwd.string().c_str());
266+
ConvertPathToUTF8(cwd).c_str());
267267
result->exit_code_ = ExitCode::kGenericUserError;
268268
return;
269269
}
@@ -279,7 +279,7 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
279279
simdjson::ondemand::object main_object;
280280

281281
if (json_parser.iterate(raw_json).get(document)) {
282-
fprintf(stderr, "Can't parse %s\n", path.string().c_str());
282+
fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str());
283283
result->exit_code_ = ExitCode::kGenericUserError;
284284
return;
285285
}
@@ -288,9 +288,9 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
288288
if (root_error == simdjson::error_code::INCORRECT_TYPE) {
289289
fprintf(stderr,
290290
"Root value unexpected not an object for %s\n\n",
291-
path.string().c_str());
291+
ConvertPathToUTF8(path).c_str());
292292
} else {
293-
fprintf(stderr, "Can't parse %s\n", path.string().c_str());
293+
fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str());
294294
}
295295
result->exit_code_ = ExitCode::kGenericUserError;
296296
return;
@@ -299,8 +299,9 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
299299
// If package_json object doesn't have "scripts" field, throw an error.
300300
simdjson::ondemand::object scripts_object;
301301
if (main_object["scripts"].get_object().get(scripts_object)) {
302-
fprintf(
303-
stderr, "Can't find \"scripts\" field in %s\n", path.string().c_str());
302+
fprintf(stderr,
303+
"Can't find \"scripts\" field in %s\n",
304+
ConvertPathToUTF8(path).c_str());
304305
result->exit_code_ = ExitCode::kGenericUserError;
305306
return;
306307
}
@@ -314,13 +315,13 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
314315
"Script \"%.*s\" is unexpectedly not a string for %s\n\n",
315316
static_cast<int>(command_id.size()),
316317
command_id.data(),
317-
path.string().c_str());
318+
ConvertPathToUTF8(path).c_str());
318319
} else {
319320
fprintf(stderr,
320321
"Missing script: \"%.*s\" for %s\n\n",
321322
static_cast<int>(command_id.size()),
322323
command_id.data(),
323-
path.string().c_str());
324+
ConvertPathToUTF8(path).c_str());
324325
fprintf(stderr, "Available scripts are:\n");
325326

326327
// Reset the object to iterate over it again

‎test/parallel/test-node-run.js‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ common.requireNoPackageJSONAbove();
55

66
const { it, describe } = require('node:test');
77
const assert = require('node:assert');
8+
const fs = require('node:fs');
9+
const path = require('node:path');
810

911
const fixtures = require('../common/fixtures');
12+
const tmpdir = require('../common/tmpdir');
1013
const envSuffix = common.isWindows ? '-windows' : '';
1114

1215
describe('node --run [command]', { concurrency: !process.env.TEST_PARALLEL }, () => {
@@ -223,6 +226,45 @@ describe('node --run [command]', { concurrency: !process.env.TEST_PARALLEL }, ()
223226
assert.strictEqual(child.code, 0);
224227
});
225228

229+
it('handles package paths outside the active Windows code page',
230+
{ skip: !common.isWindows }, async () => {
231+
tmpdir.refresh();
232+
233+
const projectDir = path.join(tmpdir.path, 'node-run-\u{20BB7}');
234+
const packageJsonPath = path.join(projectDir, 'package.json');
235+
const nodeModulesBin = path.join(projectDir, 'node_modules', '.bin');
236+
const checkScript = path.join(projectDir, 'check.js');
237+
238+
fs.mkdirSync(nodeModulesBin, { recursive: true });
239+
fs.writeFileSync(packageJsonPath, JSON.stringify({
240+
scripts: {
241+
unicode: `"${process.execPath}" check.js`,
242+
},
243+
}));
244+
fs.writeFileSync(checkScript, `
245+
'use strict';
246+
console.log(JSON.stringify({
247+
cwd: process.cwd(),
248+
packageJsonPath: process.env.NODE_RUN_PACKAGE_JSON_PATH,
249+
path: process.env.PATH,
250+
}));
251+
`);
252+
253+
const child = await common.spawnPromisified(
254+
process.execPath,
255+
[ '--run', 'unicode'],
256+
{ cwd: projectDir },
257+
);
258+
259+
assert.strictEqual(child.stderr, '');
260+
assert.strictEqual(child.code, 0);
261+
262+
const output = JSON.parse(child.stdout);
263+
assert.strictEqual(output.cwd, projectDir);
264+
assert.strictEqual(output.packageJsonPath, packageJsonPath);
265+
assert.strictEqual(output.path.split(path.delimiter)[0], nodeModulesBin);
266+
});
267+
226268
it('returns error on unparsable file', async () => {
227269
const child = await common.spawnPromisified(
228270
process.execPath,

0 commit comments

Comments
 (0)