Support platform file paths through escape
Signed-off-by: John Howard <jhoward@microsoft.com> Upstream-commit: e8e3dd32c5bad727010ec787f484b98942977531 Component: engine
This commit is contained in:
@ -94,12 +94,12 @@ func parseWords(rest string) []string {
|
||||
blankOK = true
|
||||
phase = inQuote
|
||||
}
|
||||
if ch == '\\' {
|
||||
if ch == tokenEscape {
|
||||
if pos+1 == len(rest) {
|
||||
continue // just skip \ at end
|
||||
continue // just skip an escape token at end of line
|
||||
}
|
||||
// If we're not quoted and we see a \, then always just
|
||||
// add \ plus the char to the word, even if the char
|
||||
// If we're not quoted and we see an escape token, then always just
|
||||
// add the escape token plus the char to the word, even if the char
|
||||
// is a quote.
|
||||
word += string(ch)
|
||||
pos++
|
||||
@ -112,11 +112,11 @@ func parseWords(rest string) []string {
|
||||
if ch == quote {
|
||||
phase = inWord
|
||||
}
|
||||
// \ is special except for ' quotes - can't escape anything for '
|
||||
if ch == '\\' && quote != '\'' {
|
||||
// The escape token is special except for ' quotes - can't escape anything for '
|
||||
if ch == tokenEscape && quote != '\'' {
|
||||
if pos+1 == len(rest) {
|
||||
phase = inWord
|
||||
continue // just skip \ at end
|
||||
continue // just skip the escape token at end
|
||||
}
|
||||
pos++
|
||||
nextCh := rune(rest[pos])
|
||||
|
||||
@ -3,6 +3,7 @@ package parser
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -37,10 +38,26 @@ type Node struct {
|
||||
var (
|
||||
dispatch map[string]func(string) (*Node, map[string]bool, error)
|
||||
tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
|
||||
tokenLineContinuation = regexp.MustCompile(`\\[ \t]*$`)
|
||||
tokenLineContinuation *regexp.Regexp
|
||||
tokenEscape rune
|
||||
tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P<escapechar>.).*$`)
|
||||
tokenComment = regexp.MustCompile(`^#.*$`)
|
||||
lookingForDirectives bool
|
||||
directiveEscapeSeen bool
|
||||
)
|
||||
|
||||
const defaultTokenEscape = "\\"
|
||||
|
||||
// setTokenEscape sets the default token for escaping characters in a Dockerfile.
|
||||
func setTokenEscape(s string) error {
|
||||
if s != "`" && s != "\\" {
|
||||
return fmt.Errorf("invalid ESCAPE '%s'. Must be ` or \\", s)
|
||||
}
|
||||
tokenEscape = rune(s[0])
|
||||
tokenLineContinuation = regexp.MustCompile(`\` + s + `[ \t]*$`)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Dispatch Table. see line_parsers.go for the parse functions.
|
||||
// The command is parsed and mapped to the line parser. The line parser
|
||||
@ -70,6 +87,29 @@ func init() {
|
||||
|
||||
// ParseLine parse a line and return the remainder.
|
||||
func ParseLine(line string) (string, *Node, error) {
|
||||
|
||||
// Handle the parser directive '# escape=<char>. Parser directives must preceed
|
||||
// any builder instruction or other comments, and cannot be repeated.
|
||||
if lookingForDirectives {
|
||||
tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line))
|
||||
if len(tecMatch) > 0 {
|
||||
if directiveEscapeSeen == true {
|
||||
return "", nil, fmt.Errorf("only one escape parser directive can be used")
|
||||
}
|
||||
for i, n := range tokenEscapeCommand.SubexpNames() {
|
||||
if n == "escapechar" {
|
||||
if err := setTokenEscape(tecMatch[i]); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
directiveEscapeSeen = true
|
||||
return "", nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lookingForDirectives = false
|
||||
|
||||
if line = stripComments(line); line == "" {
|
||||
return "", nil, nil
|
||||
}
|
||||
@ -103,6 +143,9 @@ func ParseLine(line string) (string, *Node, error) {
|
||||
// Parse is the main parse routine.
|
||||
// It handles an io.ReadWriteCloser and returns the root of the AST.
|
||||
func Parse(rwc io.Reader) (*Node, error) {
|
||||
directiveEscapeSeen = false
|
||||
lookingForDirectives = true
|
||||
setTokenEscape(defaultTokenEscape) // Assume the default token for escape
|
||||
currentLine := 0
|
||||
root := &Node{}
|
||||
root.StartLine = -1
|
||||
|
||||
@ -131,22 +131,22 @@ func TestLineInformation(t *testing.T) {
|
||||
t.Fatalf("Error parsing dockerfile %s: %v", testFileLineInfo, err)
|
||||
}
|
||||
|
||||
if ast.StartLine != 4 || ast.EndLine != 30 {
|
||||
fmt.Fprintf(os.Stderr, "Wrong root line information: expected(%d-%d), actual(%d-%d)\n", 4, 30, ast.StartLine, ast.EndLine)
|
||||
if ast.StartLine != 5 || ast.EndLine != 31 {
|
||||
fmt.Fprintf(os.Stderr, "Wrong root line information: expected(%d-%d), actual(%d-%d)\n", 5, 31, ast.StartLine, ast.EndLine)
|
||||
t.Fatalf("Root line information doesn't match result.")
|
||||
}
|
||||
if len(ast.Children) != 3 {
|
||||
fmt.Fprintf(os.Stderr, "Wrong number of child: expected(%d), actual(%d)\n", 3, len(ast.Children))
|
||||
t.Fatalf("Root line information doesn't match result.")
|
||||
t.Fatalf("Root line information doesn't match result for %s", testFileLineInfo)
|
||||
}
|
||||
expected := [][]int{
|
||||
{4, 4},
|
||||
{10, 11},
|
||||
{16, 30},
|
||||
{5, 5},
|
||||
{11, 12},
|
||||
{17, 31},
|
||||
}
|
||||
for i, child := range ast.Children {
|
||||
if child.StartLine != expected[i][0] || child.EndLine != expected[i][1] {
|
||||
fmt.Fprintf(os.Stderr, "Wrong line information for child %d: expected(%d-%d), actual(%d-%d)\n",
|
||||
t.Logf("Wrong line information for child %d: expected(%d-%d), actual(%d-%d)\n",
|
||||
i, expected[i][0], expected[i][1], child.StartLine, child.EndLine)
|
||||
t.Fatalf("Root line information doesn't match result.")
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# ESCAPE=\
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#escape=\
|
||||
FROM brimstone/ubuntu:14.04
|
||||
|
||||
MAINTAINER brimstone@the.narro.ws
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
# Comment here. Should not be looking for the following parser directive.
|
||||
# Hence the following line will be ignored, and the subsequent backslash
|
||||
# continuation will be the default.
|
||||
# escape = `
|
||||
|
||||
FROM image
|
||||
MAINTAINER foo@bar.com
|
||||
ENV GOPATH \
|
||||
\go
|
||||
@ -0,0 +1,3 @@
|
||||
(from "image")
|
||||
(maintainer "foo@bar.com")
|
||||
(env "GOPATH" "\\go")
|
||||
@ -0,0 +1,7 @@
|
||||
# escape = ``
|
||||
# There is no white space line after the directives. This still succeeds, but goes
|
||||
# against best practices.
|
||||
FROM image
|
||||
MAINTAINER foo@bar.com
|
||||
ENV GOPATH `
|
||||
\go
|
||||
@ -0,0 +1,3 @@
|
||||
(from "image")
|
||||
(maintainer "foo@bar.com")
|
||||
(env "GOPATH" "\\go")
|
||||
@ -0,0 +1,6 @@
|
||||
#escape = `
|
||||
|
||||
FROM image
|
||||
MAINTAINER foo@bar.com
|
||||
ENV GOPATH `
|
||||
\go
|
||||
@ -0,0 +1,3 @@
|
||||
(from "image")
|
||||
(maintainer "foo@bar.com")
|
||||
(env "GOPATH" "\\go")
|
||||
Reference in New Issue
Block a user