Browse code

Minor tweaks to quotes in env vars

Addresses part of #32140, in particular:
- this will make it so that double backslashes in double-quoted
strings will result in a single backslash. While in single quotes it remains
a double backslash.
- missing closing " and ' will now generate an error

Signed-off-by: Doug Davis <dug@us.ibm.com>

Doug Davis authored on 2017/04/04 02:45:39
Showing 5 changed files
... ...
@@ -1,18 +1,21 @@
1 1
 A|hello                    |     hello
2 2
 A|he'll'o                  |     hello
3
-A|he'llo                   |     hello
3
+A|he'llo                   |     error
4 4
 A|he\'llo                  |     he'llo
5
-A|he\\'llo                 |     he\llo
5
+A|he\\'llo                 |     error
6 6
 A|abc\tdef                 |     abctdef
7 7
 A|"abc\tdef"               |     abc\tdef
8
+A|"abc\\tdef"              |     abc\tdef
8 9
 A|'abc\tdef'               |     abc\tdef
9 10
 A|hello\                   |     hello
10 11
 A|hello\\                  |     hello\
11
-A|"hello                   |     hello
12
-A|"hello\"                 |     hello"
12
+A|"hello                   |     error
13
+A|"hello\"                 |     error
13 14
 A|"hel'lo"                 |     hel'lo
14
-A|'hello                   |     hello
15
+A|'hello                   |     error
15 16
 A|'hello\'                 |     hello\
17
+A|'hello\there'            |     hello\there
18
+A|'hello\\there'           |     hello\\there
16 19
 A|"''"                     |     ''
17 20
 A|$.                       |     $.
18 21
 A|$1                       |
... ...
@@ -24,6 +27,8 @@ W|he$pwd.                  |     he/home.
24 24
 A|he$PWD                   |     he/home
25 25
 A|he\$PWD                  |     he$PWD
26 26
 A|he\\$PWD                 |     he\/home
27
+A|"he\$PWD"                |     he$PWD
28
+A|"he\\$PWD"               |     he\/home
27 29
 A|he\${}                   |     he${}
28 30
 A|he\${}xx                 |     he${}xx
29 31
 A|he${}                    |     he
... ...
@@ -60,18 +65,18 @@ A|he${XXX:-\$PWD:}xx       |     he$PWD:xx
60 60
 A|he${XXX:-\${PWD}z}xx     |     he${PWDz}xx
61 61
 A|안녕하세요                 |     안녕하세요
62 62
 A|안'녕'하세요               |     안녕하세요
63
-A|안'녕하세요                |     안녕하세요
63
+A|안'녕하세요                |     error
64 64
 A|안녕\'하세요               |     안녕'하세요
65
-A|안\\'녕하세요              |     안\녕하세요
65
+A|안\\'녕하세요              |     error
66 66
 A|안녕\t하세요               |     안녕t하세요
67 67
 A|"안녕\t하세요"             |     안녕\t하세요
68
-A|'안녕\t하세요              |     안녕\t하세요
68
+A|'안녕\t하세요              |     error
69 69
 A|안녕하세요\                |     안녕하세요
70 70
 A|안녕하세요\\               |     안녕하세요\
71
-A|"안녕하세요                |     안녕하세요
72
-A|"안녕하세요\"              |     안녕하세요"
71
+A|"안녕하세요                |     error
72
+A|"안녕하세요\"              |     error
73 73
 A|"안녕'하세요"              |     안녕'하세요
74
-A|'안녕하세요                |     안녕하세요
74
+A|'안녕하세요                |     error
75 75
 A|'안녕하세요\'              |     안녕하세요\
76 76
 A|안녕$1x                    |     안녕x
77 77
 A|안녕$.x                    |     안녕$.x
