Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes NODE_OPTIONS when spaces are escaped #24065

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/cli.md
Expand Up @@ -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`
Expand Down
46 changes: 42 additions & 4 deletions src/node.cc
Expand Up @@ -671,11 +671,49 @@ int InitializeNodeWithArgs(std::vector<std::string>* 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<std::string> env_argv = SplitString("x " + node_options, ' ');
env_argv[0] = argv->at(0);
std::vector<std::string> 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have SplitString(), why don't just fix then use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that SplitString should contain an actual parsing logic, or double quote/backslash support, does it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference this is the other place where SplitString is used - while it wouldn't be broken by using the same logic, in this case it sounds like it would be slightly overkill:

SplitString(per_process::cli_options->trace_event_categories, ',');

Copy link
Member

@himself65 himself65 Mar 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I think it is necessary to abstract common functions like SplitString and we should maintain these functions instead of hacking code everywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they have the same semantical requirements I'm all for it, but in this case it just doesn't seem the case? I can abstract this behavior in a separate function (SmartSplitString), but it'll eventually be used in a single place.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arcanis I think having a common function is probably a good idea. How about SplitString check's the delimiter specified and does the new logic only if it is a space? In that way we still have a common function which will do the right thing if you use a space, but otherwise we don't have extra overhead?

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;
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/print A.js
@@ -0,0 +1 @@
console.log('A')
5 changes: 5 additions & 0 deletions test/parallel/test-cli-node-options.js
Expand Up @@ -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');
Expand Down