mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-26 17:34:11 -05:00
e86a0bf3fe
* Add support for extra sendmail arguments * Sendmail args to exec.command should be a list * Add go-shellquote package * Use go-shellquote lib for parsing Sendmail args * Only parse if sendmail is configured
102 lines
2.3 KiB
Go
102 lines
2.3 KiB
Go
package shellquote
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Join quotes each argument and joins them with a space.
|
|
// If passed to /bin/sh, the resulting string will be split back into the
|
|
// original arguments.
|
|
func Join(args ...string) string {
|
|
var buf bytes.Buffer
|
|
for i, arg := range args {
|
|
if i != 0 {
|
|
buf.WriteByte(' ')
|
|
}
|
|
quote(arg, &buf)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
const (
|
|
specialChars = "\\'\"`${[|&;<>()*?!"
|
|
extraSpecialChars = " \t\n"
|
|
prefixChars = "~"
|
|
)
|
|
|
|
func quote(word string, buf *bytes.Buffer) {
|
|
// We want to try to produce a "nice" output. As such, we will
|
|
// backslash-escape most characters, but if we encounter a space, or if we
|
|
// encounter an extra-special char (which doesn't work with
|
|
// backslash-escaping) we switch over to quoting the whole word. We do this
|
|
// with a space because it's typically easier for people to read multi-word
|
|
// arguments when quoted with a space rather than with ugly backslashes
|
|
// everywhere.
|
|
origLen := buf.Len()
|
|
|
|
if len(word) == 0 {
|
|
// oops, no content
|
|
buf.WriteString("''")
|
|
return
|
|
}
|
|
|
|
cur, prev := word, word
|
|
atStart := true
|
|
for len(cur) > 0 {
|
|
c, l := utf8.DecodeRuneInString(cur)
|
|
cur = cur[l:]
|
|
if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) {
|
|
// copy the non-special chars up to this point
|
|
if len(cur) < len(prev) {
|
|
buf.WriteString(prev[0 : len(prev)-len(cur)-l])
|
|
}
|
|
buf.WriteByte('\\')
|
|
buf.WriteRune(c)
|
|
prev = cur
|
|
} else if strings.ContainsRune(extraSpecialChars, c) {
|
|
// start over in quote mode
|
|
buf.Truncate(origLen)
|
|
goto quote
|
|
}
|
|
atStart = false
|
|
}
|
|
if len(prev) > 0 {
|
|
buf.WriteString(prev)
|
|
}
|
|
return
|
|
|
|
quote:
|
|
// quote mode
|
|
// Use single-quotes, but if we find a single-quote in the word, we need
|
|
// to terminate the string, emit an escaped quote, and start the string up
|
|
// again
|
|
inQuote := false
|
|
for len(word) > 0 {
|
|
i := strings.IndexRune(word, '\'')
|
|
if i == -1 {
|
|
break
|
|
}
|
|
if i > 0 {
|
|
if !inQuote {
|
|
buf.WriteByte('\'')
|
|
inQuote = true
|
|
}
|
|
buf.WriteString(word[0:i])
|
|
}
|
|
word = word[i+1:]
|
|
if inQuote {
|
|
buf.WriteByte('\'')
|
|
inQuote = false
|
|
}
|
|
buf.WriteString("\\'")
|
|
}
|
|
if len(word) > 0 {
|
|
if !inQuote {
|
|
buf.WriteByte('\'')
|
|
}
|
|
buf.WriteString(word)
|
|
buf.WriteByte('\'')
|
|
}
|
|
}
|