... ...
@@ -166,15 +166,28 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
166 166
 func (sw *shellWord) processSingleQuote() (string, error) {
167 167
 	// All chars between single quotes are taken as-is
168 168
 	// Note, you can't escape '
169
+	//
170
+	// From the "sh" man page:
171
+	// Single Quotes
172
+	//   Enclosing characters in single quotes preserves the literal meaning of
173
+	//   all the characters (except single quotes, making it impossible to put
174
+	//   single-quotes in a single-quoted string).
175
+
169 176
 	var result string
170 177
 
171 178
 	sw.scanner.Next()
172 179
 
173 180
 	for {
174 181
 		ch := sw.scanner.Next()
175
-		if ch == '\'' || ch == scanner.EOF {
182
+
183
+		if ch == scanner.EOF {
184
+			return "", fmt.Errorf("unexpected end of statement while looking for matching single-quote")
185
+		}
186
+
187
+		if ch == '\'' {
176 188
 			break
177 189
 		}
190
+
178 191
 		result += string(ch)
179 192
 	}
180 193
 
... ...
@@ -184,16 +197,32 @@ func (sw *shellWord) processSingleQuote() (string, error) {
184 184
 func (sw *shellWord) processDoubleQuote() (string, error) {
185 185
 	// All chars up to the next " are taken as-is, even ', except any $ chars
186 186
 	// But you can escape " with a \ (or ` if escape token set accordingly)
187
+	//
188
+	// From the "sh" man page:
189
+	// Double Quotes
190
+	//  Enclosing characters within double quotes preserves the literal meaning
191
+	//  of all characters except dollarsign ($), backquote (`), and backslash
192
+	//  (\).  The backslash inside double quotes is historically weird, and
193
+	//  serves to quote only the following characters:
194
+	//    $ ` " \ <newline>.
195
+	//  Otherwise it remains literal.
196
+
187 197
 	var result string
188 198
 
189 199
 	sw.scanner.Next()
190 200
 
191
-	for sw.scanner.Peek() != scanner.EOF {
201
+	for {
192 202
 		ch := sw.scanner.Peek()
203
+
204
+		if ch == scanner.EOF {
205
+			return "", fmt.Errorf("unexpected end of statement while looking for matching double-quote")
206
+		}
207
+
193 208
 		if ch == '"' {
194 209
 			sw.scanner.Next()
195 210
 			break
196 211
 		}
212
+
197 213
 		if ch == '$' {
198 214
 			tmp, err := sw.processDollar()
199 215
 			if err != nil {
... ...
@@ -210,8 +239,11 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
210 210
 					continue
211 211
 				}
212 212
 
213
-				if chNext == '"' || chNext == '$' {
214
-					// \" and \$ can be escaped, all other \'s are left as-is
213
+				// Note: for now don't do anything special with ` chars.
214
+				// Not sure what to do with them anyway since we're not going
215
+				// to execute the text in there (not now anyway).
216
+				if chNext == '"' || chNext == '$' || chNext == sw.escapeToken {
217
+					// These chars can be escaped, all other \'s are left as-is
215 218
 					ch = sw.scanner.Next()
216 219
 				}
217 220
 			}
... ...
@@ -75,8 +75,10 @@ func TestShellParser4Words(t *testing.T) {
75 75
 
76 76
 	envs := []string{}
77 77
 	scanner := bufio.NewScanner(file)
78
+	lineNum := 0
78 79
 	for scanner.Scan() {
79 80
 		line := scanner.Text()
81
+		lineNum = lineNum + 1
80 82
 
81 83
 		if strings.HasPrefix(line, "#") {
82 84
 			continue
... ...
@@ -90,7 +92,7 @@ func TestShellParser4Words(t *testing.T) {
90 90
 
91 91
 		words := strings.Split(line, "|")
92 92
 		if len(words) != 2 {
93
-			t.Fatalf("Error in '%s' - should be exactly one | in: %q", fn, line)
93
+			t.Fatalf("Error in '%s'(line %d) - should be exactly one | in: %q", fn, lineNum, line)
94 94
 		}
95 95
 		test := strings.TrimSpace(words[0])
96 96
 		expected := strings.Split(strings.TrimLeft(words[1], " "), ",")
... ...
@@ -102,11 +104,11 @@ func TestShellParser4Words(t *testing.T) {
102 102
 		}
103 103
 
104 104
 		if len(result) != len(expected) {
105
-			t.Fatalf("Error. %q was suppose to result in %q, but got %q instead", test, expected, result)
105
+			t.Fatalf("Error on line %d. %q was suppose to result in %q, but got %q instead", lineNum, test, expected, result)
106 106
 		}
107 107
 		for i, w := range expected {
108 108
 			if w != result[i] {
109
-				t.Fatalf("Error. %q was suppose to result in %q, but got %q instead", test, expected, result)
109
+				t.Fatalf("Error on line %d. %q was suppose to result in %q, but got %q instead", lineNum, test, expected, result)
110 110
 			}
111 111
 		}
112 112
 	}
... ...
@@ -21,5 +21,10 @@ hel"lo${trailing}" | helloab c
21 21
 hello" there  " | hello there  
22 22
 hello there     | hello,there
23 23
 hello\ there | hello there
24
-hello" there | hello there
24
+hello" there | error
25 25
 hello\" there | hello",there
26
+hello"\\there" | hello\there
27
+hello"\there" | hello\there
28
+hello'\\there' | hello\\there
29
+hello'\there' | hello\there
30
+hello'$there' | hello$there
... ...
@@ -182,6 +182,14 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) {
182 182
   RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo)
183 183
   ENV abc4 "\$foo"
184 184
   RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo)
185
+  ENV foo2="abc\def"
186
+  RUN [ "$foo2" = 'abc\def' ]
187
+  ENV foo3="abc\\def"
188
+  RUN [ "$foo3" = 'abc\def' ]
189
+  ENV foo4='abc\\def'
190
+  RUN [ "$foo4" = 'abc\\def' ]
191
+  ENV foo5='abc\def'
192
+  RUN [ "$foo5" = 'abc\def' ]
185 193
   `))
186 194
 
187 195
 	envResult := []string{}