Skip to content

Commit a91aeff

Browse files
committed
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 <180910180+Archkon@users.noreply.github.com>
1 parent 598693b commit a91aeff

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
@@ -123,7 +123,7 @@ void ProcessRunner::SetEnvironmentVariables() {
123123
// Add NODE_RUN_PACKAGE_JSON_PATH environment variable to the environment to
124124
// indicate which package.json is being processed.
125125
env_vars_.push_back("NODE_RUN_PACKAGE_JSON_PATH=" +
126-
package_json_path_.string());
126+
ConvertPathToUTF8(package_json_path_));
127127

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

207207
void ProcessRunner::Run() {
208208
// keeps the string alive until destructor
209-
cwd_ = package_json_path_.parent_path().string();
209+
cwd_ = ConvertPathToUTF8(package_json_path_.parent_path());
210210
options_.cwd = cwd_.c_str();
211211
if (int r = uv_spawn(loop_, &process_, &options_)) {
212212
fprintf(stderr, "Error: %s\n", uv_strerror(r));
@@ -228,14 +228,14 @@ FindPackageJson(const std::filesystem::path& cwd) {
228228
// Append "path/node_modules/.bin" to the env var, if it is a directory.
229229
auto node_modules_bin = directory_path / "node_modules" / ".bin";
230230
if (std::filesystem::is_directory(node_modules_bin)) {
231-
path_env_var += node_modules_bin.string() + env_var_separator;
231+
path_env_var += ConvertPathToUTF8(node_modules_bin) + env_var_separator;
232232
}
233233

234234
if (raw_content.empty()) {
235235
package_json_path = directory_path / "package.json";
236236
// This is required for Windows because std::filesystem::path::c_str()
237237
// returns wchar_t* on Windows, and char* on other platforms.
238-
std::string contents = package_json_path.string();
238+
std::string contents = ConvertPathToUTF8(package_json_path);
239239
USE(ReadFileSync(&raw_content, contents.c_str()) > 0);
240240
}
241241
}
@@ -258,7 +258,7 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
258258
if (!package_json.has_value()) {
259259
fprintf(stderr,
260260
"Can't find package.json for directory %s\n",
261-
cwd.string().c_str());
261+
ConvertPathToUTF8(cwd).c_str());
262262
result->exit_code_ = ExitCode::kGenericUserError;
263263
return;
264264
}
@@ -274,7 +274,7 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
274274
simdjson::ondemand::object main_object;
275275

276276
if (json_parser.iterate(raw_json).get(document)) {
277-
fprintf(stderr, "Can't parse %s\n", path.string().c_str());
277+
fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str());
278278
result->exit_code_ = ExitCode::kGenericUserError;
279279
return;
280280
}
@@ -283,9 +283,9 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
283283
if (root_error == simdjson::error_code::INCORRECT_TYPE) {
284284
fprintf(stderr,
285285
"Root value unexpected not an object for %s\n\n",
286-
path.string().c_str());
286+
ConvertPathToUTF8(path).c_str());
287287
} else {
288-
fprintf(stderr, "Can't parse %s\n", path.string().c_str());
288+
fprintf(stderr, "Can't parse %s\n", ConvertPathToUTF8(path).c_str());
289289
}
290290
result->exit_code_ = ExitCode::kGenericUserError;
291291
return;
@@ -294,8 +294,9 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
294294
// If package_json object doesn't have "scripts" field, throw an error.
295295
simdjson::ondemand::object scripts_object;
296296
if (main_object["scripts"].get_object().get(scripts_object)) {
297-
fprintf(
298-
stderr, "Can't find \"scripts\" field in %s\n", path.string().c_str());
297+
fprintf(stderr,
298+
"Can't find \"scripts\" field in %s\n",
299+
ConvertPathToUTF8(path).c_str());
299300
result->exit_code_ = ExitCode::kGenericUserError;
300301
return;
301302
}
@@ -309,13 +310,13 @@ void RunTask(const std::shared_ptr<InitializationResultImpl>& result,
309310
"Script \"%.*s\" is unexpectedly not a string for %s\n\n",
310311
static_cast<int>(command_id.size()),
311312
command_id.data(),
312-
path.string().c_str());
313+
ConvertPathToUTF8(path).c_str());
313314
} else {
314315
fprintf(stderr,
315316
"Missing script: \"%.*s\" for %s\n\n",
316317
static_cast<int>(command_id.size()),
317318
command_id.data(),
318-
path.string().c_str());
319+
ConvertPathToUTF8(path).c_str());
319320
fprintf(stderr, "Available scripts are:\n");
320321

321322
// 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]', () => {
@@ -201,6 +204,45 @@ describe('node --run [command]', () => {
201204
assert.strictEqual(child.code, 0);
202205
});
203206

207+
it('handles package paths outside the active Windows code page',
208+
{ skip: !common.isWindows }, async () => {
209+
tmpdir.refresh();
210+
211+
const projectDir = path.join(tmpdir.path, 'node-run-\u{20BB7}');
212+
const packageJsonPath = path.join(projectDir, 'package.json');
213+
const nodeModulesBin = path.join(projectDir, 'node_modules', '.bin');
214+
const checkScript = path.join(projectDir, 'check.js');
215+
216+
fs.mkdirSync(nodeModulesBin, { recursive: true });
217+
fs.writeFileSync(packageJsonPath, JSON.stringify({
218+
scripts: {
219+
unicode: `"${process.execPath}" check.js`,
220+
},
221+
}));
222+
fs.writeFileSync(checkScript, `
223+
'use strict';
224+
console.log(JSON.stringify({
225+
cwd: process.cwd(),
226+
packageJsonPath: process.env.NODE_RUN_PACKAGE_JSON_PATH,
227+
path: process.env.PATH,
228+
}));
229+
`);
230+
231+
const child = await common.spawnPromisified(
232+
process.execPath,
233+
[ '--run', 'unicode'],
234+
{ cwd: projectDir },
235+
);
236+
237+
assert.strictEqual(child.stderr, '');
238+
assert.strictEqual(child.code, 0);
239+
240+
const output = JSON.parse(child.stdout);
241+
assert.strictEqual(output.cwd, projectDir);
242+
assert.strictEqual(output.packageJsonPath, packageJsonPath);
243+
assert.strictEqual(output.path.split(path.delimiter)[0], nodeModulesBin);
244+
});
245+
204246
it('returns error on unparsable file', async () => {
205247
const child = await common.spawnPromisified(
206248
process.execPath,

0 commit comments

Comments
 (0)