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>
This commit is contained in:
Doug Davis 2017-04-03 10:45:39 -07:00
parent 6311e9fc9d
commit 2fb7c3c4f0
5 changed files with 71 additions and 19 deletions

View file

@ -1,18 +1,21 @@
A|hello | hello A|hello | hello
A|he'll'o | hello A|he'll'o | hello
A|he'llo | hello A|he'llo | error
A|he\'llo | he'llo A|he\'llo | he'llo
A|he\\'llo | he\llo A|he\\'llo | error
A|abc\tdef | abctdef A|abc\tdef | abctdef
A|"abc\tdef" | abc\tdef A|"abc\tdef" | abc\tdef
A|"abc\\tdef" | abc\tdef
A|'abc\tdef' | abc\tdef A|'abc\tdef' | abc\tdef
A|hello\ | hello A|hello\ | hello
A|hello\\ | hello\ A|hello\\ | hello\
A|"hello | hello A|"hello | error
A|"hello\" | hello" A|"hello\" | error
A|"hel'lo" | hel'lo A|"hel'lo" | hel'lo
A|'hello | hello A|'hello | error
A|'hello\' | hello\ A|'hello\' | hello\
A|'hello\there' | hello\there
A|'hello\\there' | hello\\there
A|"''" | '' A|"''" | ''
A|$. | $. A|$. | $.
A|$1 | A|$1 |
@ -24,6 +27,8 @@ W|he$pwd. | he/home.
A|he$PWD | he/home A|he$PWD | he/home
A|he\$PWD | he$PWD A|he\$PWD | he$PWD
A|he\\$PWD | he\/home A|he\\$PWD | he\/home
A|"he\$PWD" | he$PWD
A|"he\\$PWD" | he\/home
A|he\${} | he${} A|he\${} | he${}
A|he\${}xx | he${}xx A|he\${}xx | he${}xx
A|he${} | he A|he${} | he
@ -60,18 +65,18 @@ A|he${XXX:-\$PWD:}xx | he$PWD:xx
A|he${XXX:-\${PWD}z}xx | he${PWDz}xx A|he${XXX:-\${PWD}z}xx | he${PWDz}xx
A|안녕하세요 | 안녕하세요 A|안녕하세요 | 안녕하세요
A|안'녕'하세요 | 안녕하세요 A|안'녕'하세요 | 안녕하세요
A|안'녕하세요 | 안녕하세요 A|안'녕하세요 | error
A|안녕\'하세요 | 안녕'하세요 A|안녕\'하세요 | 안녕'하세요
A|안\\'녕하세요 | 안\녕하세요 A|안\\'녕하세요 | error
A|안녕\t하세요 | 안녕t하세요 A|안녕\t하세요 | 안녕t하세요
A|"안녕\t하세요" | 안녕\t하세요 A|"안녕\t하세요" | 안녕\t하세요
A|'안녕\t하세요 | 안녕\t하세요 A|'안녕\t하세요 | error
A|안녕하세요\ | 안녕하세요 A|안녕하세요\ | 안녕하세요
A|안녕하세요\\ | 안녕하세요\ A|안녕하세요\\ | 안녕하세요\
A|"안녕하세요 | 안녕하세요 A|"안녕하세요 | error
A|"안녕하세요\" | 안녕하세요" A|"안녕하세요\" | error
A|"안녕'하세요" | 안녕'하세요 A|"안녕'하세요" | 안녕'하세요
A|'안녕하세요 | 안녕하세요 A|'안녕하세요 | error
A|'안녕하세요\' | 안녕하세요\ A|'안녕하세요\' | 안녕하세요\
A|안녕$1x | 안녕x A|안녕$1x | 안녕x
A|안녕$.x | 안녕$.x A|안녕$.x | 안녕$.x

View file

