Compare commits

...

1 Commits

Author SHA1 Message Date
David Wiesen
e7eb7ae0b8 powershell: normalize literal comma-array args 2026-05-19 09:45:56 -07:00
3 changed files with 104 additions and 19 deletions

View File

@@ -70,6 +70,34 @@ fn commands_for_exec_policy_parses_powershell_shell_wrapper() {
);
}
#[test]
fn commands_for_exec_policy_normalizes_powershell_literal_comma_arrays() {
let command = vec![
"powershell.exe".to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"gh issue view 18861 --repo openai/codex --json number,title,state".to_string(),
];
assert_eq!(
commands_for_exec_policy(&command),
ExecPolicyCommands {
commands: vec![vec![
"gh".to_string(),
"issue".to_string(),
"view".to_string(),
"18861".to_string(),
"-repo".to_string(),
"openai/codex".to_string(),
"-json".to_string(),
"number,title,state".to_string(),
]],
used_complex_parsing: false,
command_origin: ExecPolicyCommandOrigin::PowerShell,
}
);
}
#[test]
fn unmatched_safe_powershell_words_are_allowed() {
let command = vec!["Get-Content".to_string(), "Cargo.toml".to_string()];

View File

@@ -88,24 +88,47 @@ function Write-Response {
$stdout.WriteLine(($Response | ConvertTo-Json -Compress -Depth 3))
}
function Convert-LiteralCommandValue {
param($expression)
if ($expression -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
return $expression.Value
}
if ($expression -is [System.Management.Automation.Language.ExpandableStringExpressionAst]) {
if ($expression.NestedExpressions.Count -gt 0) {
return $null
}
return $expression.Value
}
if ($expression -is [System.Management.Automation.Language.ConstantExpressionAst]) {
return $expression.Value.ToString()
}
if ($expression.GetType().FullName -eq 'System.Management.Automation.Language.ArrayLiteralAst') {
$parts = [System.Collections.Generic.List[string]]::new()
foreach ($element in $expression.Elements) {
$part = Convert-LiteralCommandValue $element
if ($part -eq $null) {
return $null
}
$parts.Add($part)
}
return [string]::Join(',', $parts)
}
return $null
}
function Convert-CommandElement {
param($element)
# Accept only literal-ish command elements. Variable expansion, subexpressions, splats,
# and other dynamic forms return $null so the whole request becomes unsupported.
if ($element -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
return @($element.Value)
}
if ($element -is [System.Management.Automation.Language.ExpandableStringExpressionAst]) {
if ($element.NestedExpressions.Count -gt 0) {
return $null
}
return @($element.Value)
}
if ($element -is [System.Management.Automation.Language.ConstantExpressionAst]) {
return @($element.Value.ToString())
$literalValue = Convert-LiteralCommandValue $element
if ($literalValue -ne $null) {
return @($literalValue)
}
if ($element -is [System.Management.Automation.Language.CommandParameterAst]) {
@@ -113,17 +136,25 @@ function Convert-CommandElement {
return @('-' + $element.ParameterName)
}
if ($element.Argument -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
return @('-' + $element.ParameterName, $element.Argument.Value)
}
if ($element.Argument -is [System.Management.Automation.Language.ConstantExpressionAst]) {
return @('-' + $element.ParameterName, $element.Argument.Value.ToString())
$argumentValue = Convert-LiteralCommandValue $element.Argument
if ($argumentValue -ne $null) {
return @('-' + $element.ParameterName, $argumentValue)
}
return $null
}
if ($element -is [System.Management.Automation.Language.CommandExpressionAst]) {
if ($element.Redirections.Count -gt 0) {
return $null
}
$expressionValue = Convert-LiteralCommandValue $element.Expression
if ($expressionValue -ne $null) {
return @($expressionValue)
}
}
return $null
}

View File

@@ -235,4 +235,30 @@ mod tests {
]
);
}
#[cfg(windows)]
#[test]
fn parses_literal_comma_separated_argument_as_one_word() {
let commands = parse_powershell_command_into_plain_commands(&[
"powershell.exe".to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"gh issue view 18861 --repo openai/codex --json number,title,state".to_string(),
])
.expect("parse");
assert_eq!(
commands,
vec![vec![
"gh".to_string(),
"issue".to_string(),
"view".to_string(),
"18861".to_string(),
"-repo".to_string(),
"openai/codex".to_string(),
"-json".to_string(),
"number,title,state".to_string(),
]]
);
}
}