fix: 修复 Windows trans 编码与参数传递
This commit is contained in:
@@ -5,6 +5,7 @@ if ($operation -in @('head', 'tail')) {
|
||||
if ($arg -in @('-n', '--lines')) { $i += 1; if ($i -ge $toolArgs.Count) { Fail ($arg + ' requires a value') 2 }; $linesWanted = Parse-NonNegativeInt $arg $toolArgs[$i] 10000; continue }
|
||||
if ($arg.StartsWith('-n') -and $arg.Length -gt 2) { $linesWanted = Parse-NonNegativeInt '-n' $arg.Substring(2) 10000; continue }
|
||||
if ($arg.StartsWith('--lines=')) { $linesWanted = Parse-NonNegativeInt '--lines' $arg.Substring(8) 10000; continue }
|
||||
if ($arg -match '^-(\d+)$') { $linesWanted = Parse-NonNegativeInt ('-' + $Matches[1]) $Matches[1] 10000; continue }
|
||||
if ($arg.StartsWith('-') -and $arg -ne '-') { Fail ('unsupported ' + $operation + ' option on Windows route: ' + $arg) 2 }
|
||||
$paths.Add($arg)
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ describe("ssh windows PowerShell safety prelude", () => {
|
||||
expect(prelude).toContain("function ConvertTo-Json");
|
||||
expect(prelude).toContain("'PSPath','PSParentPath','PSChildName','PSDrive','PSProvider','ReadCount'");
|
||||
expect(prelude).toContain("Microsoft.PowerShell.Utility\\ConvertTo-Json");
|
||||
expect(prelude).toContain("$PSDefaultParameterValues['Get-Content:Encoding'] = 'utf8'");
|
||||
expect(prelude).toContain("Set-Location -LiteralPath 'C:\\test'");
|
||||
});
|
||||
});
|
||||
@@ -114,6 +115,25 @@ describe("ssh windows fs read-only operations", () => {
|
||||
expect(decodedWindowsPowerShellCommand(cmd.parsed.remoteCommand)).toContain('python tool.py "71-FILTER 高频输出精度与频率跳变改进"');
|
||||
});
|
||||
|
||||
test("supports GNU head/tail counts and preserves PowerShell native argv", () => {
|
||||
const headScript = windowsFsReadOnlyScript("F:\\Work\\demo", "head", ["-35", "docs/read me.md"]);
|
||||
const tailScript = windowsFsReadOnlyScript("F:\\Work\\demo", "tail", ["-20", "docs/read me.md"]);
|
||||
const pythonCode = 'import pathlib; print({"utf8": pathlib.Path(r"docs/报告.md").read_text(encoding="utf-8") != ""})';
|
||||
const python = parseSshInvocation("D601:win/F/Work/demo", ["ps", "python", "-c", pythonCode]);
|
||||
const launcher = decodedWindowsPowerShellCommand(python.parsed.remoteCommand);
|
||||
const encodedPayload = launcher.match(/\$payloadJsonB64 = ''([A-Za-z0-9+/=]+)'';/u)?.[1];
|
||||
|
||||
expect(headScript).toContain("$arg -match '^-(\\d+)$'");
|
||||
expect(tailScript).toContain("$arg -match '^-(\\d+)$'");
|
||||
expect(python.parsed.invocationKind).toBe("argv");
|
||||
expect(encodedPayload).toBeDefined();
|
||||
expect(JSON.parse(Buffer.from(encodedPayload ?? "", "base64").toString("utf8"))).toEqual({
|
||||
command: "python",
|
||||
args: ["-c", pythonCode],
|
||||
nativeArguments: `"-c" "${pythonCode.replaceAll('"', '\\"')}"`,
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects unsupported Windows read operations instead of treating them as POSIX", () => {
|
||||
expect(() => parseSshInvocation("D601:win/c/test", ["sed", "-n", "1p", "hello.md"])).toThrow("unsupported ssh win operation: sed");
|
||||
});
|
||||
|
||||
+62
-4
@@ -501,11 +501,11 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
|
||||
if (operation === "ps" || operation === "powershell" || operation === "powershell.exe") {
|
||||
const commandArgs = args[1] === "--" ? args.slice(2) : args.slice(1);
|
||||
if (commandArgs.length >= 2 && (commandArgs[0] === "-File" || commandArgs[0] === "-file")) {
|
||||
const fileArgs = commandArgs.slice(1).map((a: string) => a.replace(/\\/g, "/")).join(" ");
|
||||
const fileArgs = [commandArgs[1]?.replace(/\\/g, "/") ?? "", ...commandArgs.slice(2)];
|
||||
return {
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsPowerShellInlineLauncherScript("& " + fileArgs, route.workspace)),
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsPowerShellArgvLauncherScript(fileArgs, route.workspace)),
|
||||
requiresStdin: false,
|
||||
invocationKind: "helper",
|
||||
invocationKind: "argv",
|
||||
};
|
||||
}
|
||||
if (commandArgs.length === 0) {
|
||||
@@ -515,8 +515,15 @@ function parseWinRouteArgs(route: ParsedSshRoute, args: string[]): ParsedSshArgs
|
||||
invocationKind: "helper",
|
||||
};
|
||||
}
|
||||
if (commandArgs.length > 1) {
|
||||
return {
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsPowerShellArgvLauncherScript(commandArgs, route.workspace)),
|
||||
requiresStdin: false,
|
||||
invocationKind: "argv",
|
||||
};
|
||||
}
|
||||
return {
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsPowerShellInlineLauncherScript(commandArgs.join(" "), route.workspace)),
|
||||
remoteCommand: buildWindowsPowerShellInvocation(buildWindowsPowerShellInlineLauncherScript(commandArgs[0] ?? "", route.workspace)),
|
||||
requiresStdin: false,
|
||||
invocationKind: "helper",
|
||||
};
|
||||
@@ -810,6 +817,7 @@ export function windowsPowerShellScriptPrelude(cwd: string | null): string {
|
||||
"[Console]::InputEncoding = [System.Text.UTF8Encoding]::new()",
|
||||
"[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()",
|
||||
"$OutputEncoding = [System.Text.UTF8Encoding]::new()",
|
||||
"$PSDefaultParameterValues['Get-Content:Encoding'] = 'utf8'",
|
||||
"$env:PYTHONUTF8 = '1'",
|
||||
"$env:PYTHONIOENCODING = 'utf-8'",
|
||||
...windowsPowerShellJsonSafetyPreludeLines,
|
||||
@@ -822,6 +830,56 @@ function buildWindowsPowerShellInlineLauncherScript(command: string, cwd: string
|
||||
return buildWindowsPowerShellScriptRunner(powerShellSingleQuote(command), cwd);
|
||||
}
|
||||
|
||||
function buildWindowsPowerShellArgvLauncherScript(commandArgs: string[], cwd: string | null): string {
|
||||
const command = commandArgs[0] ?? "";
|
||||
if (command.trim().length === 0) throw new Error("ssh win ps requires a command before argv arguments");
|
||||
const args = commandArgs.slice(1);
|
||||
const payloadJsonB64 = Buffer.from(JSON.stringify({
|
||||
command,
|
||||
args,
|
||||
nativeArguments: args.map(windowsNativeProcessArgument).join(" "),
|
||||
}), "utf8").toString("base64");
|
||||
const script = [
|
||||
`$payloadJsonB64 = ${powerShellSingleQuote(payloadJsonB64)};`,
|
||||
"$payload = ConvertFrom-Json -InputObject ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($payloadJsonB64)));",
|
||||
"$command = [string]$payload.command;",
|
||||
"$commandArgs = @();",
|
||||
"if ($null -ne $payload.args) { foreach ($item in @($payload.args)) { $commandArgs += [string]$item } }",
|
||||
"$resolvedCommand = Get-Command -Name $command -ErrorAction Stop;",
|
||||
"if ($resolvedCommand.CommandType -eq 'Application') {",
|
||||
" $startInfo = [Diagnostics.ProcessStartInfo]::new();",
|
||||
" $startInfo.FileName = $resolvedCommand.Source;",
|
||||
" $startInfo.Arguments = [string]$payload.nativeArguments;",
|
||||
" $startInfo.WorkingDirectory = (Get-Location).ProviderPath;",
|
||||
" $startInfo.UseShellExecute = $false;",
|
||||
" $process = [Diagnostics.Process]::new(); $process.StartInfo = $startInfo;",
|
||||
" if (-not $process.Start()) { throw ('failed to start native command: ' + $command) }",
|
||||
" $process.WaitForExit(); $global:LASTEXITCODE = $process.ExitCode;",
|
||||
"} else { & $command @commandArgs; }",
|
||||
].join(" ");
|
||||
return buildWindowsPowerShellScriptRunner(powerShellSingleQuote(script), cwd);
|
||||
}
|
||||
|
||||
function windowsNativeProcessArgument(value: string): string {
|
||||
if (value.length === 0) return '""';
|
||||
let quoted = '"';
|
||||
let backslashes = 0;
|
||||
for (const character of value) {
|
||||
if (character === "\\") {
|
||||
backslashes += 1;
|
||||
continue;
|
||||
}
|
||||
if (character === '"') {
|
||||
quoted += "\\".repeat(backslashes * 2 + 1) + '"';
|
||||
backslashes = 0;
|
||||
continue;
|
||||
}
|
||||
quoted += "\\".repeat(backslashes) + character;
|
||||
backslashes = 0;
|
||||
}
|
||||
return quoted + "\\".repeat(backslashes * 2) + '"';
|
||||
}
|
||||
|
||||
function buildWindowsPowerShellStdinLauncherScript(cwd: string | null): string {
|
||||
return buildWindowsPowerShellScriptRunner("[Console]::In.ReadToEnd()", cwd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user