github.com/sams1990/dockerrepo@v17.12.1-ce-rc2+incompatible/builder/dockerfile/parser/line_parsers.go (about)

     1  package parser
     2  
     3  // line parsers are dispatch calls that parse a single unit of text into a
     4  // Node object which contains the whole statement. Dockerfiles have varied
     5  // (but not usually unique, see ONBUILD for a unique example) parsing rules
     6  // per-command, and these unify the processing in a way that makes it
     7  // manageable.
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"sort"
    14  	"strings"
    15  	"unicode"
    16  	"unicode/utf8"
    17  
    18  	"github.com/docker/docker/builder/dockerfile/command"
    19  )
    20  
    21  var (
    22  	errDockerfileNotStringArray = errors.New("when using JSON array syntax, arrays must be comprised of strings only")
    23  )
    24  
    25  const (
    26  	commandLabel = "LABEL"
    27  )
    28  
    29  // ignore the current argument. This will still leave a command parsed, but
    30  // will not incorporate the arguments into the ast.
    31  func parseIgnore(rest string, d *Directive) (*Node, map[string]bool, error) {
    32  	return &Node{}, nil, nil
    33  }
    34  
    35  // used for onbuild. Could potentially be used for anything that represents a
    36  // statement with sub-statements.
    37  //
    38  // ONBUILD RUN foo bar -> (onbuild (run foo bar))
    39  //
    40  func parseSubCommand(rest string, d *Directive) (*Node, map[string]bool, error) {
    41  	if rest == "" {
    42  		return nil, nil, nil
    43  	}
    44  
    45  	child, err := newNodeFromLine(rest, d)
    46  	if err != nil {
    47  		return nil, nil, err
    48  	}
    49  
    50  	return &Node{Children: []*Node{child}}, nil, nil
    51  }
    52  
    53  // helper to parse words (i.e space delimited or quoted strings) in a statement.
    54  // The quotes are preserved as part of this function and they are stripped later
    55  // as part of processWords().
    56  func parseWords(rest string, d *Directive) []string {
    57  	const (
    58  		inSpaces = iota // looking for start of a word
    59  		inWord
    60  		inQuote
    61  	)
    62  
    63  	words := []string{}
    64  	phase := inSpaces
    65  	word := ""
    66  	quote := '\000'
    67  	blankOK := false
    68  	var ch rune
    69  	var chWidth int
    70  
    71  	for pos := 0; pos <= len(rest); pos += chWidth {
    72  		if pos != len(rest) {
    73  			ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
    74  		}
    75  
    76  		if phase == inSpaces { // Looking for start of word
    77  			if pos == len(rest) { // end of input
    78  				break
    79  			}
    80  			if unicode.IsSpace(ch) { // skip spaces
    81  				continue
    82  			}
    83  			phase = inWord // found it, fall through
    84  		}
    85  		if (phase == inWord || phase == inQuote) && (pos == len(rest)) {
    86  			if blankOK || len(word) > 0 {
    87  				words = append(words, word)
    88  			}
    89  			break
    90  		}
    91  		if phase == inWord {
    92  			if unicode.IsSpace(ch) {
    93  				phase = inSpaces
    94  				if blankOK || len(word) > 0 {
    95  					words = append(words, word)
    96  				}
    97  				word = ""
    98  				blankOK = false
    99  				continue
   100  			}
   101  			if ch == '\'' || ch == '"' {
   102  				quote = ch
   103  				blankOK = true
   104  				phase = inQuote
   105  			}
   106  			if ch == d.escapeToken {
   107  				if pos+chWidth == len(rest) {
   108  					continue // just skip an escape token at end of line
   109  				}
   110  				// If we're not quoted and we see an escape token, then always just
   111  				// add the escape token plus the char to the word, even if the char
   112  				// is a quote.
   113  				word += string(ch)
   114  				pos += chWidth
   115  				ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
   116  			}
   117  			word += string(ch)
   118  			continue
   119  		}
   120  		if phase == inQuote {
   121  			if ch == quote {
   122  				phase = inWord
   123  			}
   124  			// The escape token is special except for ' quotes - can't escape anything for '
   125  			if ch == d.escapeToken && quote != '\'' {
   126  				if pos+chWidth == len(rest) {
   127  					phase = inWord
   128  					continue // just skip the escape token at end
   129  				}
   130  				pos += chWidth
   131  				word += string(ch)
   132  				ch, chWidth = utf8.DecodeRuneInString(rest[pos:])
   133  			}
   134  			word += string(ch)
   135  		}
   136  	}
   137  
   138  	return words
   139  }
   140  
   141  // parse environment like statements. Note that this does *not* handle
   142  // variable interpolation, which will be handled in the evaluator.
   143  func parseNameVal(rest string, key string, d *Directive) (*Node, error) {
   144  	// This is kind of tricky because we need to support the old
   145  	// variant:   KEY name value
   146  	// as well as the new one:    KEY name=value ...
   147  	// The trigger to know which one is being used will be whether we hit
   148  	// a space or = first.  space ==> old, "=" ==> new
   149  
   150  	words := parseWords(rest, d)
   151  	if len(words) == 0 {
   152  		return nil, nil
   153  	}
   154  
   155  	// Old format (KEY name value)
   156  	if !strings.Contains(words[0], "=") {
   157  		parts := tokenWhitespace.Split(rest, 2)
   158  		if len(parts) < 2 {
   159  			return nil, fmt.Errorf(key + " must have two arguments")
   160  		}
   161  		return newKeyValueNode(parts[0], parts[1]), nil
   162  	}
   163  
   164  	var rootNode *Node
   165  	var prevNode *Node
   166  	for _, word := range words {
   167  		if !strings.Contains(word, "=") {
   168  			return nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word)
   169  		}
   170  
   171  		parts := strings.SplitN(word, "=", 2)
   172  		node := newKeyValueNode(parts[0], parts[1])
   173  		rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
   174  	}
   175  
   176  	return rootNode, nil
   177  }
   178  
   179  func newKeyValueNode(key, value string) *Node {
   180  	return &Node{
   181  		Value: key,
   182  		Next:  &Node{Value: value},
   183  	}
   184  }
   185  
   186  func appendKeyValueNode(node, rootNode, prevNode *Node) (*Node, *Node) {
   187  	if rootNode == nil {
   188  		rootNode = node
   189  	}
   190  	if prevNode != nil {
   191  		prevNode.Next = node
   192  	}
   193  
   194  	prevNode = node.Next
   195  	return rootNode, prevNode
   196  }
   197  
   198  func parseEnv(rest string, d *Directive) (*Node, map[string]bool, error) {
   199  	node, err := parseNameVal(rest, "ENV", d)
   200  	return node, nil, err
   201  }
   202  
   203  func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) {
   204  	node, err := parseNameVal(rest, commandLabel, d)
   205  	return node, nil, err
   206  }
   207  
   208  // NodeFromLabels returns a Node for the injected labels
   209  func NodeFromLabels(labels map[string]string) *Node {
   210  	keys := []string{}
   211  	for key := range labels {
   212  		keys = append(keys, key)
   213  	}
   214  	// Sort the label to have a repeatable order
   215  	sort.Strings(keys)
   216  
   217  	labelPairs := []string{}
   218  	var rootNode *Node
   219  	var prevNode *Node
   220  	for _, key := range keys {
   221  		value := labels[key]
   222  		labelPairs = append(labelPairs, fmt.Sprintf("%q='%s'", key, value))
   223  		// Value must be single quoted to prevent env variable expansion
   224  		// See https://github.com/docker/docker/issues/26027
   225  		node := newKeyValueNode(key, "'"+value+"'")
   226  		rootNode, prevNode = appendKeyValueNode(node, rootNode, prevNode)
   227  	}
   228  
   229  	return &Node{
   230  		Value:    command.Label,
   231  		Original: commandLabel + " " + strings.Join(labelPairs, " "),
   232  		Next:     rootNode,
   233  	}
   234  }
   235  
   236  // parses a statement containing one or more keyword definition(s) and/or
   237  // value assignments, like `name1 name2= name3="" name4=value`.
   238  // Note that this is a stricter format than the old format of assignment,
   239  // allowed by parseNameVal(), in a way that this only allows assignment of the
   240  // form `keyword=[<value>]` like  `name2=`, `name3=""`, and `name4=value` above.
   241  // In addition, a keyword definition alone is of the form `keyword` like `name1`
   242  // above. And the assignments `name2=` and `name3=""` are equivalent and
   243  // assign an empty value to the respective keywords.
   244  func parseNameOrNameVal(rest string, d *Directive) (*Node, map[string]bool, error) {
   245  	words := parseWords(rest, d)
   246  	if len(words) == 0 {
   247  		return nil, nil, nil
   248  	}
   249  
   250  	var (
   251  		rootnode *Node
   252  		prevNode *Node
   253  	)
   254  	for i, word := range words {
   255  		node := &Node{}
   256  		node.Value = word
   257  		if i == 0 {
   258  			rootnode = node
   259  		} else {
   260  			prevNode.Next = node
   261  		}
   262  		prevNode = node
   263  	}
   264  
   265  	return rootnode, nil, nil
   266  }
   267  
   268  // parses a whitespace-delimited set of arguments. The result is effectively a
   269  // linked list of string arguments.
   270  func parseStringsWhitespaceDelimited(rest string, d *Directive) (*Node, map[string]bool, error) {
   271  	if rest == "" {
   272  		return nil, nil, nil
   273  	}
   274  
   275  	node := &Node{}
   276  	rootnode := node
   277  	prevnode := node
   278  	for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp
   279  		prevnode = node
   280  		node.Value = str
   281  		node.Next = &Node{}
   282  		node = node.Next
   283  	}
   284  
   285  	// XXX to get around regexp.Split *always* providing an empty string at the
   286  	// end due to how our loop is constructed, nil out the last node in the
   287  	// chain.
   288  	prevnode.Next = nil
   289  
   290  	return rootnode, nil, nil
   291  }
   292  
   293  // parseString just wraps the string in quotes and returns a working node.
   294  func parseString(rest string, d *Directive) (*Node, map[string]bool, error) {
   295  	if rest == "" {
   296  		return nil, nil, nil
   297  	}
   298  	n := &Node{}
   299  	n.Value = rest
   300  	return n, nil, nil
   301  }
   302  
   303  // parseJSON converts JSON arrays to an AST.
   304  func parseJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
   305  	rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
   306  	if !strings.HasPrefix(rest, "[") {
   307  		return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest)
   308  	}
   309  
   310  	var myJSON []interface{}
   311  	if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil {
   312  		return nil, nil, err
   313  	}
   314  
   315  	var top, prev *Node
   316  	for _, str := range myJSON {
   317  		s, ok := str.(string)
   318  		if !ok {
   319  			return nil, nil, errDockerfileNotStringArray
   320  		}
   321  
   322  		node := &Node{Value: s}
   323  		if prev == nil {
   324  			top = node
   325  		} else {
   326  			prev.Next = node
   327  		}
   328  		prev = node
   329  	}
   330  
   331  	return top, map[string]bool{"json": true}, nil
   332  }
   333  
   334  // parseMaybeJSON determines if the argument appears to be a JSON array. If
   335  // so, passes to parseJSON; if not, quotes the result and returns a single
   336  // node.
   337  func parseMaybeJSON(rest string, d *Directive) (*Node, map[string]bool, error) {
   338  	if rest == "" {
   339  		return nil, nil, nil
   340  	}
   341  
   342  	node, attrs, err := parseJSON(rest, d)
   343  
   344  	if err == nil {
   345  		return node, attrs, nil
   346  	}
   347  	if err == errDockerfileNotStringArray {
   348  		return nil, nil, err
   349  	}
   350  
   351  	node = &Node{}
   352  	node.Value = rest
   353  	return node, nil, nil
   354  }
   355  
   356  // parseMaybeJSONToList determines if the argument appears to be a JSON array. If
   357  // so, passes to parseJSON; if not, attempts to parse it as a whitespace
   358  // delimited string.
   359  func parseMaybeJSONToList(rest string, d *Directive) (*Node, map[string]bool, error) {
   360  	node, attrs, err := parseJSON(rest, d)
   361  
   362  	if err == nil {
   363  		return node, attrs, nil
   364  	}
   365  	if err == errDockerfileNotStringArray {
   366  		return nil, nil, err
   367  	}
   368  
   369  	return parseStringsWhitespaceDelimited(rest, d)
   370  }
   371  
   372  // The HEALTHCHECK command is like parseMaybeJSON, but has an extra type argument.
   373  func parseHealthConfig(rest string, d *Directive) (*Node, map[string]bool, error) {
   374  	// Find end of first argument
   375  	var sep int
   376  	for ; sep < len(rest); sep++ {
   377  		if unicode.IsSpace(rune(rest[sep])) {
   378  			break
   379  		}
   380  	}
   381  	next := sep
   382  	for ; next < len(rest); next++ {
   383  		if !unicode.IsSpace(rune(rest[next])) {
   384  			break
   385  		}
   386  	}
   387  
   388  	if sep == 0 {
   389  		return nil, nil, nil
   390  	}
   391  
   392  	typ := rest[:sep]
   393  	cmd, attrs, err := parseMaybeJSON(rest[next:], d)
   394  	if err != nil {
   395  		return nil, nil, err
   396  	}
   397  
   398  	return &Node{Value: typ, Next: cmd}, attrs, err
   399  }