restic/internal/backend/shell_split.go

79 lines
1.6 KiB
Go
Raw Normal View History

package backend
2017-04-03 06:57:33 +00:00
import (
"unicode"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/errors"
2017-04-03 06:57:33 +00:00
)
// shellSplitter splits a command string into separater arguments. It supports
// single and double quoted strings.
type shellSplitter struct {
quote rune
lastChar rune
}
func (s *shellSplitter) isSplitChar(c rune) bool {
// only test for quotes if the last char was not a backslash
if s.lastChar != '\\' {
// quote ended
if s.quote != 0 && c == s.quote {
s.quote = 0
return true
}
// quote starts
if s.quote == 0 && (c == '"' || c == '\'') {
s.quote = c
return true
}
}
s.lastChar = c
// within quote
if s.quote != 0 {
return false
}
// outside quote
return c == '\\' || unicode.IsSpace(c)
}
// SplitShellArgs returns the list of arguments from a shell command string.
2017-04-03 19:05:42 +00:00
func SplitShellArgs(data string) (cmd string, args []string, err error) {
2017-04-03 06:57:33 +00:00
s := &shellSplitter{}
// derived from strings.SplitFunc
fieldStart := -1 // Set to -1 when looking for start of field.
for i, rune := range data {
if s.isSplitChar(rune) {
if fieldStart >= 0 {
2017-04-03 19:05:42 +00:00
args = append(args, data[fieldStart:i])
2017-04-03 06:57:33 +00:00
fieldStart = -1
}
} else if fieldStart == -1 {
fieldStart = i
}
}
if fieldStart >= 0 { // Last field might end at EOF.
2017-04-03 19:05:42 +00:00
args = append(args, data[fieldStart:])
2017-04-03 06:57:33 +00:00
}
switch s.quote {
case '\'':
2017-04-03 19:05:42 +00:00
return "", nil, errors.New("single-quoted string not terminated")
2017-04-03 06:57:33 +00:00
case '"':
2017-04-03 19:05:42 +00:00
return "", nil, errors.New("double-quoted string not terminated")
2017-04-03 06:57:33 +00:00
}
2017-04-03 19:05:42 +00:00
if len(args) == 0 {
return "", nil, errors.New("command string is empty")
}
cmd, args = args[0], args[1:]
return cmd, args, nil
2017-04-03 06:57:33 +00:00
}