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 }