@ -166,15 +166,28 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) {
func (sw *shellWord) processSingleQuote() (string, error) { func (sw *shellWord) processSingleQuote() (string, error) {
// All chars between single quotes are taken as-is // All chars between single quotes are taken as-is
// Note, you can't escape ' // Note, you can't escape '
//
// From the "sh" man page:
// Single Quotes
// Enclosing characters in single quotes preserves the literal meaning of
// all the characters (except single quotes, making it impossible to put
// single-quotes in a single-quoted string).
var result string var result string
sw.scanner.Next() sw.scanner.Next()
for { for {
ch := sw.scanner.Next() ch := sw.scanner.Next()
if ch == '\'' || ch == scanner.EOF {
if ch == scanner.EOF {
return "", fmt.Errorf("unexpected end of statement while looking for matching single-quote")
}
if ch == '\'' {
break break
} }
result += string(ch) result += string(ch)
} }
@ -184,16 +197,32 @@ func (sw *shellWord) processSingleQuote() (string, error) {
func (sw *shellWord) processDoubleQuote() (string, error) { func (sw *shellWord) processDoubleQuote() (string, error) {
// All chars up to the next " are taken as-is, even ', except any $ chars // All chars up to the next " are taken as-is, even ', except any $ chars
// But you can escape " with a \ (or ` if escape token set accordingly) // But you can escape " with a \ (or ` if escape token set accordingly)
//
// From the "sh" man page:
// Double Quotes
// Enclosing characters within double quotes preserves the literal meaning
// of all characters except dollarsign ($), backquote (`), and backslash
// (\). The backslash inside double quotes is historically weird, and
// serves to quote only the following characters:
// $ ` " \ <newline>.
// Otherwise it remains literal.
var result string var result string
sw.scanner.Next() sw.scanner.Next()
for sw.scanner.Peek() != scanner.EOF { for {
ch := sw.scanner.Peek() ch := sw.scanner.Peek()
if ch == scanner.EOF {
return "", fmt.Errorf("unexpected end of statement while looking for matching double-quote")
}
if ch == '"' { if ch == '"' {
sw.scanner.Next() sw.scanner.Next()
break break
} }
if ch == '$' { if ch == '$' {
tmp, err := sw.processDollar() tmp, err := sw.processDollar()
if err != nil { if err != nil {
@ -210,8 +239,11 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
continue continue
} }
if chNext == '"' || chNext == '$' { // Note: for now don't do anything special with ` chars.
// \" and \$ can be escaped, all other \'s are left as-is // Not sure what to do with them anyway since we're not going
// to execute the text in there (not now anyway).
if chNext == '"' || chNext == '$' || chNext == sw.escapeToken {
// These chars can be escaped, all other \'s are left as-is
ch = sw.scanner.Next() ch = sw.scanner.Next()
} }
} }

View file

@ -75,8 +75,10 @@ func TestShellParser4Words(t *testing.T) {
envs := []string{} envs := []string{}
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
lineNum = lineNum + 1
if strings.HasPrefix(line, "#") { if strings.HasPrefix(line, "#") {
continue continue
@ -90,7 +92,7 @@ func TestShellParser4Words(t *testing.T) {
words := strings.Split(line, "|") words := strings.Split(line, "|")
if len(words) != 2 { if len(words) != 2 {
t.Fatalf("Error in '%s' - should be exactly one | in: %q", fn, line) t.Fatalf("Error in '%s'(line %d) - should be exactly one | in: %q", fn, lineNum, line)
} }
test := strings.TrimSpace(words[0]) test := strings.TrimSpace(words[0])
expected := strings.Split(strings.TrimLeft(words[1], " "), ",") expected := strings.Split(strings.TrimLeft(words[1], " "), ",")
@ -102,11 +104,11 @@ func TestShellParser4Words(t *testing.T) {
} }
if len(result) != len(expected) { if len(result) != len(expected) {
t.Fatalf("Error. %q was suppose to result in %q, but got %q instead", test, expected, result) t.Fatalf("Error on line %d. %q was suppose to result in %q, but got %q instead", lineNum, test, expected, result)
} }
for i, w := range expected { for i, w := range expected {
if w != result[i] { if w != result[i] {
t.Fatalf("Error. %q was suppose to result in %q, but got %q instead", test, expected, result) t.Fatalf("Error on line %d. %q was suppose to result in %q, but got %q instead", lineNum, test, expected, result)
} }
} }
} }

View file

@ -21,5 +21,10 @@ hel"lo${trailing}" | helloab c
hello" there " | hello there hello" there " | hello there
hello there | hello,there hello there | hello,there
hello\ there | hello there hello\ there | hello there
hello" there | hello there hello" there | error
hello\" there | hello",there hello\" there | hello",there
hello"\\there" | hello\there
hello"\there" | hello\there
hello'\\there' | hello\\there
hello'\there' | hello\there
hello'$there' | hello$there

View file

@ -182,6 +182,14 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) {
RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo) RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo)
ENV abc4 "\$foo" ENV abc4 "\$foo"
RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo) RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo)
ENV foo2="abc\def"
RUN [ "$foo2" = 'abc\def' ]
ENV foo3="abc\\def"
RUN [ "$foo3" = 'abc\def' ]
ENV foo4='abc\\def'
RUN [ "$foo4" = 'abc\\def' ]
ENV foo5='abc\def'
RUN [ "$foo5" = 'abc\def' ]
`)) `))
envResult := []string{} envResult := []string{}