mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
fix(cli): 修复 Windows 路径反斜杠被吞掉的问题并添加跨平台路径支持
- 重写 escapeWindowsArg 函数,正确处理反斜杠和引号转义 - 添加 escapeUnixArg 函数支持 Linux/macOS shell 转义 - 添加 normalizePathSeparators 函数自动转换路径分隔符 - 修复 vscode-lsp.ts 中的 TypeScript 类型错误
This commit is contained in:
@@ -1,30 +1,117 @@
|
||||
/**
|
||||
* Windows cmd.exe argument escaping for spawn({ shell: true }).
|
||||
* Cross-platform shell argument escaping utilities.
|
||||
*
|
||||
* This utility escapes cmd.exe metacharacters using caret (^) so that user
|
||||
* controlled input cannot inject additional commands.
|
||||
* Provides proper escaping for command-line arguments when using spawn({ shell: true })
|
||||
* on different platforms:
|
||||
* - Windows: cmd.exe metacharacter handling with proper quote escaping
|
||||
* - Unix (Linux/macOS): POSIX shell escaping with single quotes
|
||||
*/
|
||||
|
||||
const WINDOWS_METACHARS = /[&|<>()%!"]/g;
|
||||
|
||||
/**
|
||||
* Escape a command-line argument for Windows cmd.exe.
|
||||
* Follows Microsoft C/C++ runtime argv parsing rules.
|
||||
*
|
||||
* Rules:
|
||||
* 1. Arguments containing spaces, tabs, or quotes must be quoted
|
||||
* 2. Backslashes are literal unless followed by a quote
|
||||
* 3. To include a literal quote, use \"
|
||||
* 4. Backslashes before a quote must be doubled: \\" produces \"
|
||||
* 5. Trailing backslashes must be doubled when the arg is quoted
|
||||
*
|
||||
* @param arg - The argument to escape
|
||||
* @returns Properly escaped argument safe for Windows command line
|
||||
*/
|
||||
export function escapeWindowsArg(arg: string): string {
|
||||
if (arg === '') return '""';
|
||||
|
||||
// Normalize newlines to spaces to prevent cmd.exe from
|
||||
// misinterpreting multiline arguments (breaks argument parsing)
|
||||
let sanitizedArg = arg.replace(/\r?\n/g, ' ');
|
||||
// Normalize newlines to spaces to prevent cmd.exe issues
|
||||
arg = arg.replace(/\r?\n/g, ' ');
|
||||
|
||||
// Escape caret first to avoid double-escaping when prefixing other metachars.
|
||||
let escaped = sanitizedArg.replace(/\^/g, '^^');
|
||||
// Check if the argument needs quoting
|
||||
// Characters that require quoting: space, tab, double quote, or backslash
|
||||
const needsQuoting = /[ \t"\\]/.test(arg);
|
||||
|
||||
// Escape cmd.exe metacharacters with caret.
|
||||
escaped = escaped.replace(WINDOWS_METACHARS, '^$&');
|
||||
|
||||
// Wrap whitespace-containing args in double quotes.
|
||||
if (/\s/.test(escaped)) {
|
||||
escaped = `"${escaped}"`;
|
||||
if (!needsQuoting) {
|
||||
// No special characters - return as is
|
||||
return arg;
|
||||
}
|
||||
|
||||
return escaped;
|
||||
// Build the escaped string with proper quoting
|
||||
let result = '"';
|
||||
|
||||
for (let i = 0; i < arg.length; i++) {
|
||||
// Count consecutive backslashes
|
||||
let backslashes = 0;
|
||||
while (i < arg.length && arg[i] === '\\') {
|
||||
backslashes++;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === arg.length) {
|
||||
// Trailing backslashes: double them all (they precede the closing quote)
|
||||
result += '\\'.repeat(backslashes * 2);
|
||||
break;
|
||||
}
|
||||
|
||||
if (arg[i] === '"') {
|
||||
// Backslashes followed by a quote: double the backslashes and escape the quote
|
||||
result += '\\'.repeat(backslashes * 2 + 1) + '"';
|
||||
} else {
|
||||
// Backslashes not followed by a quote: they are literal
|
||||
result += '\\'.repeat(backslashes) + arg[i];
|
||||
}
|
||||
}
|
||||
|
||||
result += '"';
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a command-line argument for Unix shells (bash, sh, zsh).
|
||||
* Uses single quotes which treat all characters literally except single quote itself.
|
||||
*
|
||||
* Strategy:
|
||||
* - Wrap argument in single quotes
|
||||
* - Replace any single quotes with: '\'' (end quote, escaped quote, start quote)
|
||||
*
|
||||
* @param arg - The argument to escape
|
||||
* @returns Properly escaped argument safe for Unix shell
|
||||
*/
|
||||
export function escapeUnixArg(arg: string): string {
|
||||
if (arg === '') return "''";
|
||||
|
||||
// If no special characters, return as-is
|
||||
// Safe characters: alphanumeric, hyphen, underscore, dot, forward slash, colon
|
||||
if (/^[a-zA-Z0-9_.\/:@=-]+$/.test(arg)) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
// Use single quotes and escape any single quotes within
|
||||
// 'arg' -> 'arg'\''s' for "arg's"
|
||||
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a command-line argument for the current platform.
|
||||
* Automatically selects the appropriate escaping method based on OS.
|
||||
*
|
||||
* @param arg - The argument to escape
|
||||
* @returns Properly escaped argument for the current platform's shell
|
||||
*/
|
||||
export function escapeShellArg(arg: string): string {
|
||||
if (process.platform === 'win32') {
|
||||
return escapeWindowsArg(arg);
|
||||
}
|
||||
return escapeUnixArg(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need shell escaping on the current platform.
|
||||
* Windows always needs escaping when shell: true due to cmd.exe.
|
||||
* Unix typically doesn't need escaping when shell: false.
|
||||
*
|
||||
* @returns true if shell escaping should be applied
|
||||
*/
|
||||
export function needsShellEscaping(): boolean {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user