waveterm/pkg/util/shellutil/shellquote.go
Evan Simkowitz 209647f343
Handle integer overflow cases in shellquote (#1822)
Resolves CodeQL warnings
2025-01-23 18:18:44 -08:00

162 lines
2.7 KiB
Go

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package shellutil
import (
"log"
"regexp"
)
const (
MaxQuoteSize = 10000000 // 10MB
)
var (
safePattern = regexp.MustCompile(`^[a-zA-Z0-9_/.-]+$`)
envVarNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
)
func IsValidEnvVarName(name string) bool {
return envVarNamePattern.MatchString(name)
}
func HardQuote(s string) string {
if s == "" {
return "\"\""
}
if safePattern.MatchString(s) {
return s
}
if !checkQuoteSize(s) {
return ""
}
buf := make([]byte, 0, len(s)+5)
buf = append(buf, '"')
for i := 0; i < len(s); i++ {
switch s[i] {
case '"', '\\', '$', '`':
buf = append(buf, '\\', s[i])
case '\n':
buf = append(buf, '\\', '\n')
default:
buf = append(buf, s[i])
}
}
buf = append(buf, '"')
return string(buf)
}
// does not encode newlines or backticks
func HardQuoteFish(s string) string {
if s == "" {
return "\"\""
}
if safePattern.MatchString(s) {
return s
}
if !checkQuoteSize(s) {
return ""
}
buf := make([]byte, 0, len(s)+5)
buf = append(buf, '"')
for i := 0; i < len(s); i++ {
switch s[i] {
case '"', '\\', '$':
buf = append(buf, '\\', s[i])
default:
buf = append(buf, s[i])
}
}
buf = append(buf, '"')
return string(buf)
}
func HardQuotePowerShell(s string) string {
if s == "" {
return "\"\""
}
if !checkQuoteSize(s) {
return ""
}
buf := make([]byte, 0, len(s)+5)
buf = append(buf, '"')
for i := 0; i < len(s); i++ {
c := s[i]
// In PowerShell, backtick (`) is the escape character
switch c {
case '"', '`', '$':
buf = append(buf, '`')
case '\n':
buf = append(buf, '`', 'n') // PowerShell uses `n for newline
}
buf = append(buf, c)
}
buf = append(buf, '"')
return string(buf)
}
func SoftQuote(s string) string {
if s == "" {
return "\"\""
}
// Handle special case of ~ paths
if len(s) > 0 && s[0] == '~' {
// If it's just ~ or ~/something with no special chars, leave it as is
if len(s) == 1 || (len(s) > 1 && s[1] == '/' && safePattern.MatchString(s[2:])) {
return s
}
// Otherwise quote everything after the ~ (including the /)
if len(s) > 1 && s[1] == '/' {
return "~" + SoftQuote(s[1:])
}
}
if safePattern.MatchString(s) {
return s
}
if !checkQuoteSize(s) {
return ""
}
buf := make([]byte, 0, len(s)+5)
buf = append(buf, '"')
for i := 0; i < len(s); i++ {
c := s[i]
// In soft quote, we don't escape $ to allow expansion
if c == '"' || c == '\\' || c == '`' {
buf = append(buf, '\\')
}
buf = append(buf, c)
}
buf = append(buf, '"')
return string(buf)
}
func checkQuoteSize(s string) bool {
if len(s) > MaxQuoteSize {
log.Printf("string too long to quote: %s", s)
return false
}
return true
}