Sebastiaan van Stijn
2024-06-04 11:33:43 +02:00
parent dbdd3601eb
commit 9358f84668
146 changed files with 2661 additions and 1102 deletions

View File

@ -17,6 +17,7 @@ import (
var (
errDockerfileNotStringArray = errors.New("when using JSON array syntax, arrays must be comprised of strings only")
errDockerfileNotJSONArray = errors.New("not a JSON array")
)
const (
@ -58,11 +59,11 @@ func parseWords(rest string, d *directives) []string {
words := []string{}
phase := inSpaces
word := ""
quote := '\000'
blankOK := false
var ch rune
var chWidth int
var sbuilder strings.Builder
for pos := 0; pos <= len(rest); pos += chWidth {
if pos != len(rest) {
@ -79,18 +80,18 @@ func parseWords(rest string, d *directives) []string {
phase = inWord // found it, fall through
}
if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
if blankOK || len(word) > 0 {
words = append(words, word)
if blankOK || sbuilder.Len() > 0 {
words = append(words, sbuilder.String())
}
break
}
if phase == inWord {
if unicode.IsSpace(ch) {
phase = inSpaces
if blankOK || len(word) > 0 {
words = append(words, word)
if blankOK || sbuilder.Len() > 0 {
words = append(words, sbuilder.String())
}
word = ""
sbuilder.Reset()
blankOK = false
continue
}
@ -106,11 +107,11 @@ func parseWords(rest string, d *directives) []string {
// 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)
sbuilder.WriteRune(ch)
pos += chWidth
ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
}
word += string(ch)
sbuilder.WriteRune(ch)
continue
}
if phase == inQuote {
@ -124,10 +125,10 @@ func parseWords(rest string, d *directives) []string {
continue // just skip the escape token at end
}
pos += chWidth
word += string(ch)
sbuilder.WriteRune(ch)
ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
}
word += string(ch)
sbuilder.WriteRune(ch)
}
}
@ -277,7 +278,7 @@ func parseString(rest string, d *directives) (*Node, map[string]bool, error) {
func parseJSON(rest string) (*Node, map[string]bool, error) {
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
if !strings.HasPrefix(rest, "[") {
return nil, nil, errors.Errorf("Error parsing %q as a JSON array", rest)
return nil, nil, errDockerfileNotJSONArray
}
var myJSON []interface{}

View File

@ -114,7 +114,6 @@ type Heredoc struct {
var (
dispatch map[string]func(string, *directives) (*Node, map[string]bool, error)
reWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`)
reComment = regexp.MustCompile(`^#.*$`)
reHeredoc = regexp.MustCompile(`^(\d*)<<(-?)([^<]*)$`)
reLeadingTabs = regexp.MustCompile(`(?m)^\t+`)
)
@ -169,8 +168,8 @@ func (d *directives) setEscapeToken(s string) error {
// possibleParserDirective looks for parser directives, eg '# escapeToken=<char>'.
// Parser directives must precede any builder instruction or other comments,
// and cannot be repeated.
func (d *directives) possibleParserDirective(line string) error {
directive, err := d.parser.ParseLine([]byte(line))
func (d *directives) possibleParserDirective(line []byte) error {
directive, err := d.parser.ParseLine(line)
if err != nil {
return err
}
@ -284,6 +283,7 @@ func Parse(rwc io.Reader) (*Result, error) {
scanner.Split(scanLines)
warnings := []Warning{}
var comments []string
buf := &bytes.Buffer{}
var err error
for scanner.Scan() {
@ -307,10 +307,12 @@ func Parse(rwc io.Reader) (*Result, error) {
currentLine++
startLine := currentLine
line, isEndOfLine := trimContinuationCharacter(string(bytesRead), d)
if isEndOfLine && line == "" {
bytesRead, isEndOfLine := trimContinuationCharacter(bytesRead, d)
if isEndOfLine && len(bytesRead) == 0 {
continue
}
buf.Reset()
buf.Write(bytesRead)
var hasEmptyContinuationLine bool
for !isEndOfLine && scanner.Scan() {
@ -329,11 +331,12 @@ func Parse(rwc io.Reader) (*Result, error) {
continue
}
continuationLine := string(bytesRead)
continuationLine, isEndOfLine = trimContinuationCharacter(continuationLine, d)
line += continuationLine
bytesRead, isEndOfLine = trimContinuationCharacter(bytesRead, d)
buf.Write(bytesRead)
}
line := buf.String()
if hasEmptyContinuationLine {
warnings = append(warnings, Warning{
Short: "Empty continuation line found in: " + line,
@ -348,7 +351,7 @@ func Parse(rwc io.Reader) (*Result, error) {
return nil, withLocation(err, startLine, currentLine)
}
if child.canContainHeredoc() {
if child.canContainHeredoc() && strings.Contains(line, "<<") {
heredocs, err := heredocsFromLine(line)
if err != nil {
return nil, withLocation(err, startLine, currentLine)
@ -415,7 +418,7 @@ func heredocFromMatch(match []string) (*Heredoc, error) {
// If there are quotes in one but not the other, then we know that some
// part of the heredoc word is quoted, so we shouldn't expand the content.
shlex.RawQuotes = false
words, err := shlex.ProcessWords(rest, []string{})
words, err := shlex.ProcessWords(rest, emptyEnvs{})
if err != nil {
return nil, err
}
@ -425,7 +428,7 @@ func heredocFromMatch(match []string) (*Heredoc, error) {
}
shlex.RawQuotes = true
wordsRaw, err := shlex.ProcessWords(rest, []string{})
wordsRaw, err := shlex.ProcessWords(rest, emptyEnvs{})
if err != nil {
return nil, err
}
@ -466,7 +469,7 @@ func heredocsFromLine(line string) ([]Heredoc, error) {
shlex.RawQuotes = true
shlex.RawEscapes = true
shlex.SkipUnsetEnv = true
words, _ := shlex.ProcessWords(line, []string{})
words, _ := shlex.ProcessWords(line, emptyEnvs{})
var docs []Heredoc
for _, word := range words {
@ -487,7 +490,10 @@ func ChompHeredocContent(src string) string {
}
func trimComments(src []byte) []byte {
return reComment.ReplaceAll(src, []byte{})
if !isComment(src) {
return src
}
return nil
}
func trimLeadingWhitespace(src []byte) []byte {
@ -501,7 +507,8 @@ func trimNewline(src []byte) []byte {
}
func isComment(line []byte) bool {
return reComment.Match(trimLeadingWhitespace(trimNewline(line)))
line = trimLeadingWhitespace(line)
return len(line) > 0 && line[0] == '#'
}
func isEmptyContinuationLine(line []byte) bool {
@ -510,9 +517,9 @@ func isEmptyContinuationLine(line []byte) bool {
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
func trimContinuationCharacter(line string, d *directives) (string, bool) {
if d.lineContinuationRegex.MatchString(line) {
line = d.lineContinuationRegex.ReplaceAllString(line, "$1")
func trimContinuationCharacter(line []byte, d *directives) ([]byte, bool) {
if d.lineContinuationRegex.Match(line) {
line = d.lineContinuationRegex.ReplaceAll(line, []byte("$1"))
return line, false
}
return line, true
@ -525,7 +532,7 @@ func processLine(d *directives, token []byte, stripLeftWhitespace bool) ([]byte,
if stripLeftWhitespace {
token = trimLeadingWhitespace(token)
}
return trimComments(token), d.possibleParserDirective(string(token))
return trimComments(token), d.possibleParserDirective(token)
}
// Variation of bufio.ScanLines that preserves the line endings
@ -550,3 +557,13 @@ func handleScannerError(err error) error {
return err
}
}
type emptyEnvs struct{}
func (emptyEnvs) Get(string) (string, bool) {
return "", false
}
func (emptyEnvs) Keys() []string {
return nil
}

View File

@ -36,7 +36,7 @@ func extractBuilderFlags(line string) (string, []string, error) {
words := []string{}
phase := inSpaces
word := ""
sbuilder := &strings.Builder{}
quote := '\000'
blankOK := false
var ch rune
@ -62,13 +62,14 @@ func extractBuilderFlags(line string) (string, []string, error) {
phase = inWord // found something with "--", fall through
}
if (phase == inWord || phase == inQuote) && (pos == len(line)) {
if word != "--" && (blankOK || len(word) > 0) {
if word := sbuilder.String(); word != "--" && (blankOK || len(word) > 0) {
words = append(words, word)
}
break
}
if phase == inWord {
if unicode.IsSpace(ch) {
word := sbuilder.String()
phase = inSpaces
if word == "--" {
return line[pos:], words, nil
@ -76,7 +77,7 @@ func extractBuilderFlags(line string) (string, []string, error) {
if blankOK || len(word) > 0 {
words = append(words, word)
}
word = ""
sbuilder.Reset()
blankOK = false
continue
}
@ -93,7 +94,9 @@ func extractBuilderFlags(line string) (string, []string, error) {
pos++
ch = rune(line[pos])
}
word += string(ch)
if _, err := sbuilder.WriteRune(ch); err != nil {
return "", nil, err
}
continue
}
if phase == inQuote {
@ -109,7 +112,9 @@ func extractBuilderFlags(line string) (string, []string, error) {
pos++
ch = rune(line[pos])
}
word += string(ch)
if _, err := sbuilder.WriteRune(ch); err != nil {
return "", nil, err
}
}
}