mirror of
https://github.com/restic/restic.git
synced 2025-08-12 14:47:41 +00:00
Move backend/sftp.SplitShellArgs to backend/
This commit is contained in:
@@ -179,7 +179,7 @@ func (r *SFTP) IsNotExist(err error) bool {
|
||||
|
||||
func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
|
||||
if cfg.Command != "" {
|
||||
return SplitShellArgs(cfg.Command)
|
||||
return backend.SplitShellArgs(cfg.Command)
|
||||
}
|
||||
|
||||
cmd = "ssh"
|
||||
|
@@ -1,78 +0,0 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func SplitShellArgs(data string) (cmd string, args []string, err error) {
|
||||
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 {
|
||||
args = append(args, data[fieldStart:i])
|
||||
fieldStart = -1
|
||||
}
|
||||
} else if fieldStart == -1 {
|
||||
fieldStart = i
|
||||
}
|
||||
}
|
||||
if fieldStart >= 0 { // Last field might end at EOF.
|
||||
args = append(args, data[fieldStart:])
|
||||
}
|
||||
|
||||
switch s.quote {
|
||||
case '\'':
|
||||
return "", nil, errors.New("single-quoted string not terminated")
|
||||
case '"':
|
||||
return "", nil, errors.New("double-quoted string not terminated")
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return "", nil, errors.New("command string is empty")
|
||||
}
|
||||
|
||||
cmd, args = args[0], args[1:]
|
||||
|
||||
return cmd, args, nil
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShellSplitter(t *testing.T) {
|
||||
var tests = []struct {
|
||||
data string
|
||||
cmd string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
`foo`,
|
||||
"foo", []string{},
|
||||
},
|
||||
{
|
||||
`'foo'`,
|
||||
"foo", []string{},
|
||||
},
|
||||
{
|
||||
`foo bar baz`,
|
||||
"foo", []string{"bar", "baz"},
|
||||
},
|
||||
{
|
||||
`foo 'bar' baz`,
|
||||
"foo", []string{"bar", "baz"},
|
||||
},
|
||||
{
|
||||
`'bar box' baz`,
|
||||
"bar box", []string{"baz"},
|
||||
},
|
||||
{
|
||||
`"bar 'box'" baz`,
|
||||
"bar 'box'", []string{"baz"},
|
||||
},
|
||||
{
|
||||
`'bar "box"' baz`,
|
||||
`bar "box"`, []string{"baz"},
|
||||
},
|
||||
{
|
||||
`\"bar box baz`,
|
||||
`"bar`, []string{"box", "baz"},
|
||||
},
|
||||
{
|
||||
`"bar/foo/x" "box baz"`,
|
||||
"bar/foo/x", []string{"box baz"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
cmd, args, err := SplitShellArgs(test.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if cmd != test.cmd {
|
||||
t.Fatalf("wrong cmd returned, want:\n %#v\ngot:\n %#v",
|
||||
test.cmd, cmd)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(args, test.args) {
|
||||
t.Fatalf("wrong args returned, want:\n %#v\ngot:\n %#v",
|
||||
test.args, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellSplitterInvalid(t *testing.T) {
|
||||
var tests = []struct {
|
||||
data string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"foo'",
|
||||
"single-quoted string not terminated",
|
||||
},
|
||||
{
|
||||
`foo"`,
|
||||
"double-quoted string not terminated",
|
||||
},
|
||||
{
|
||||
"foo 'bar",
|
||||
"single-quoted string not terminated",
|
||||
},
|
||||
{
|
||||
`foo "bar`,
|
||||
"double-quoted string not terminated",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
cmd, args, err := SplitShellArgs(test.data)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error not found: %v", test.err)
|
||||
}
|
||||
|
||||
if err.Error() != test.err {
|
||||
t.Fatalf("expected error not found, want:\n %q\ngot:\n %q", test.err, err.Error())
|
||||
}
|
||||
|
||||
if cmd != "" {
|
||||
t.Fatalf("splitter returned cmd from invalid data: %v", cmd)
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
t.Fatalf("splitter returned fields from invalid data: %v", args)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user