mirror of
https://github.com/fleetdm/fleet
synced 2026-05-12 03:28:48 +00:00
#21038 Fleet server now accepts arguments via stdin. This is useful for passing secrets that you don't want to expose as env vars, in the command line, or in the config file. Demo: https://www.loom.com/share/c8b4dc6ae6ef4182bc812d7f43423f4d # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
156 lines
3.6 KiB
Go
156 lines
3.6 KiB
Go
// Based on https://github.com/kballard/go-shellquote
|
|
|
|
package shellquote
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
var (
|
|
UnterminatedSingleQuoteError = errors.New("unterminated single-quoted string")
|
|
UnterminatedDoubleQuoteError = errors.New("unterminated double-quoted string")
|
|
UnterminatedEscapeError = errors.New("unterminated backslash-escape")
|
|
)
|
|
|
|
var (
|
|
splitChars = " \n\t"
|
|
singleChar = '\''
|
|
doubleChar = '"'
|
|
escapeChar = '\\'
|
|
doubleEscapeChars = "$`\"\n\\"
|
|
)
|
|
|
|
// Split splits a string according to /bin/sh's word-splitting rules. It
|
|
// supports backslash-escapes, single-quotes, and double-quotes. Notably it does
|
|
// not support the $” style of quoting. It also doesn't attempt to perform any
|
|
// other sort of expansion, including brace expansion, shell expansion, or
|
|
// pathname expansion.
|
|
//
|
|
// If the given input has an unterminated quoted string or ends in a
|
|
// backslash-escape, one of UnterminatedSingleQuoteError,
|
|
// UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned.
|
|
func Split(input string) (words []string, err error) {
|
|
var buf bytes.Buffer
|
|
words = make([]string, 0)
|
|
|
|
for len(input) > 0 {
|
|
// skip any splitChars at the start
|
|
c, l := utf8.DecodeRuneInString(input)
|
|
if strings.ContainsRune(splitChars, c) {
|
|
input = input[l:]
|
|
continue
|
|
} else if c == escapeChar {
|
|
// Look ahead for escaped newline, so we can skip over it
|
|
next := input[l:]
|
|
if len(next) == 0 {
|
|
err = UnterminatedEscapeError
|
|
return
|
|
}
|
|
c2, l2 := utf8.DecodeRuneInString(next)
|
|
if c2 == '\n' {
|
|
input = next[l2:]
|
|
continue
|
|
}
|
|
}
|
|
|
|
var word string
|
|
word, input, err = splitWord(input, &buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
words = append(words, word)
|
|
}
|
|
return
|
|
}
|
|
|
|
func splitWord(input string, buf *bytes.Buffer) (word string, remainder string, err error) {
|
|
buf.Reset()
|
|
|
|
raw:
|
|
{
|
|
cur := input
|
|
for len(cur) > 0 {
|
|
c, l := utf8.DecodeRuneInString(cur)
|
|
cur = cur[l:]
|
|
if c == singleChar {
|
|
buf.WriteString(input[0 : len(input)-len(cur)-l])
|
|
input = cur
|
|
goto single
|
|
} else if c == doubleChar {
|
|
buf.WriteString(input[0 : len(input)-len(cur)-l])
|
|
input = cur
|
|
goto double
|
|
} else if c == escapeChar {
|
|
buf.WriteString(input[0 : len(input)-len(cur)-l])
|
|
input = cur
|
|
goto escape
|
|
} else if strings.ContainsRune(splitChars, c) {
|
|
buf.WriteString(input[0 : len(input)-len(cur)-l])
|
|
return buf.String(), cur, nil
|
|
}
|
|
}
|
|
if len(input) > 0 {
|
|
buf.WriteString(input)
|
|
input = ""
|
|
}
|
|
goto done
|
|
}
|
|
|
|
escape:
|
|
{
|
|
if len(input) == 0 {
|
|
return "", "", UnterminatedEscapeError
|
|
}
|
|
c, l := utf8.DecodeRuneInString(input)
|
|
// a backslash-escaped newline is elided from the output entirely
|
|
if c != '\n' {
|
|
buf.WriteString(input[:l])
|
|
}
|
|
input = input[l:]
|
|
}
|
|
goto raw
|
|
|
|
single:
|
|
{
|
|
i := strings.IndexRune(input, singleChar)
|
|
if i == -1 {
|
|
return "", "", UnterminatedSingleQuoteError
|
|
}
|
|
buf.WriteString(input[0:i])
|
|
input = input[i+1:]
|
|
goto raw
|
|
}
|
|
|
|
double:
|
|
{
|
|
cur := input
|
|
for len(cur) > 0 {
|
|
c, l := utf8.DecodeRuneInString(cur)
|
|
cur = cur[l:]
|
|
if c == doubleChar {
|
|
buf.WriteString(input[0 : len(input)-len(cur)-l])
|
|
input = cur
|
|
goto raw
|
|
} else if c == escapeChar {
|
|
// bash only supports certain escapes in double-quoted strings
|
|
c2, l2 := utf8.DecodeRuneInString(cur)
|
|
cur = cur[l2:]
|
|
if strings.ContainsRune(doubleEscapeChars, c2) {
|
|
buf.WriteString(input[0 : len(input)-len(cur)-l-l2])
|
|
// newline is special, skip the backslash entirely
|
|
if c2 != '\n' {
|
|
buf.WriteRune(c2)
|
|
}
|
|
input = cur
|
|
}
|
|
}
|
|
}
|
|
return "", "", UnterminatedDoubleQuoteError
|
|
}
|
|
|
|
done:
|
|
return buf.String(), input, nil
|
|
}
|