6
6
namespace node ::task_runner {
7
7
8
8
#ifdef _WIN32
9
- static constexpr char bin_path[] = " \\ node_modules\\ .bin" ;
9
+ static constexpr const char * bin_path = " \\ node_modules\\ .bin" ;
10
10
#else
11
- static constexpr char bin_path[] = " /node_modules/.bin" ;
11
+ static constexpr const char * bin_path = " /node_modules/.bin" ;
12
12
#endif // _WIN32
13
13
14
- ProcessRunner::ProcessRunner (
15
- std::shared_ptr<InitializationResultImpl> result,
16
- std::string_view command,
17
- const std::optional<std::string>& positional_args) {
14
+ ProcessRunner::ProcessRunner (std::shared_ptr<InitializationResultImpl> result,
15
+ std::string_view command,
16
+ const PositionalArgs& positional_args) {
18
17
memset (&options_, 0 , sizeof (uv_process_options_t ));
19
18
20
19
// Get the current working directory.
@@ -54,10 +53,6 @@ ProcessRunner::ProcessRunner(
54
53
55
54
std::string command_str (command);
56
55
57
- if (positional_args.has_value ()) {
58
- command_str += " " + EscapeShell (positional_args.value ());
59
- }
60
-
61
56
// Set environment variables
62
57
uv_env_item_t * env_items;
63
58
int env_count;
@@ -69,33 +64,45 @@ ProcessRunner::ProcessRunner(
69
64
// ProcessRunner instance.
70
65
for (int i = 0 ; i < env_count; i++) {
71
66
std::string name = env_items[i].name ;
72
- std::string value = env_items[i].value ;
67
+ auto value = env_items[i].value ;
73
68
74
69
#ifdef _WIN32
75
70
// We use comspec environment variable to find cmd.exe path on Windows
76
71
// Example: 'C:\\Windows\\system32\\cmd.exe'
77
72
// If we don't find it, we fallback to 'cmd.exe' for Windows
78
- if (name. size () == 7 && StringEqualNoCaseN ( name.c_str (), " comspec" , 7 )) {
73
+ if (StringEqualNoCase ( name.c_str (), " comspec" )) {
79
74
file_ = value;
80
75
}
81
76
#endif // _WIN32
82
77
83
78
// Check if environment variable key is matching case-insensitive "path"
84
- if (name.size () == 4 && StringEqualNoCaseN (name.c_str (), " path" , 4 )) {
85
- value.insert (0 , current_bin_path);
79
+ if (StringEqualNoCase (name.c_str (), " path" )) {
80
+ env_vars_.push_back (name + " =" + current_bin_path + value);
81
+ } else {
82
+ // Environment variables should be in "KEY=value" format
83
+ env_vars_.push_back (name + " =" + value);
86
84
}
87
-
88
- // Environment variables should be in "KEY=value" format
89
- value.insert (0 , name + " =" );
90
- env_vars_.push_back (value);
91
85
}
92
86
uv_os_free_environ (env_items, env_count);
93
87
94
88
// Use the stored reference on the instance.
95
89
options_.file = file_.c_str ();
96
90
91
+ // Add positional arguments to the command string.
92
+ // Note that each argument needs to be escaped.
93
+ if (!positional_args.empty ()) {
94
+ for (const auto & arg : positional_args) {
95
+ command_str += " " + EscapeShell (arg);
96
+ }
97
+ }
98
+
97
99
#ifdef _WIN32
98
- if (file_.find (" cmd.exe" ) != std::string::npos) {
100
+ // We check whether file_ ends with cmd.exe in a case-insensitive manner.
101
+ // C++20 provides ends_with, but we roll our own for compatibility.
102
+ const char * cmdexe = " cmd.exe" ;
103
+ if (file_.size () >= strlen (cmdexe) &&
104
+ StringEqualNoCase (cmdexe,
105
+ file_.c_str () + file_.size () - strlen (cmdexe))) {
99
106
// If the file is cmd.exe, use the following command line arguments:
100
107
// "/c" Carries out the command and exit.
101
108
// "/d" Disables execution of AutoRun commands.
@@ -104,6 +111,9 @@ ProcessRunner::ProcessRunner(
104
111
command_args_ = {
105
112
options_.file , " /d" , " /s" , " /c" , " \" " + command_str + " \" " };
106
113
} else {
114
+ // If the file is not cmd.exe, and it is unclear wich shell is being used,
115
+ // so assume -c is the correct syntax (Unix-like shells use -c for this
116
+ // purpose).
107
117
command_args_ = {options_.file , " -c" , command_str};
108
118
}
109
119
#else
@@ -128,7 +138,7 @@ ProcessRunner::ProcessRunner(
128
138
// EscapeShell escapes a string to be used as a command line argument.
129
139
// It replaces single quotes with "\\'" and double quotes with "\\\"".
130
140
// It also removes excessive quote pairs and handles edge cases.
131
- std::string EscapeShell (const std::string& input) {
141
+ std::string EscapeShell (const std::string_view input) {
132
142
// If the input is an empty string, return a pair of quotes
133
143
if (input.empty ()) {
134
144
return " ''" ;
@@ -140,11 +150,12 @@ std::string EscapeShell(const std::string& input) {
140
150
// Check if input contains any forbidden characters
141
151
// If it doesn't, return the input as is.
142
152
if (input.find_first_of (forbidden_characters) == std::string::npos) {
143
- return input;
153
+ return std::string ( input) ;
144
154
}
145
155
146
156
// Replace single quotes("'") with "\\'"
147
- std::string escaped = std::regex_replace (input, std::regex (" '" ), " \\ '" );
157
+ std::string escaped =
158
+ std::regex_replace (std::string (input), std::regex (" '" ), " \\ '" );
148
159
149
160
// Wrap the result in single quotes
150
161
escaped = " '" + escaped + " '" ;
@@ -188,7 +199,7 @@ void ProcessRunner::Run() {
188
199
189
200
void RunTask (std::shared_ptr<InitializationResultImpl> result,
190
201
std::string_view command_id,
191
- const std::optional <std::string >& positional_args) {
202
+ const std::vector <std::string_view >& positional_args) {
192
203
std::string_view path = " package.json" ;
193
204
std::string raw_json;
194
205
@@ -256,20 +267,21 @@ void RunTask(std::shared_ptr<InitializationResultImpl> result,
256
267
// If the "--" flag is not found, it returns an empty optional.
257
268
// Otherwise, it returns the positional arguments as a single string.
258
269
// Example: "node -- script.js arg1 arg2" returns "arg1 arg2".
259
- std::optional<std::string> GetPositionalArgs (
260
- const std::vector<std::string>& args) {
270
+ PositionalArgs GetPositionalArgs (const std::vector<std::string>& args) {
261
271
// If the "--" flag is not found, return an empty optional
262
272
// Otherwise, return the positional arguments as a single string
263
273
if (auto dash_dash = std::find (args.begin (), args.end (), " --" );
264
274
dash_dash != args.end ()) {
265
- std::string positional_args;
275
+ PositionalArgs positional_args{} ;
266
276
for (auto it = dash_dash + 1 ; it != args.end (); ++it) {
267
- positional_args += it->c_str ();
277
+ // SAFETY: The following code is safe because the lifetime of the arguments
278
+ // is guaranteed to be valid until the end of the task runner.
279
+ positional_args.push_back (std::string_view (it->c_str (), it->size ()));
268
280
}
269
281
return positional_args;
270
282
}
271
283
272
- return std::nullopt ;
284
+ return {} ;
273
285
}
274
286
275
287
} // namespace node::task_runner
0 commit comments