mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
fix: stop extracting patterns at command flags and revert command-validation changes
This commit is contained in:
parent
8b4150e017
commit
627ea8462d
2 changed files with 174 additions and 27 deletions
|
|
@ -41,7 +41,7 @@ export function extractPatternsFromCommand(command: string): string[] {
|
|||
}
|
||||
|
||||
function isValidToken(token: string): boolean {
|
||||
return !!token && !token.match(/[/\\~:]/) && token !== "." && !token.match(/\.\w+$/)
|
||||
return !!token && !token.startsWith("-") && !token.match(/[/\\~:]/) && token !== "." && !token.match(/\.\w+$/)
|
||||
}
|
||||
|
||||
function extractFromTokens(tokens: string[], patterns: Set<string>): void {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { parse } from "shell-quote"
|
||||
|
||||
type ShellToken = string | { op: string } | { command: string }
|
||||
|
||||
/**
|
||||
* # Command Denylist Feature - Longest Prefix Match Strategy
|
||||
*
|
||||
|
|
@ -70,38 +72,183 @@ import { parse } from "shell-quote"
|
|||
export function parseCommand(command: string): string[] {
|
||||
if (!command?.trim()) return []
|
||||
|
||||
try {
|
||||
const parsed = parse(command)
|
||||
const commands: string[] = []
|
||||
let currentCommand: string[] = []
|
||||
// Split by newlines first (handle different line ending formats)
|
||||
// This regex splits on \r\n (Windows), \n (Unix), or \r (old Mac)
|
||||
const lines = command.split(/\r\n|\r|\n/)
|
||||
const allCommands: string[] = []
|
||||
|
||||
for (const token of parsed) {
|
||||
if (typeof token === "object" && "op" in token) {
|
||||
// Chain operator - split command
|
||||
if (["&&", "||", ";", "|"].includes(token.op)) {
|
||||
if (currentCommand.length > 0) {
|
||||
commands.push(currentCommand.join(" "))
|
||||
currentCommand = []
|
||||
}
|
||||
} else {
|
||||
// Other operators are part of the command
|
||||
currentCommand.push(token.op)
|
||||
for (const line of lines) {
|
||||
// Skip empty lines
|
||||
if (!line.trim()) continue
|
||||
|
||||
// Process each line through the existing parsing logic
|
||||
const lineCommands = parseCommandLine(line)
|
||||
allCommands.push(...lineCommands)
|
||||
}
|
||||
|
||||
return allCommands
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single line of commands (internal helper function)
|
||||
*/
|
||||
function parseCommandLine(command: string): string[] {
|
||||
if (!command?.trim()) return []
|
||||
|
||||
// Storage for replaced content
|
||||
const redirections: string[] = []
|
||||
const subshells: string[] = []
|
||||
const quotes: string[] = []
|
||||
const arrayIndexing: string[] = []
|
||||
const arithmeticExpressions: string[] = []
|
||||
const variables: string[] = []
|
||||
const parameterExpansions: string[] = []
|
||||
const processSubstitutions: string[] = []
|
||||
|
||||
// First handle PowerShell redirections by temporarily replacing them
|
||||
let processedCommand = command.replace(/\d*>&\d*/g, (match) => {
|
||||
redirections.push(match)
|
||||
return `__REDIR_${redirections.length - 1}__`
|
||||
})
|
||||
|
||||
// Handle arithmetic expressions: $((...)) pattern
|
||||
// Match the entire arithmetic expression including nested parentheses
|
||||
processedCommand = processedCommand.replace(/\$\(\([^)]*(?:\)[^)]*)*\)\)/g, (match) => {
|
||||
arithmeticExpressions.push(match)
|
||||
return `__ARITH_${arithmeticExpressions.length - 1}__`
|
||||
})
|
||||
|
||||
// Handle parameter expansions: ${...} patterns (including array indexing)
|
||||
// This covers ${var}, ${var:-default}, ${var:+alt}, ${#var}, ${var%pattern}, etc.
|
||||
processedCommand = processedCommand.replace(/\$\{[^}]+\}/g, (match) => {
|
||||
parameterExpansions.push(match)
|
||||
return `__PARAM_${parameterExpansions.length - 1}__`
|
||||
})
|
||||
|
||||
// Handle process substitutions: <(...) and >(...)
|
||||
processedCommand = processedCommand.replace(/[<>]\([^)]+\)/g, (match) => {
|
||||
processSubstitutions.push(match)
|
||||
return `__PROCSUB_${processSubstitutions.length - 1}__`
|
||||
})
|
||||
|
||||
// Handle simple variable references: $varname pattern
|
||||
// This prevents shell-quote from splitting $count into separate tokens
|
||||
processedCommand = processedCommand.replace(/\$[a-zA-Z_][a-zA-Z0-9_]*/g, (match) => {
|
||||
variables.push(match)
|
||||
return `__VAR_${variables.length - 1}__`
|
||||
})
|
||||
|
||||
// Handle special bash variables: $?, $!, $#, $$, $@, $*, $-, $0-$9
|
||||
processedCommand = processedCommand.replace(/\$[?!#$@*\-0-9]/g, (match) => {
|
||||
variables.push(match)
|
||||
return `__VAR_${variables.length - 1}__`
|
||||
})
|
||||
|
||||
// Then handle subshell commands
|
||||
processedCommand = processedCommand
|
||||
.replace(/\$\((.*?)\)/g, (_, inner) => {
|
||||
subshells.push(inner.trim())
|
||||
return `__SUBSH_${subshells.length - 1}__`
|
||||
})
|
||||
.replace(/`(.*?)`/g, (_, inner) => {
|
||||
subshells.push(inner.trim())
|
||||
return `__SUBSH_${subshells.length - 1}__`
|
||||
})
|
||||
|
||||
// Then handle quoted strings
|
||||
processedCommand = processedCommand.replace(/"[^"]*"/g, (match) => {
|
||||
quotes.push(match)
|
||||
return `__QUOTE_${quotes.length - 1}__`
|
||||
})
|
||||
|
||||
let tokens: ShellToken[]
|
||||
try {
|
||||
tokens = parse(processedCommand) as ShellToken[]
|
||||
} catch (error: any) {
|
||||
// If shell-quote fails to parse, fall back to simple splitting
|
||||
console.warn("shell-quote parse error:", error.message, "for command:", processedCommand)
|
||||
|
||||
// Simple fallback: split by common operators
|
||||
const fallbackCommands = processedCommand
|
||||
.split(/(?:&&|\|\||;|\|)/)
|
||||
.map((cmd) => cmd.trim())
|
||||
.filter((cmd) => cmd.length > 0)
|
||||
|
||||
// Restore all placeholders for each command
|
||||
return fallbackCommands.map((cmd) => {
|
||||
let result = cmd
|
||||
// Restore quotes
|
||||
result = result.replace(/__QUOTE_(\d+)__/g, (_, i) => quotes[parseInt(i)])
|
||||
// Restore redirections
|
||||
result = result.replace(/__REDIR_(\d+)__/g, (_, i) => redirections[parseInt(i)])
|
||||
// Restore array indexing expressions
|
||||
result = result.replace(/__ARRAY_(\d+)__/g, (_, i) => arrayIndexing[parseInt(i)])
|
||||
// Restore arithmetic expressions
|
||||
result = result.replace(/__ARITH_(\d+)__/g, (_, i) => arithmeticExpressions[parseInt(i)])
|
||||
// Restore parameter expansions
|
||||
result = result.replace(/__PARAM_(\d+)__/g, (_, i) => parameterExpansions[parseInt(i)])
|
||||
// Restore process substitutions
|
||||
result = result.replace(/__PROCSUB_(\d+)__/g, (_, i) => processSubstitutions[parseInt(i)])
|
||||
// Restore variable references
|
||||
result = result.replace(/__VAR_(\d+)__/g, (_, i) => variables[parseInt(i)])
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
const commands: string[] = []
|
||||
let currentCommand: string[] = []
|
||||
|
||||
for (const token of tokens) {
|
||||
if (typeof token === "object" && "op" in token) {
|
||||
// Chain operator - split command
|
||||
if (["&&", "||", ";", "|"].includes(token.op)) {
|
||||
if (currentCommand.length > 0) {
|
||||
commands.push(currentCommand.join(" "))
|
||||
currentCommand = []
|
||||
}
|
||||
} else if (typeof token === "string") {
|
||||
} else {
|
||||
// Other operators (>, &) are part of the command
|
||||
currentCommand.push(token.op)
|
||||
}
|
||||
} else if (typeof token === "string") {
|
||||
// Check if it's a subshell placeholder
|
||||
const subshellMatch = token.match(/__SUBSH_(\d+)__/)
|
||||
if (subshellMatch) {
|
||||
if (currentCommand.length > 0) {
|
||||
commands.push(currentCommand.join(" "))
|
||||
currentCommand = []
|
||||
}
|
||||
commands.push(subshells[parseInt(subshellMatch[1])])
|
||||
} else {
|
||||
currentCommand.push(token)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any remaining command
|
||||
if (currentCommand.length > 0) {
|
||||
commands.push(currentCommand.join(" "))
|
||||
}
|
||||
|
||||
return commands
|
||||
} catch (_error) {
|
||||
// If shell-quote fails, fall back to simple splitting
|
||||
return [command]
|
||||
}
|
||||
|
||||
// Add any remaining command
|
||||
if (currentCommand.length > 0) {
|
||||
commands.push(currentCommand.join(" "))
|
||||
}
|
||||
|
||||
// Restore quotes and redirections
|
||||
return commands.map((cmd) => {
|
||||
let result = cmd
|
||||
// Restore quotes
|
||||
result = result.replace(/__QUOTE_(\d+)__/g, (_, i) => quotes[parseInt(i)])
|
||||
// Restore redirections
|
||||
result = result.replace(/__REDIR_(\d+)__/g, (_, i) => redirections[parseInt(i)])
|
||||
// Restore array indexing expressions
|
||||
result = result.replace(/__ARRAY_(\d+)__/g, (_, i) => arrayIndexing[parseInt(i)])
|
||||
// Restore arithmetic expressions
|
||||
result = result.replace(/__ARITH_(\d+)__/g, (_, i) => arithmeticExpressions[parseInt(i)])
|
||||
// Restore parameter expansions
|
||||
result = result.replace(/__PARAM_(\d+)__/g, (_, i) => parameterExpansions[parseInt(i)])
|
||||
// Restore process substitutions
|
||||
result = result.replace(/__PROCSUB_(\d+)__/g, (_, i) => processSubstitutions[parseInt(i)])
|
||||
// Restore variable references
|
||||
result = result.replace(/__VAR_(\d+)__/g, (_, i) => variables[parseInt(i)])
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue