Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -10,7 +10,7 @@ github.com/gorilla/mux v1.7.0 |
| 10 | 10 |
github.com/Microsoft/opengcs a10967154e143a36014584a6f664344e3bb0aa64 |
| 11 | 11 |
github.com/konsorten/go-windows-terminal-sequences v1.0.1 |
| 12 | 12 |
github.com/kr/pty 521317be5ebc228a0f0ede099fa2a0b5ece22e49 # v1.1.4 |
| 13 |
-github.com/mattn/go-shellwords v1.0.3 |
|
| 13 |
+github.com/mattn/go-shellwords a72fbe27a1b0ed0df2f02754945044ce1456608b # v1.0.5 |
|
| 14 | 14 |
github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1 |
| 15 | 15 |
github.com/tchap/go-patricia v2.2.6 |
| 16 | 16 |
github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 # v0.1.0 |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"errors" |
| 5 | 5 |
"os" |
| 6 | 6 |
"regexp" |
| 7 |
+ "strings" |
|
| 7 | 8 |
) |
| 8 | 9 |
|
| 9 | 10 |
var ( |
| ... | ... |
@@ -21,13 +22,17 @@ func isSpace(r rune) bool {
|
| 21 | 21 |
return false |
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
-func replaceEnv(s string) string {
|
|
| 24 |
+func replaceEnv(getenv func(string) string, s string) string {
|
|
| 25 |
+ if getenv == nil {
|
|
| 26 |
+ getenv = os.Getenv |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 25 | 29 |
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
| 26 | 30 |
s = s[1:] |
| 27 | 31 |
if s[0] == '{' {
|
| 28 | 32 |
s = s[1 : len(s)-1] |
| 29 | 33 |
} |
| 30 |
- return os.Getenv(s) |
|
| 34 |
+ return getenv(s) |
|
| 31 | 35 |
}) |
| 32 | 36 |
} |
| 33 | 37 |
|
| ... | ... |
@@ -35,16 +40,24 @@ type Parser struct {
|
| 35 | 35 |
ParseEnv bool |
| 36 | 36 |
ParseBacktick bool |
| 37 | 37 |
Position int |
| 38 |
+ |
|
| 39 |
+ // If ParseEnv is true, use this for getenv. |
|
| 40 |
+ // If nil, use os.Getenv. |
|
| 41 |
+ Getenv func(string) string |
|
| 38 | 42 |
} |
| 39 | 43 |
|
| 40 | 44 |
func NewParser() *Parser {
|
| 41 |
- return &Parser{ParseEnv, ParseBacktick, 0}
|
|
| 45 |
+ return &Parser{
|
|
| 46 |
+ ParseEnv: ParseEnv, |
|
| 47 |
+ ParseBacktick: ParseBacktick, |
|
| 48 |
+ Position: 0, |
|
| 49 |
+ } |
|
| 42 | 50 |
} |
| 43 | 51 |
|
| 44 | 52 |
func (p *Parser) Parse(line string) ([]string, error) {
|
| 45 | 53 |
args := []string{}
|
| 46 | 54 |
buf := "" |
| 47 |
- var escaped, doubleQuoted, singleQuoted, backQuote bool |
|
| 55 |
+ var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool |
|
| 48 | 56 |
backtick := "" |
| 49 | 57 |
|
| 50 | 58 |
pos := -1 |
| ... | ... |
@@ -68,12 +81,12 @@ loop: |
| 68 | 68 |
} |
| 69 | 69 |
|
| 70 | 70 |
if isSpace(r) {
|
| 71 |
- if singleQuoted || doubleQuoted || backQuote {
|
|
| 71 |
+ if singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
|
| 72 | 72 |
buf += string(r) |
| 73 | 73 |
backtick += string(r) |
| 74 | 74 |
} else if got {
|
| 75 | 75 |
if p.ParseEnv {
|
| 76 |
- buf = replaceEnv(buf) |
|
| 76 |
+ buf = replaceEnv(p.Getenv, buf) |
|
| 77 | 77 |
} |
| 78 | 78 |
args = append(args, buf) |
| 79 | 79 |
buf = "" |
| ... | ... |
@@ -84,7 +97,7 @@ loop: |
| 84 | 84 |
|
| 85 | 85 |
switch r {
|
| 86 | 86 |
case '`': |
| 87 |
- if !singleQuoted && !doubleQuoted {
|
|
| 87 |
+ if !singleQuoted && !doubleQuoted && !dollarQuote {
|
|
| 88 | 88 |
if p.ParseBacktick {
|
| 89 | 89 |
if backQuote {
|
| 90 | 90 |
out, err := shellRun(backtick) |
| ... | ... |
@@ -100,18 +113,55 @@ loop: |
| 100 | 100 |
backtick = "" |
| 101 | 101 |
backQuote = !backQuote |
| 102 | 102 |
} |
| 103 |
+ case ')': |
|
| 104 |
+ if !singleQuoted && !doubleQuoted && !backQuote {
|
|
| 105 |
+ if p.ParseBacktick {
|
|
| 106 |
+ if dollarQuote {
|
|
| 107 |
+ out, err := shellRun(backtick) |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ return nil, err |
|
| 110 |
+ } |
|
| 111 |
+ if r == ')' {
|
|
| 112 |
+ buf = buf[:len(buf)-len(backtick)-2] + out |
|
| 113 |
+ } else {
|
|
| 114 |
+ buf = buf[:len(buf)-len(backtick)-1] + out |
|
| 115 |
+ } |
|
| 116 |
+ } |
|
| 117 |
+ backtick = "" |
|
| 118 |
+ dollarQuote = !dollarQuote |
|
| 119 |
+ continue |
|
| 120 |
+ } |
|
| 121 |
+ backtick = "" |
|
| 122 |
+ dollarQuote = !dollarQuote |
|
| 123 |
+ } |
|
| 124 |
+ case '(':
|
|
| 125 |
+ if !singleQuoted && !doubleQuoted && !backQuote {
|
|
| 126 |
+ if !dollarQuote && strings.HasSuffix(buf, "$") {
|
|
| 127 |
+ dollarQuote = true |
|
| 128 |
+ buf += "("
|
|
| 129 |
+ continue |
|
| 130 |
+ } else {
|
|
| 131 |
+ return nil, errors.New("invalid command line string")
|
|
| 132 |
+ } |
|
| 133 |
+ } |
|
| 103 | 134 |
case '"': |
| 104 |
- if !singleQuoted {
|
|
| 135 |
+ if !singleQuoted && !dollarQuote {
|
|
| 105 | 136 |
doubleQuoted = !doubleQuoted |
| 106 | 137 |
continue |
| 107 | 138 |
} |
| 108 | 139 |
case '\'': |
| 109 |
- if !doubleQuoted {
|
|
| 140 |
+ if !doubleQuoted && !dollarQuote {
|
|
| 110 | 141 |
singleQuoted = !singleQuoted |
| 111 | 142 |
continue |
| 112 | 143 |
} |
| 113 | 144 |
case ';', '&', '|', '<', '>': |
| 114 | 145 |
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
| 146 |
+ if r == '>' && len(buf) > 0 {
|
|
| 147 |
+ if c := buf[0]; '0' <= c && c <= '9' {
|
|
| 148 |
+ i -= 1 |
|
| 149 |
+ got = false |
|
| 150 |
+ } |
|
| 151 |
+ } |
|
| 115 | 152 |
pos = i |
| 116 | 153 |
break loop |
| 117 | 154 |
} |
| ... | ... |
@@ -119,19 +169,19 @@ loop: |
| 119 | 119 |
|
| 120 | 120 |
got = true |
| 121 | 121 |
buf += string(r) |
| 122 |
- if backQuote {
|
|
| 122 |
+ if backQuote || dollarQuote {
|
|
| 123 | 123 |
backtick += string(r) |
| 124 | 124 |
} |
| 125 | 125 |
} |
| 126 | 126 |
|
| 127 | 127 |
if got {
|
| 128 | 128 |
if p.ParseEnv {
|
| 129 |
- buf = replaceEnv(buf) |
|
| 129 |
+ buf = replaceEnv(p.Getenv, buf) |
|
| 130 | 130 |
} |
| 131 | 131 |
args = append(args, buf) |
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 |
- if escaped || singleQuoted || doubleQuoted || backQuote {
|
|
| 134 |
+ if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
|
|
| 135 | 135 |
return nil, errors.New("invalid command line string")
|
| 136 | 136 |
} |
| 137 | 137 |
|
| 138 | 138 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,24 @@ |
| 0 |
+// +build !go1.6 |
|
| 1 |
+ |
|
| 2 |
+package shellwords |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "os" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "runtime" |
|
| 8 |
+ "strings" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func shellRun(line string) (string, error) {
|
|
| 12 |
+ var b []byte |
|
| 13 |
+ var err error |
|
| 14 |
+ if runtime.GOOS == "windows" {
|
|
| 15 |
+ b, err = exec.Command(os.Getenv("COMSPEC"), "/c", line).Output()
|
|
| 16 |
+ } else {
|
|
| 17 |
+ b, err = exec.Command(os.Getenv("SHELL"), "-c", line).Output()
|
|
| 18 |
+ } |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ return "", err |
|
| 21 |
+ } |
|
| 22 |
+ return strings.TrimSpace(string(b)), nil |
|
| 23 |
+} |
| ... | ... |
@@ -1,4 +1,4 @@ |
| 1 |
-// +build !windows |
|
| 1 |
+// +build !windows,go1.6 |
|
| 2 | 2 |
|
| 3 | 3 |
package shellwords |
| 4 | 4 |
|
| ... | ... |
@@ -13,6 +13,9 @@ func shellRun(line string) (string, error) {
|
| 13 | 13 |
shell := os.Getenv("SHELL")
|
| 14 | 14 |
b, err := exec.Command(shell, "-c", line).Output() |
| 15 | 15 |
if err != nil {
|
| 16 |
+ if eerr, ok := err.(*exec.ExitError); ok {
|
|
| 17 |
+ b = eerr.Stderr |
|
| 18 |
+ } |
|
| 16 | 19 |
return "", errors.New(err.Error() + ":" + string(b)) |
| 17 | 20 |
} |
| 18 | 21 |
return strings.TrimSpace(string(b)), nil |
| ... | ... |
@@ -1,3 +1,5 @@ |
| 1 |
+// +build windows,go1.6 |
|
| 2 |
+ |
|
| 1 | 3 |
package shellwords |
| 2 | 4 |
|
| 3 | 5 |
import ( |
| ... | ... |
@@ -11,6 +13,9 @@ func shellRun(line string) (string, error) {
|
| 11 | 11 |
shell := os.Getenv("COMSPEC")
|
| 12 | 12 |
b, err := exec.Command(shell, "/c", line).Output() |
| 13 | 13 |
if err != nil {
|
| 14 |
+ if eerr, ok := err.(*exec.ExitError); ok {
|
|
| 15 |
+ b = eerr.Stderr |
|
| 16 |
+ } |
|
| 14 | 17 |
return "", errors.New(err.Error() + ":" + string(b)) |
| 15 | 18 |
} |
| 16 | 19 |
return strings.TrimSpace(string(b)), nil |