@@ -242,10 +242,6 @@ func extractSedPattern(command string) (string, error) {
242242}
243243
244244func extractEchoString (command string ) (string , error ) {
245- // Match strings inside echo with single or double quotes
246- // Note: Ideally, the pattern should be `(?s)echo\s+(?:-e\s+)?(['"])(.*?)\1'`
247- // But the go built-in lib regexp doesn't support this backreferences.
248-
249245 // First try single quotes
250246 singleRe := regexp .MustCompile (`(?s)echo\s+(?:-e\s+)?'(.*?)'` )
251247 matches := singleRe .FindStringSubmatch (command )
@@ -261,7 +257,89 @@ func extractEchoString(command string) (string, error) {
261257 return matches [1 ], nil
262258 }
263259
264- return "" , fmt .Errorf ("no quoted string found in echo command" )
260+ // Check with need manual parsing
261+ // This handles cases like: echo 'hello "world" good morning'
262+ return extractEchoStringManual (command )
263+ }
264+
265+ // extractEchoStringManual uses manual parsing to ensure opening and closing quotes match
266+ // This handles cases like: echo 'hello "world" good morning'
267+ func extractEchoStringManual (command string ) (string , error ) {
268+ // Find the echo command and optional -e flag
269+ echoRe := regexp .MustCompile (`echo\s+(?:-e\s+)?` )
270+ loc := echoRe .FindStringIndex (command )
271+ if loc == nil {
272+ return "" , fmt .Errorf ("no echo command found" )
273+ }
274+
275+ // Get the rest of the string after 'echo' and optional '-e'
276+ rest := command [loc [1 ]:]
277+ rest = strings .TrimSpace (rest )
278+
279+ if len (rest ) == 0 {
280+ return "" , fmt .Errorf ("no quoted string found in echo command" )
281+ }
282+
283+ // Check if it starts with a quote
284+ if rest [0 ] != '\'' && rest [0 ] != '"' {
285+ return "" , fmt .Errorf ("echo string must start with a quote" )
286+ }
287+
288+ quoteChar := rest [0 ]
289+
290+ // Find the matching closing quote (same type as opening)
291+ escaped := false
292+ for i := 1 ; i < len (rest ); i ++ {
293+ if escaped {
294+ escaped = false
295+ continue
296+ }
297+ if rest [i ] == '\\' {
298+ escaped = true
299+ continue
300+ }
301+ if rest [i ] == quoteChar {
302+ // Found the matching closing quote - return WITH quotes
303+ return rest [0 : i + 1 ], nil
304+ }
305+ }
306+
307+ return "" , fmt .Errorf ("no matching closing quote found" )
308+ }
309+
310+ // findSeparatorOutsideQuotes finds the index of a separator that is not within quotes
311+ func findSeparatorOutsideQuotes (cmd string , sep string ) int {
312+ inSingleQuote := false
313+ inDoubleQuote := false
314+ escaped := false
315+
316+ for i := 0 ; i < len (cmd ); i ++ {
317+ if escaped {
318+ escaped = false
319+ continue
320+ }
321+
322+ if cmd [i ] == '\\' {
323+ escaped = true
324+ continue
325+ }
326+
327+ // Toggle quote states
328+ if cmd [i ] == '\'' && ! inDoubleQuote {
329+ inSingleQuote = ! inSingleQuote
330+ } else if cmd [i ] == '"' && ! inSingleQuote {
331+ inDoubleQuote = ! inDoubleQuote
332+ }
333+
334+ // Check if we found the separator outside quotes
335+ if ! inSingleQuote && ! inDoubleQuote {
336+ if i + len (sep ) <= len (cmd ) && cmd [i :i + len (sep )] == sep {
337+ return i
338+ }
339+ }
340+ }
341+
342+ return - 1
265343}
266344
267345func verifyCmdWithFullPath (cmd , chrootPath string ) (string , error ) {
@@ -290,7 +368,7 @@ func verifyCmdWithFullPath(cmd, chrootPath string) (string, error) {
290368 sepIdx := - 1
291369 sep := ""
292370 for _ , s := range separators {
293- if idx := strings . Index (cmd , s ); idx != - 1 && (sepIdx == - 1 || idx < sepIdx ) {
371+ if idx := findSeparatorOutsideQuotes (cmd , s ); idx != - 1 && (sepIdx == - 1 || idx < sepIdx ) {
294372 sepIdx = idx
295373 sep = s
296374 }
0 commit comments