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