| 27 | const batchFileRegExp = /\.(?:bat|cmd)$/i; |
| 28 | |
| 29 | const escapeWindowsCommand = parsed => { |
| 30 | // Resolve the file to an absolute path, following its shebang to the interpreter if any |
| 31 | const resolvedFile = resolveWithShebang(parsed); |
| 32 | |
| 33 | // A directly executable file is spawned as-is, bypassing `cmd.exe` and its escaping |
| 34 | if (resolvedFile !== undefined && directlyExecutableRegExp.test(resolvedFile)) { |
| 35 | return parsed; |
| 36 | } |
| 37 | |
| 38 | /* |
| 39 | `cmd.exe` treats CR and LF as command separators and offers no way to escape them, so allowing either would enable command injection. |
| 40 | Reject them instead. |
| 41 | */ |
| 42 | for (const value of [parsed.file, ...parsed.commandArguments]) { |
| 43 | assertNoLineBreak(value); |
| 44 | } |
| 45 | |
| 46 | const isDoubleEscape = resolvedFile !== undefined && batchFileRegExp.test(resolvedFile); |
| 47 | // POSIX separators must become Windows ones (`foo/bar` -> `foo\bar`), otherwise resolution always fails with ENOENT |
| 48 | const escapedFile = escapeMetaChars(path.normalize(parsed.file)); |
| 49 | const escapedArguments = parsed.commandArguments.map(argument => escapeArgument(argument, isDoubleEscape)); |
| 50 | const commandLine = `"${[escapedFile, ...escapedArguments].join(' ')}"`; |
| 51 | |
| 52 | // Let `node:child_process` pass the already-escaped command line through untouched |
| 53 | parsed.options.windowsVerbatimArguments = true; |
| 54 | return { |
| 55 | file: process.env.comspec || 'cmd.exe', |
| 56 | commandArguments: ['/d', '/s', '/c', commandLine], |
| 57 | options: parsed.options, |
| 58 | }; |
| 59 | }; |
| 60 | |
| 61 | // Resolve the command's absolute path, then, if it is a shebang script, resolve its interpreter instead, since Windows cannot run shebangs natively |
| 62 | const resolveWithShebang = parsed => { |
no test coverage detected