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  }