package tools import ( "fmt" "strings" ) // shellQuote quotes a string for safe shell use. func shellQuote(s string) string { if s == "" { return "''" } // Simple single-quote escaping escaped := strings.ReplaceAll(s, "'", "'\"'\"'") return "'" + escaped + "'" } // containsString checks if s contains substr. func containsString(s, substr string) bool { return strings.Contains(s, substr) } // trimOutput trims whitespace from output. func trimOutput(s string) string { return strings.TrimSpace(s) } // sedEscapeLiteral escapes a literal string for use in a sed s/pattern/ context. // Escapes: / \ & . * [ ] ^ $ and newlines. func sedEscapeLiteral(s string) string { replacer := strings.NewReplacer( `\`, `\\`, `/`, `\/`, `&`, `\&`, `.`, `\.`, `*`, `\*`, `[`, `\[`, `]`, `\]`, `^`, `\^`, `$`, `\$`, "\n", `\n`, ) return replacer.Replace(s) } // sedEscapePattern escapes a regex pattern for use in sed, only escaping the delimiter. // The pattern is passed as-is for regex matching, only / and newlines are escaped. func sedEscapePattern(s string) string { replacer := strings.NewReplacer( `/`, `\/`, "\n", `\n`, ) return replacer.Replace(s) } // sedEscapeReplacement escapes a replacement string for sed s//replacement/ context. // Only escapes: / \ & and newlines (these have special meaning in sed replacements). func sedEscapeReplacement(s string) string { replacer := strings.NewReplacer( `\`, `\\`, `/`, `\/`, `&`, `\&`, "\n", `\n`, ) return replacer.Replace(s) } // sedEscapeInsertText escapes text for sed i\ or a\ commands. // Newlines need to be escaped with backslash continuation for multi-line inserts. func sedEscapeInsertText(s string) string { return strings.ReplaceAll(s, "\n", `\n`) } // sedInPlace builds a portable sed in-place edit command that works on // GNU sed (Linux), BSD sed (macOS/FreeBSD), and BusyBox sed (Alpine). // Uses sed -i.bak + rm for universal portability. // // Parameters: // - flags: extra sed flags like "-E", or "" for none // - expr: the sed expression WITHOUT single-quote wrapping (e.g., // "s/foo/bar/g"). sedInPlace shell-quotes it itself (via shellQuote), // so apostrophes in user content can't break the shell quoting. // - path: the raw file path (will be shell-quoted internally) func sedInPlace(flags, expr, path string) string { quotedExpr := shellQuote(expr) quotedPath := shellQuote(path) quotedBak := shellQuote(path + ".bak") if flags != "" { flags = " " + flags } return fmt.Sprintf("sed -i.bak%s %s %s 2>&1 && rm -f %s", flags, quotedExpr, quotedPath, quotedBak) } // sanitizeTsharkValue removes characters that could break tshark display filters. // Allows alphanumeric, dash, dot, @, underscore, plus, colon, and space. func sanitizeTsharkValue(s string) string { var b strings.Builder for _, r := range s { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '.' || r == '@' || r == '_' || r == '+' || r == ':' || r == ' ' { b.WriteRune(r) } } return b.String() } // sanitizeAlphanumeric validates that a string contains only safe characters. // Allows alphanumeric, dash, dot, and underscore. Used for network interface names, // grep keywords, and other values embedded inside sh -c strings. func sanitizeAlphanumeric(s string) string { var b strings.Builder for _, r := range s { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '.' || r == '_' { b.WriteRune(r) } } return b.String() } // sanitizeShellInnerPath validates a file path used inside sh -c '...' strings. // Rejects characters that could break out of single-quoted shell context or // enable command injection. Returns error for unsafe paths. func sanitizeShellInnerPath(s string) (string, error) { if s == "" { return "", fmt.Errorf("path cannot be empty") } for _, r := range s { if r == '\'' || r == '`' || r == ';' || r == '&' || r == '|' || r == '$' || r == '!' || r == '\n' || r == '\r' || r < 32 { return "", fmt.Errorf("invalid characters in path") } } return s, nil }