diff --git a/doc/api/cli.md b/doc/api/cli.md index a121cbea75a663..c896f4e6e04b03 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -642,6 +642,13 @@ if they had been specified on the command line before the actual command line (so they can be overridden). Node.js will exit with an error if an option that is not allowed in the environment is used, such as `-p` or a script file. +In case an option value happens to contain a space (for example a path listed in +`--require`), it must be escaped using double quotes. For example: + +```bash +--require "./my path/file.js" +``` + Node.js options that are allowed are: - `--enable-fips` - `--experimental-modules` diff --git a/src/node.cc b/src/node.cc index 7c0118758dfd94..b1296c09fe1818 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2610,23 +2610,49 @@ void Init(std::vector* argv, #if !defined(NODE_WITHOUT_NODE_OPTIONS) std::string node_options; - if (SafeGetenv("NODE_OPTIONS", &node_options)) { + + if (credentials::SafeGetenv("NODE_OPTIONS", &node_options)) { std::vector env_argv; // [0] is expected to be the program name, fill it in from the real argv. env_argv.push_back(argv->at(0)); - // Split NODE_OPTIONS at each ' ' character. - std::string::size_type index = std::string::npos; - do { - std::string::size_type prev_index = index; - index = node_options.find(' ', index + 1); - if (index - prev_index == 1) continue; - - const std::string option = node_options.substr( - prev_index + 1, index - prev_index - 1); - if (!option.empty()) - env_argv.emplace_back(std::move(option)); - } while (index != std::string::npos); + bool is_in_string = false; + bool will_start_new_arg = true; + for (std::string::size_type index = 0; + index < node_options.size(); + ++index) { + char c = node_options.at(index); + + // Backslashes escape the following character + if (c == '\\' && is_in_string) { + if (index + 1 == node_options.size()) { + errors->push_back("invalid value for NODE_OPTIONS " + "(invalid escape)\n"); + return 9; + } else { + c = node_options.at(++index); + } + } else if (c == ' ' && !is_in_string) { + will_start_new_arg = true; + continue; + } else if (c == '"') { + is_in_string = !is_in_string; + continue; + } + + if (will_start_new_arg) { + env_argv.push_back(std::string(1, c)); + will_start_new_arg = false; + } else { + env_argv.back() += c; + } + } + + if (is_in_string) { + errors->push_back("invalid value for NODE_OPTIONS " + "(unterminated string)\n"); + return 9; + } ProcessArgv(&env_argv, nullptr, true); diff --git a/test/fixtures/print A.js b/test/fixtures/print A.js new file mode 100644 index 00000000000000..30a56425de829c --- /dev/null +++ b/test/fixtures/print A.js @@ -0,0 +1 @@ +console.log('A') diff --git a/test/parallel/test-cli-node-options.js b/test/parallel/test-cli-node-options.js index 3e1d6808c835cc..fe4845ce11fcab 100644 --- a/test/parallel/test-cli-node-options.js +++ b/test/parallel/test-cli-node-options.js @@ -15,7 +15,12 @@ tmpdir.refresh(); process.chdir(tmpdir.path); const printA = require.resolve('../fixtures/printA.js'); +const printSpaceA = require.resolve('../fixtures/print A.js'); + +expect(` -r ${printA} `, 'A\nB\n'); expect(`-r ${printA}`, 'A\nB\n'); +expect(`-r ${JSON.stringify(printA)}`, 'A\nB\n'); +expect(`-r ${JSON.stringify(printSpaceA)}`, 'A\nB\n'); expect(`-r ${printA} -r ${printA}`, 'A\nB\n'); expect('--no-deprecation', 'B\n'); expect('--no-warnings', 'B\n');