Skip to content

Commit

Permalink
Merge pull request #45 from mattn/fix-44
Browse files Browse the repository at this point in the history
Fixes #44
  • Loading branch information
mattn authored Dec 1, 2020
2 parents 9e822f2 + ebba8f7 commit 2c8720d
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 35 deletions.
135 changes: 106 additions & 29 deletions shellwords.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package shellwords

import (
"bytes"
"errors"
"os"
"regexp"
"strings"
"unicode"
)

var (
Expand All @@ -27,13 +29,72 @@ func replaceEnv(getenv func(string) string, s string) string {
getenv = os.Getenv
}

return envRe.ReplaceAllStringFunc(s, func(s string) string {
s = s[1:]
if s[0] == '{' {
s = s[1 : len(s)-1]
var buf bytes.Buffer
rs := []rune(s)
for i := 0; i < len(rs); i++ {
r := rs[i]
if r == '\\' {
i++
if i == len(rs) {
break
}
buf.WriteRune(rs[i])
continue
} else if r == '$' {
i++
if i == len(rs) {
buf.WriteRune(r)
break
}
if rs[i] == 0x7b {
i++
p := i
for ; i < len(rs); i++ {
r = rs[i]
if r == '\\' {
i++
if i == len(rs) {
return s
}
continue
}
if r == 0x7d || (!unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r)) {
break
}
}
if r != 0x7d {
return s
}
if i > p {
buf.WriteString(getenv(s[p:i]))
}
} else {
p := i
for ; i < len(rs); i++ {
r := rs[i]
if r == '\\' {
i++
if i == len(rs) {
return s
}
continue
}
if !unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r) {
break
}
}
if i > p {
buf.WriteString(getenv(s[p:i]))
i--
} else {
buf.WriteString(s[p:])
}
}
} else {
buf.WriteRune(r)
}
return getenv(s)
})
}
return buf.String()
}

type Parser struct {
Expand All @@ -56,14 +117,22 @@ func NewParser() *Parser {
}
}

type argType int

const (
argNo argType = iota
argSingle
argQuoted
)

func (p *Parser) Parse(line string) ([]string, error) {
args := []string{}
buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
backtick := ""

pos := -1
got := false
got := argNo

i := -1
loop:
Expand All @@ -72,7 +141,7 @@ loop:
if escaped {
buf += string(r)
escaped = false
got = true
got = argSingle
continue
}

Expand All @@ -89,21 +158,25 @@ loop:
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
buf += string(r)
backtick += string(r)
} else if got {
} else if got != argNo {
if p.ParseEnv {
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
}
for _, str := range strs {
args = append(args, str)
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
}
for _, str := range strs {
args = append(args, str)
}
} else {
args = append(args, replaceEnv(p.Getenv, buf))
}
} else {
args = append(args, buf)
}
buf = ""
got = false
got = argNo
}
continue
}
Expand Down Expand Up @@ -156,15 +229,15 @@ loop:
case '"':
if !singleQuoted && !dollarQuote {
if doubleQuoted {
got = true
got = argQuoted
}
doubleQuoted = !doubleQuoted
continue
}
case '\'':
if !doubleQuoted && !dollarQuote {
if singleQuoted {
got = true
got = argSingle
}
singleQuoted = !singleQuoted
continue
Expand All @@ -174,30 +247,34 @@ loop:
if r == '>' && len(buf) > 0 {
if c := buf[0]; '0' <= c && c <= '9' {
i -= 1
got = false
got = argNo
}
}
pos = i
break loop
}
}

got = true
got = argSingle
buf += string(r)
if backQuote || dollarQuote {
backtick += string(r)
}
}

if got {
if got != argNo {
if p.ParseEnv {
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
}
for _, str := range strs {
args = append(args, str)
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
}
for _, str := range strs {
args = append(args, str)
}
} else {
args = append(args, replaceEnv(p.Getenv, buf))
}
} else {
args = append(args, buf)
Expand Down
36 changes: 30 additions & 6 deletions shellwords_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ func TestEnvArgumentsFail(t *testing.T) {
t.Fatal("Should be an error")
}
os.Setenv("FOO", "bar `")
_, err = parser.Parse("$FOO ")
result, err := parser.Parse("$FOO ")
if err == nil {
t.Fatal("Should be an error")
t.Fatal("Should be an error: ", result)
}
}

Expand All @@ -300,20 +300,20 @@ func TestDupEnv(t *testing.T) {

parser := NewParser()
parser.ParseEnv = true
args, err := parser.Parse("echo $$FOO$")
args, err := parser.Parse("echo $FOO$")
if err != nil {
t.Fatal(err)
}
expected := []string{"echo", "$bar$"}
expected := []string{"echo", "bar$"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}

args, err = parser.Parse("echo $${FOO_BAR}$")
args, err = parser.Parse("echo ${FOO_BAR}$")
if err != nil {
t.Fatal(err)
}
expected = []string{"echo", "$baz$"}
expected = []string{"echo", "baz$"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
Expand Down Expand Up @@ -383,3 +383,27 @@ func TestBackquoteInFlag(t *testing.T) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
}

func TestEnvInQuoted(t *testing.T) {
os.Setenv("FOO", "bar")

parser := NewParser()
parser.ParseEnv = true
args, err := parser.Parse(`ssh 127.0.0.1 "echo $FOO"`)
if err != nil {
panic(err)
}
expected := []string{"ssh", "127.0.0.1", "echo bar"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}

args, err = parser.Parse(`ssh 127.0.0.1 "echo \\$FOO"`)
if err != nil {
panic(err)
}
expected = []string{"ssh", "127.0.0.1", "echo $FOO"}
if !reflect.DeepEqual(args, expected) {
t.Fatalf("Expected %#v, but %#v:", expected, args)
}
}

0 comments on commit 2c8720d

Please sign in to comment.