diff --git a/builder/dockerfile/evaluator.go b/builder/dockerfile/evaluator.go index 304739aa82..c3843936f2 100644 --- a/builder/dockerfile/evaluator.go +++ b/builder/dockerfile/evaluator.go @@ -169,13 +169,13 @@ func (b *Builder) dispatch(stepN int, stepTotal int, ast *parser.Node) error { var words []string if allowWordExpansion[cmd] { - words, err = ProcessWords(str, envs) + words, err = ProcessWords(str, envs, b.directive.EscapeToken) if err != nil { return err } strList = append(strList, words...) } else { - str, err = ProcessWord(str, envs) + str, err = ProcessWord(str, envs, b.directive.EscapeToken) if err != nil { return err } diff --git a/builder/dockerfile/shell_parser.go b/builder/dockerfile/shell_parser.go index c714266778..3a095299a8 100644 --- a/builder/dockerfile/shell_parser.go +++ b/builder/dockerfile/shell_parser.go @@ -14,19 +14,21 @@ import ( ) type shellWord struct { - word string - scanner scanner.Scanner - envs []string - pos int + word string + scanner scanner.Scanner + envs []string + pos int + escapeToken rune } // ProcessWord will use the 'env' list of environment variables, // and replace any env var references in 'word'. -func ProcessWord(word string, env []string) (string, error) { +func ProcessWord(word string, env []string, escapeToken rune) (string, error) { sw := &shellWord{ - word: word, - envs: env, - pos: 0, + word: word, + envs: env, + pos: 0, + escapeToken: escapeToken, } sw.scanner.Init(strings.NewReader(word)) word, _, err := sw.process() @@ -40,11 +42,12 @@ func ProcessWord(word string, env []string) (string, error) { // this splitting is done **after** the env var substitutions are done. // Note, each one is trimmed to remove leading and trailing spaces (unless // they are quoted", but ProcessWord retains spaces between words. -func ProcessWords(word string, env []string) ([]string, error) { +func ProcessWords(word string, env []string, escapeToken rune) ([]string, error) { sw := &shellWord{ - word: word, - envs: env, - pos: 0, + word: word, + envs: env, + pos: 0, + escapeToken: escapeToken, } sw.scanner.Init(strings.NewReader(word)) _, words, err := sw.process() @@ -138,8 +141,8 @@ func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) { // Not special, just add it to the result ch = sw.scanner.Next() - if ch == '\\' { - // '\' escapes, except end of line + if ch == sw.escapeToken { + // '\' (default escape token, but ` allowed) escapes, except end of line ch = sw.scanner.Next() @@ -179,7 +182,7 @@ func (sw *shellWord) processSingleQuote() (string, error) { func (sw *shellWord) processDoubleQuote() (string, error) { // All chars up to the next " are taken as-is, even ', except any $ chars - // But you can escape " with a \ + // But you can escape " with a \ (or ` if escape token set accordingly) var result string sw.scanner.Next() @@ -198,7 +201,7 @@ func (sw *shellWord) processDoubleQuote() (string, error) { result += tmp } else { ch = sw.scanner.Next() - if ch == '\\' { + if ch == sw.escapeToken { chNext := sw.scanner.Peek() if chNext == scanner.EOF { diff --git a/builder/dockerfile/shell_parser_test.go b/builder/dockerfile/shell_parser_test.go index 81ac591e99..35dea63fb1 100644 --- a/builder/dockerfile/shell_parser_test.go +++ b/builder/dockerfile/shell_parser_test.go @@ -40,7 +40,7 @@ func TestShellParser4EnvVars(t *testing.T) { words[0] = strings.TrimSpace(words[0]) words[1] = strings.TrimSpace(words[1]) - newWord, err := ProcessWord(words[0], envs) + newWord, err := ProcessWord(words[0], envs, '\\') if err != nil { newWord = "error" @@ -83,7 +83,7 @@ func TestShellParser4Words(t *testing.T) { test := strings.TrimSpace(words[0]) expected := strings.Split(strings.TrimLeft(words[1], " "), ",") - result, err := ProcessWords(test, envs) + result, err := ProcessWords(test, envs, '\\') if err != nil { result = []string{"error"} diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index bf825c2302..460549a240 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -6884,6 +6884,42 @@ func (s *DockerSuite) TestBuildShellWindowsPowershell(c *check.C) { } } +// Verify that escape is being correctly applied to words when escape directive is not \. +// Tests WORKDIR, ADD +func (s *DockerSuite) TestBuildEscapeNotBackslashWordTest(c *check.C) { + testRequires(c, DaemonIsWindows) + name := "testbuildescapenotbackslashwordtesta" + _, out, err := buildImageWithOut(name, + `# escape= `+"`"+` + FROM `+minimalBaseImage()+` + WORKDIR c:\windows + RUN dir /w`, + true) + if err != nil { + c.Fatal(err) + } + if !strings.Contains(strings.ToLower(out), "[system32]") { + c.Fatalf("Line with '[windows]' not found in output %q", out) + } + + name = "testbuildescapenotbackslashwordtestb" + _, out, err = buildImageWithOut(name, + `# escape= `+"`"+` + FROM `+minimalBaseImage()+` + SHELL ["powershell.exe"] + WORKDIR c:\foo + ADD Dockerfile c:\foo\ + RUN dir Dockerfile`, + true) + if err != nil { + c.Fatal(err) + } + if !strings.Contains(strings.ToLower(out), "-a----") { + c.Fatalf("Line with '-a----' not found in output %q", out) + } + +} + // #22868. Make sure shell-form CMD is marked as escaped in the config of the image func (s *DockerSuite) TestBuildCmdShellArgsEscaped(c *check.C) { testRequires(c, DaemonIsWindows)