Browse code

Windows: Honour escape directive fully

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2016/10/22 03:55:39
Showing 4 changed files
... ...
@@ -169,13 +169,13 @@ func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error {
169 169
 			var words []string
170 170
 
171 171
 			if allowWordExpansion[cmd] {
172
-				words, err = ProcessWords(str, envs)
172
+				words, err = ProcessWords(str, envs, b.directive.EscapeToken)
173 173
 				if err != nil {
174 174
 					return err
175 175
 				}
176 176
 				strList = append(strList, words...)
177 177
 			} else {
178
-				str, err = ProcessWord(str, envs)
178
+				str, err = ProcessWord(str, envs, b.directive.EscapeToken)
179 179
 				if err != nil {
180 180
 					return err
181 181
 				}
... ...
@@ -14,19 +14,21 @@ import (
14 14
 )
15 15
 
16 16
 type shellWord struct {
17
-	word    string
18
-	scanner scanner.Scanner
19
-	envs    []string
20
-	pos     int
17
+	word        string
18
+	scanner     scanner.Scanner
19
+	envs        []string
20
+	pos         int
21
+	escapeToken rune
21 22
 }
22 23
 
23 24
 // ProcessWord will use the 'env' list of environment variables,
24 25
 // and replace any env var references in 'word'.
25
-func ProcessWord(word string, env []string) (string, error) {
26
+func ProcessWord(word string, env []string, escapeToken rune) (string, error) {
26 27
 	sw := &shellWord{
27
-		word: word,
28
-		envs: env,
29
-		pos:  0,
28
+		word:        word,
29
+		envs:        env,
30
+		pos:         0,
31
+		escapeToken: escapeToken,
30 32
 	}
31 33
 	sw.scanner.Init(strings.NewReader(word))
32 34
 	word, _, err := sw.process()
... ...
@@ -40,11 +42,12 @@ func ProcessWord(word string, env []string) (string, error) {
40 40
 // this splitting is done **after** the env var substitutions are done.
41 41
 // Note, each one is trimmed to remove leading and trailing spaces (unless
42 42
 // they are quoted", but ProcessWord retains spaces between words.
43
-func ProcessWords(word string, env []string) ([]string, error) {
43
+func ProcessWords(word string, env []string, escapeToken rune) ([]string, error) {
44 44
 	sw := &shellWord{
45
-		word: word,
46
-		envs: env,
47
-		pos:  0,
45
+		word:        word,
46
+		envs:        env,
47
+		pos:         0,
48
+		escapeToken: escapeToken,
48 49
 	}
49 50
 	sw.scanner.Init(strings.NewReader(word))
50 51
 	_, words, err := sw.process()
... ...
@@ -138,8 +141,8 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
138 138
 			// Not special, just add it to the result
139 139
 			ch = sw.scanner.Next()
140 140
 
141
-			if ch == '\\' {
142
-				// '\' escapes, except end of line
141
+			if ch == sw.escapeToken {
142
+				// '\' (default escape token, but ` allowed) escapes, except end of line
143 143
 
144 144
 				ch = sw.scanner.Next()
145 145
 
... ...
@@ -179,7 +182,7 @@ func (sw *shellWord) processSingleQuote() (string, error) {
179 179
 
180 180
 func (sw *shellWord) processDoubleQuote() (string, error) {
181 181
 	// All chars up to the next " are taken as-is, even ', except any $ chars
182
-	// But you can escape " with a \
182
+	// But you can escape " with a \ (or ` if escape token set accordingly)
183 183
 	var result string
184 184
 
185 185
 	sw.scanner.Next()
... ...
@@ -198,7 +201,7 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
198 198
 			result += tmp
199 199
 		} else {
200 200
 			ch = sw.scanner.Next()
201
-			if ch == '\\' {
201
+			if ch == sw.escapeToken {
202 202
 				chNext := sw.scanner.Peek()
203 203
 
204 204
 				if chNext == scanner.EOF {
... ...
@@ -40,7 +40,7 @@ func TestShellParser4EnvVars(t *testing.T) {
40 40
 		words[0] = strings.TrimSpace(words[0])
41 41
 		words[1] = strings.TrimSpace(words[1])
42 42
 
43
-		newWord, err := ProcessWord(words[0], envs)
43
+		newWord, err := ProcessWord(words[0], envs, '\\')
44 44
 
45 45
 		if err != nil {
46 46
 			newWord = "error"
... ...
@@ -83,7 +83,7 @@ func TestShellParser4Words(t *testing.T) {
83 83
 		test := strings.TrimSpace(words[0])
84 84
 		expected := strings.Split(strings.TrimLeft(words[1], " "), ",")
85 85
 
86
-		result, err := ProcessWords(test, envs)
86
+		result, err := ProcessWords(test, envs, '\\')
87 87
 
88 88
 		if err != nil {
89 89
 			result = []string{"error"}
... ...
@@ -6884,6 +6884,42 @@ func (s *DockerSuite) TestBuildShellWindowsPowershell(c *check.C) {
6884 6884
 	}
6885 6885
 }
6886 6886
 
6887
+// Verify that escape is being correctly applied to words when escape directive is not \.
6888
+// Tests WORKDIR, ADD
6889
+func (s *DockerSuite) TestBuildEscapeNotBackslashWordTest(c *check.C) {
6890
+	testRequires(c, DaemonIsWindows)
6891
+	name := "testbuildescapenotbackslashwordtesta"
6892
+	_, out, err := buildImageWithOut(name,
6893
+		`# escape= `+"`"+`
6894
+		FROM `+minimalBaseImage()+`
6895
+        WORKDIR c:\windows
6896
+		RUN dir /w`,
6897
+		true)
6898
+	if err != nil {
6899
+		c.Fatal(err)
6900
+	}
6901
+	if !strings.Contains(strings.ToLower(out), "[system32]") {
6902
+		c.Fatalf("Line with '[windows]' not found in output %q", out)
6903
+	}
6904
+
6905
+	name = "testbuildescapenotbackslashwordtestb"
6906
+	_, out, err = buildImageWithOut(name,
6907
+		`# escape= `+"`"+`
6908
+		FROM `+minimalBaseImage()+`
6909
+		SHELL ["powershell.exe"]
6910
+        WORKDIR c:\foo
6911
+		ADD Dockerfile c:\foo\
6912
+		RUN dir Dockerfile`,
6913
+		true)
6914
+	if err != nil {
6915
+		c.Fatal(err)
6916
+	}
6917
+	if !strings.Contains(strings.ToLower(out), "-a----") {
6918
+		c.Fatalf("Line with '-a----' not found in output %q", out)
6919
+	}
6920
+
6921
+}
6922
+
6887 6923
 // #22868. Make sure shell-form CMD is marked as escaped in the config of the image
6888 6924
 func (s *DockerSuite) TestBuildCmdShellArgsEscaped(c *check.C) {
6889 6925
 	testRequires(c, DaemonIsWindows)