diff --git a/storage/go.mod b/storage/go.mod index f768f3d5bf..4a3c24c5e6 100644 --- a/storage/go.mod +++ b/storage/go.mod @@ -13,7 +13,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/klauspost/compress v1.19.0 github.com/klauspost/pgzip v1.2.6 - github.com/mattn/go-shellwords v1.0.13 + github.com/mattn/go-shellwords v1.0.14 github.com/mistifyio/go-zfs/v4 v4.0.0 github.com/moby/sys/capability v0.4.0 github.com/moby/sys/mountinfo v0.7.2 diff --git a/storage/go.sum b/storage/go.sum index f5b0f76247..e263ab37df 100644 --- a/storage/go.sum +++ b/storage/go.sum @@ -32,8 +32,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-shellwords v1.0.13 h1:DC0OMEpGjm6LfNFU4ckYcvbQKyp2vE8atyFGXNtDcf4= -github.com/mattn/go-shellwords v1.0.13/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg= +github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4= github.com/mistifyio/go-zfs/v4 v4.0.0/go.mod h1:weotFtXTHvBwhr9Mv96KYnDkTPBOHFUbm9cBmQpesL0= github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= diff --git a/vendor/github.com/mattn/go-shellwords/.gitignore b/vendor/github.com/mattn/go-shellwords/.gitignore new file mode 100644 index 0000000000..970ea629a2 --- /dev/null +++ b/vendor/github.com/mattn/go-shellwords/.gitignore @@ -0,0 +1 @@ +.codex diff --git a/vendor/github.com/mattn/go-shellwords/README.md b/vendor/github.com/mattn/go-shellwords/README.md index bdd531918c..1dbca1402e 100644 --- a/vendor/github.com/mattn/go-shellwords/README.md +++ b/vendor/github.com/mattn/go-shellwords/README.md @@ -42,6 +42,18 @@ args, err := p.Parse("./foo `echo $SHELL`") // args should be ["./foo", "/bin/bash"] ``` +```go +p := shellwords.NewParser() +p.ParseComment = true +args, err := p.Parse("./foo # comment") +// args should be ["./foo"] +``` + +```go +p := shellwords.NewParser() +p.SetExcludeSeparators('\t', ';') +``` + # Thanks This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine). diff --git a/vendor/github.com/mattn/go-shellwords/shellwords.go b/vendor/github.com/mattn/go-shellwords/shellwords.go index 3747e35494..3e211b02d0 100644 --- a/vendor/github.com/mattn/go-shellwords/shellwords.go +++ b/vendor/github.com/mattn/go-shellwords/shellwords.go @@ -11,8 +11,11 @@ import ( var ( ParseEnv bool = false ParseBacktick bool = false + ParseComment bool = false ) +var errInvalidCmdLine = errors.New("invalid command line string") + func isSpace(r rune) bool { switch r { case ' ', '\t', '\r', '\n': @@ -63,7 +66,7 @@ func replaceEnv(getenv func(string) string, s string) string { return s } if i > p { - buf.WriteString(getenv(s[p:i])) + buf.WriteString(getenv(string(rs[p:i]))) } } else { p := i @@ -81,10 +84,11 @@ func replaceEnv(getenv func(string) string, s string) string { } } if i > p { - buf.WriteString(getenv(s[p:i])) + buf.WriteString(getenv(string(rs[p:i]))) i-- } else { - buf.WriteString(s[p:]) + buf.WriteRune('$') + i-- } } } else { @@ -97,8 +101,10 @@ func replaceEnv(getenv func(string) string, s string) string { type Parser struct { ParseEnv bool ParseBacktick bool + ParseComment bool Position int Dir string + excludedSep []rune // If ParseEnv is true, use this for getenv. // If nil, use os.Getenv. @@ -109,8 +115,10 @@ func NewParser() *Parser { return &Parser{ ParseEnv: ParseEnv, ParseBacktick: ParseBacktick, + ParseComment: ParseComment, Position: 0, Dir: "", + excludedSep: []rune{}, } } @@ -122,62 +130,125 @@ const ( argQuoted ) +// isExcluded checks if separator should be ignored +func (p *Parser) isExcluded(r rune) bool { + for _, v := range p.excludedSep { + if v == r { + return true + } + } + return false +} + +// SetExcludeSeparators indicates the parser to ignore provided separators when parsing +// example: parser.SetExcludeSeparators(';','\t') +func (p *Parser) SetExcludeSeparators(r ...rune) { + p.excludedSep = r +} + +// ExcludedSeparators returns excluded separators +func (p *Parser) ExcludedSeparators() []rune { + return p.excludedSep +} + func (p *Parser) Parse(line string) ([]string, error) { - args := []string{} - buf := "" - var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool - backtick := "" + args := make([]string, 0, 1+len(line)/8) + buf := make([]byte, 0, len(line)) + var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote, comment bool + var backtick []byte pos := -1 got := argNo + flush := func() error { + if got == argQuoted || (got != argNo && len(buf) > 0) { + token := string(buf) + if p.ParseEnv { + if got == argSingle { + parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir} + strs, err := parser.Parse(replaceEnv(p.Getenv, token)) + if err != nil { + return err + } + args = append(args, strs...) + } else { + args = append(args, replaceEnv(p.Getenv, token)) + } + } else { + args = append(args, token) + } + } + buf = buf[:0] + got = argNo + return nil + } + i := -1 loop: for _, r := range line { i++ + + if comment { + if r == '\n' { + comment = false + } + continue + } + if escaped { + escaped = false + if backQuote || dollarQuote { + buf = append(buf, '\\') + buf = append(buf, string(r)...) + backtick = append(backtick, '\\') + backtick = append(backtick, string(r)...) + got = argSingle + continue + } + if p.ParseEnv && r == '$' { + // Keep the backslash so replaceEnv treats the '$' as + // literal instead of expanding it. + buf = append(buf, '\\', '$') + got = argSingle + continue + } if r == 't' { r = '\t' } if r == 'n' { r = '\n' } - buf += string(r) - escaped = false + buf = append(buf, string(r)...) got = argSingle continue } if r == '\\' { if singleQuoted { - buf += string(r) + buf = append(buf, '\\') } else { escaped = true } continue } + if p.isExcluded(r) { + got = argSingle + buf = append(buf, string(r)...) + if backQuote || dollarQuote { + backtick = append(backtick, string(r)...) + } + continue + } + if isSpace(r) { if singleQuoted || doubleQuoted || backQuote || dollarQuote { - buf += string(r) - backtick += string(r) - } else if got != argNo { - if p.ParseEnv { - if got == argSingle { - parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir} - strs, err := parser.Parse(replaceEnv(p.Getenv, buf)) - if err != nil { - return nil, err - } - args = append(args, strs...) - } else { - args = append(args, replaceEnv(p.Getenv, buf)) - } - } else { - args = append(args, buf) + buf = append(buf, byte(r)) + if backQuote || dollarQuote { + backtick = append(backtick, byte(r)) } - buf = "" - got = argNo + } else if err := flush(); err != nil { + return nil, err } continue } @@ -187,17 +258,17 @@ loop: if !singleQuoted && !doubleQuoted && !dollarQuote { if p.ParseBacktick { if backQuote { - out, err := shellRun(backtick, p.Dir) + out, err := shellRun(string(backtick), p.Dir) if err != nil { return nil, err } - buf = buf[:len(buf)-len(backtick)] + out + buf = append(buf[:len(buf)-len(backtick)], out...) } - backtick = "" + backtick = backtick[:0] backQuote = !backQuote continue } - backtick = "" + backtick = backtick[:0] backQuote = !backQuote } @@ -209,10 +280,10 @@ loop: // Preserve prior behavior by rejecting unmatched ')' // when command substitution parsing is enabled. if !dollarQuote { - return nil, errors.New("invalid command line string") + return nil, errInvalidCmdLine } - out, err := shellRun(backtick, p.Dir) + out, err := shellRun(string(backtick), p.Dir) if err != nil { return nil, err } @@ -220,11 +291,11 @@ loop: // Defensive guard: valid $(...) implies the buffer must contain // the "$(" prefix plus the collected command body. if len(buf) < len(backtick)+2 { - return nil, errors.New("invalid command line string") + return nil, errInvalidCmdLine } - buf = buf[:len(buf)-len(backtick)-2] + out - backtick = "" + buf = append(buf[:len(buf)-len(backtick)-2], out...) + backtick = backtick[:0] dollarQuote = false continue } @@ -233,11 +304,11 @@ loop: // A bare ')' is a syntax error, consistent with '(' handling. // Only close an already-open $(...) region. if !dollarQuote { - return nil, errors.New("invalid command line string") + return nil, errInvalidCmdLine } - buf += string(r) - backtick = "" + buf = append(buf, ')') + backtick = backtick[:0] dollarQuote = false got = argSingle continue @@ -245,17 +316,17 @@ loop: case '(': if !singleQuoted && !doubleQuoted && !backQuote { - if !dollarQuote && strings.HasSuffix(buf, "$") { + if n := len(buf); !dollarQuote && n > 0 && buf[n-1] == '$' && !(n > 1 && buf[n-2] == '\\') { dollarQuote = true - buf += "(" + buf = append(buf, '(') continue } else { - return nil, errors.New("invalid command line string") + return nil, errInvalidCmdLine } } case '"': - if !singleQuoted && !dollarQuote { + if !singleQuoted && !dollarQuote && !backQuote { if doubleQuoted { got = argQuoted } @@ -264,7 +335,7 @@ loop: } case '\'': - if !doubleQuoted && !dollarQuote { + if !doubleQuoted && !dollarQuote && !backQuote { if singleQuoted { got = argQuoted } @@ -275,42 +346,41 @@ loop: case ';', '&', '|', '<', '>': if !(escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote) { if r == '>' && len(buf) > 0 { - if c := buf[0]; '0' <= c && c <= '9' { - i -= 1 + isDigits := true + for _, c := range buf { + if c < '0' || c > '9' { + isDigits = false + break + } + } + if isDigits { + i -= len(buf) got = argNo } } pos = i break loop } + case '#': + if p.ParseComment && len(buf) == 0 && !(escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote) { + comment = true + continue loop + } } got = argSingle - buf += string(r) + buf = append(buf, string(r)...) if backQuote || dollarQuote { - backtick += string(r) + backtick = append(backtick, string(r)...) } } - if got != argNo { - if p.ParseEnv { - if got == argSingle { - parser := &Parser{ParseEnv: false, ParseBacktick: false, Position: 0, Dir: p.Dir} - strs, err := parser.Parse(replaceEnv(p.Getenv, buf)) - if err != nil { - return nil, err - } - args = append(args, strs...) - } else { - args = append(args, replaceEnv(p.Getenv, buf)) - } - } else { - args = append(args, buf) - } + if err := flush(); err != nil { + return nil, err } if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote { - return nil, errors.New("invalid command line string") + return nil, errInvalidCmdLine } p.Position = pos @@ -340,7 +410,18 @@ func (p *Parser) ParseWithEnvs(line string) (envs []string, args []string, err e } func isEnv(arg string) bool { - return len(strings.Split(arg, "=")) == 2 + i := strings.IndexByte(arg, '=') + if i <= 0 { + return false + } + for j := 0; j < i; j++ { + c := arg[j] + if c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || (j > 0 && '0' <= c && c <= '9') { + continue + } + return false + } + return true } func Parse(line string) ([]string, error) { diff --git a/vendor/modules.txt b/vendor/modules.txt index 0aa8ef6fc1..155cf15006 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -238,7 +238,7 @@ github.com/manifoldco/promptui/screenbuf # github.com/mattn/go-runewidth v0.0.24 ## explicit; go 1.20 github.com/mattn/go-runewidth -# github.com/mattn/go-shellwords v1.0.13 +# github.com/mattn/go-shellwords v1.0.14 ## explicit; go 1.13 github.com/mattn/go-shellwords # github.com/mattn/go-sqlite3 v1.14.47