diff --git a/doc/api/cli.md b/doc/api/cli.md index 79f2dcd2ba4596..a2c92b13341df0 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -774,6 +774,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: - `--diagnostic-report-directory` - `--diagnostic-report-filename` diff --git a/src/node.cc b/src/node.cc index 53a9ae397bf20b..c3b0a50e380ca5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -671,11 +671,49 @@ int InitializeNodeWithArgs(std::vector* argv, #if !defined(NODE_WITHOUT_NODE_OPTIONS) std::string node_options; + if (credentials::SafeGetenv("NODE_OPTIONS", &node_options)) { - // [0] is expected to be the program name, fill it in from the real argv - // and use 'x' as a placeholder while parsing. - std::vector env_argv = SplitString("x " + node_options, ' '); - env_argv[0] = argv->at(0); + 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)); + + 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; + } const int exit_code = ProcessGlobalArgs(&env_argv, nullptr, errors, true); if (exit_code != 0) return exit_code; 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 d3d1938bf8a096..07b69c5b130ea5 100644 --- a/test/parallel/test-cli-node-options.js +++ b/test/parallel/test-cli-node-options.js @@ -12,7 +12,12 @@ const tmpdir = require('../common/tmpdir'); tmpdir.refresh(); 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(` -r ${printA} -r ${printA}`, 'A\nB\n'); expect(` --require ${printA} --require ${printA}`, 'A\nB\n');