| 109 | } |
| 110 | |
| 111 | func SoftQuote(s string) string { |
| 112 | if s == "" { |
| 113 | return "\"\"" |
| 114 | } |
| 115 | |
| 116 | // Handle special case of ~ paths |
| 117 | if len(s) > 0 && s[0] == '~' { |
| 118 | // If it's just ~ or ~/something with no special chars, leave it as is |
| 119 | if len(s) == 1 || (len(s) > 1 && s[1] == '/' && safePattern.MatchString(s[2:])) { |
| 120 | return s |
| 121 | } |
| 122 | |
| 123 | // Otherwise quote everything after the ~ (including the /) |
| 124 | if len(s) > 1 && s[1] == '/' { |
| 125 | return "~" + SoftQuote(s[1:]) |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | if safePattern.MatchString(s) { |
| 130 | return s |
| 131 | } |
| 132 | |
| 133 | if !checkQuoteSize(s) { |
| 134 | return "" |
| 135 | } |
| 136 | |
| 137 | buf := make([]byte, 0, len(s)+5) |
| 138 | buf = append(buf, '"') |
| 139 | |
| 140 | for i := 0; i < len(s); i++ { |
| 141 | c := s[i] |
| 142 | // In soft quote, we don't escape $ to allow expansion |
| 143 | if c == '"' || c == '\\' || c == '`' { |
| 144 | buf = append(buf, '\\') |
| 145 | } |
| 146 | buf = append(buf, c) |
| 147 | } |
| 148 | |
| 149 | buf = append(buf, '"') |
| 150 | return string(buf) |
| 151 | } |
| 152 | |
| 153 | func checkQuoteSize(s string) bool { |
| 154 | if len(s) > MaxQuoteSize { |