github.com/mckael/restic@v0.8.3/internal/backend/sftp/split.go (about) 1 package sftp 2 3 import ( 4 "unicode" 5 6 "github.com/restic/restic/internal/errors" 7 ) 8 9 // shellSplitter splits a command string into separater arguments. It supports 10 // single and double quoted strings. 11 type shellSplitter struct { 12 quote rune 13 lastChar rune 14 } 15 16 func (s *shellSplitter) isSplitChar(c rune) bool { 17 // only test for quotes if the last char was not a backslash 18 if s.lastChar != '\\' { 19 20 // quote ended 21 if s.quote != 0 && c == s.quote { 22 s.quote = 0 23 return true 24 } 25 26 // quote starts 27 if s.quote == 0 && (c == '"' || c == '\'') { 28 s.quote = c 29 return true 30 } 31 } 32 33 s.lastChar = c 34 35 // within quote 36 if s.quote != 0 { 37 return false 38 } 39 40 // outside quote 41 return c == '\\' || unicode.IsSpace(c) 42 } 43 44 // SplitShellArgs returns the list of arguments from a shell command string. 45 func SplitShellArgs(data string) (cmd string, args []string, err error) { 46 s := &shellSplitter{} 47 48 // derived from strings.SplitFunc 49 fieldStart := -1 // Set to -1 when looking for start of field. 50 for i, rune := range data { 51 if s.isSplitChar(rune) { 52 if fieldStart >= 0 { 53 args = append(args, data[fieldStart:i]) 54 fieldStart = -1 55 } 56 } else if fieldStart == -1 { 57 fieldStart = i 58 } 59 } 60 if fieldStart >= 0 { // Last field might end at EOF. 61 args = append(args, data[fieldStart:]) 62 } 63 64 switch s.quote { 65 case '\'': 66 return "", nil, errors.New("single-quoted string not terminated") 67 case '"': 68 return "", nil, errors.New("double-quoted string not terminated") 69 } 70 71 if len(args) == 0 { 72 return "", nil, errors.New("command string is empty") 73 } 74 75 cmd, args = args[0], args[1:] 76 77 return cmd, args, nil 78 }