github.com/tompao/docker@v1.9.1/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 "strings" 14 "unicode" 15 ) 16 17 var ( 18 errDockerfileNotStringArray = errors.New("When using JSON array syntax, arrays must be comprised of strings only.") 19 ) 20 21 // ignore the current argument. This will still leave a command parsed, but 22 // will not incorporate the arguments into the ast. 23 func parseIgnore(rest string) (*Node, map[string]bool, error) { 24 return &Node{}, nil, nil 25 } 26 27 // used for onbuild. Could potentially be used for anything that represents a 28 // statement with sub-statements. 29 // 30 // ONBUILD RUN foo bar -> (onbuild (run foo bar)) 31 // 32 func parseSubCommand(rest string) (*Node, map[string]bool, error) { 33 if rest == "" { 34 return nil, nil, nil 35 } 36 37 _, child, err := parseLine(rest) 38 if err != nil { 39 return nil, nil, err 40 } 41 42 return &Node{Children: []*Node{child}}, nil, nil 43 } 44 45 // helper to parse words (i.e space delimited or quoted strings) in a statement. 46 // The quotes are preserved as part of this function and they are stripped later 47 // as part of processWords(). 48 func parseWords(rest string) []string { 49 const ( 50 inSpaces = iota // looking for start of a word 51 inWord 52 inQuote 53 ) 54 55 words := []string{} 56 phase := inSpaces 57 word := "" 58 quote := '\000' 59 blankOK := false 60 var ch rune 61 62 for pos := 0; pos <= len(rest); pos++ { 63 if pos != len(rest) { 64 ch = rune(rest[pos]) 65 } 66 67 if phase == inSpaces { // Looking for start of word 68 if pos == len(rest) { // end of input 69 break 70 } 71 if unicode.IsSpace(ch) { // skip spaces 72 continue 73 } 74 phase = inWord // found it, fall thru 75 } 76 if (phase == inWord || phase == inQuote) && (pos == len(rest)) { 77 if blankOK || len(word) > 0 { 78 words = append(words, word) 79 } 80 break 81 } 82 if phase == inWord { 83 if unicode.IsSpace(ch) { 84 phase = inSpaces 85 if blankOK || len(word) > 0 { 86 words = append(words, word) 87 } 88 word = "" 89 blankOK = false 90 continue 91 } 92 if ch == '\'' || ch == '"' { 93 quote = ch 94 blankOK = true 95 phase = inQuote 96 } 97 if ch == '\\' { 98 if pos+1 == len(rest) { 99 continue // just skip \ at end 100 } 101 // If we're not quoted and we see a \, then always just 102 // add \ plus the char to the word, even if the char 103 // is a quote. 104 word += string(ch) 105 pos++ 106 ch = rune(rest[pos]) 107 } 108 word += string(ch) 109 continue 110 } 111 if phase == inQuote { 112 if ch == quote { 113 phase = inWord 114 } 115 // \ is special except for ' quotes - can't escape anything for ' 116 if ch == '\\' && quote != '\'' { 117 if pos+1 == len(rest) { 118 phase = inWord 119 continue // just skip \ at end 120 } 121 pos++ 122 nextCh := rune(rest[pos]) 123 word += string(ch) 124 ch = nextCh 125 } 126 word += string(ch) 127 } 128 } 129 130 return words 131 } 132 133 // parse environment like statements. Note that this does *not* handle 134 // variable interpolation, which will be handled in the evaluator. 135 func parseNameVal(rest string, key string) (*Node, map[string]bool, error) { 136 // This is kind of tricky because we need to support the old 137 // variant: KEY name value 138 // as well as the new one: KEY name=value ... 139 // The trigger to know which one is being used will be whether we hit 140 // a space or = first. space ==> old, "=" ==> new 141 142 words := parseWords(rest) 143 if len(words) == 0 { 144 return nil, nil, nil 145 } 146 147 var rootnode *Node 148 149 // Old format (KEY name value) 150 if !strings.Contains(words[0], "=") { 151 node := &Node{} 152 rootnode = node 153 strs := tokenWhitespace.Split(rest, 2) 154 155 if len(strs) < 2 { 156 return nil, nil, fmt.Errorf(key + " must have two arguments") 157 } 158 159 node.Value = strs[0] 160 node.Next = &Node{} 161 node.Next.Value = strs[1] 162 } else { 163 var prevNode *Node 164 for i, word := range words { 165 if !strings.Contains(word, "=") { 166 return nil, nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word) 167 } 168 parts := strings.SplitN(word, "=", 2) 169 170 name := &Node{} 171 value := &Node{} 172 173 name.Next = value 174 name.Value = parts[0] 175 value.Value = parts[1] 176 177 if i == 0 { 178 rootnode = name 179 } else { 180 prevNode.Next = name 181 } 182 prevNode = value 183 } 184 } 185 186 return rootnode, nil, nil 187 } 188 189 func parseEnv(rest string) (*Node, map[string]bool, error) { 190 return parseNameVal(rest, "ENV") 191 } 192 193 func parseLabel(rest string) (*Node, map[string]bool, error) { 194 return parseNameVal(rest, "LABEL") 195 } 196 197 // parses a statement containing one or more keyword definition(s) and/or 198 // value assignments, like `name1 name2= name3="" name4=value`. 199 // Note that this is a stricter format than the old format of assignment, 200 // allowed by parseNameVal(), in a way that this only allows assignment of the 201 // form `keyword=[<value>]` like `name2=`, `name3=""`, and `name4=value` above. 202 // In addition, a keyword definition alone is of the form `keyword` like `name1` 203 // above. And the assignments `name2=` and `name3=""` are equivalent and 204 // assign an empty value to the respective keywords. 205 func parseNameOrNameVal(rest string) (*Node, map[string]bool, error) { 206 words := parseWords(rest) 207 if len(words) == 0 { 208 return nil, nil, nil 209 } 210 211 var ( 212 rootnode *Node 213 prevNode *Node 214 ) 215 for i, word := range words { 216 node := &Node{} 217 node.Value = word 218 if i == 0 { 219 rootnode = node 220 } else { 221 prevNode.Next = node 222 } 223 prevNode = node 224 } 225 226 return rootnode, nil, nil 227 } 228 229 // parses a whitespace-delimited set of arguments. The result is effectively a 230 // linked list of string arguments. 231 func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) { 232 if rest == "" { 233 return nil, nil, nil 234 } 235 236 node := &Node{} 237 rootnode := node 238 prevnode := node 239 for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp 240 prevnode = node 241 node.Value = str 242 node.Next = &Node{} 243 node = node.Next 244 } 245 246 // XXX to get around regexp.Split *always* providing an empty string at the 247 // end due to how our loop is constructed, nil out the last node in the 248 // chain. 249 prevnode.Next = nil 250 251 return rootnode, nil, nil 252 } 253 254 // parsestring just wraps the string in quotes and returns a working node. 255 func parseString(rest string) (*Node, map[string]bool, error) { 256 if rest == "" { 257 return nil, nil, nil 258 } 259 n := &Node{} 260 n.Value = rest 261 return n, nil, nil 262 } 263 264 // parseJSON converts JSON arrays to an AST. 265 func parseJSON(rest string) (*Node, map[string]bool, error) { 266 rest = strings.TrimLeftFunc(rest, unicode.IsSpace) 267 if !strings.HasPrefix(rest, "[") { 268 return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest) 269 } 270 271 var myJSON []interface{} 272 if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil { 273 return nil, nil, err 274 } 275 276 var top, prev *Node 277 for _, str := range myJSON { 278 s, ok := str.(string) 279 if !ok { 280 return nil, nil, errDockerfileNotStringArray 281 } 282 283 node := &Node{Value: s} 284 if prev == nil { 285 top = node 286 } else { 287 prev.Next = node 288 } 289 prev = node 290 } 291 292 return top, map[string]bool{"json": true}, nil 293 } 294 295 // parseMaybeJSON determines if the argument appears to be a JSON array. If 296 // so, passes to parseJSON; if not, quotes the result and returns a single 297 // node. 298 func parseMaybeJSON(rest string) (*Node, map[string]bool, error) { 299 if rest == "" { 300 return nil, nil, nil 301 } 302 303 node, attrs, err := parseJSON(rest) 304 305 if err == nil { 306 return node, attrs, nil 307 } 308 if err == errDockerfileNotStringArray { 309 return nil, nil, err 310 } 311 312 node = &Node{} 313 node.Value = rest 314 return node, nil, nil 315 } 316 317 // parseMaybeJSONToList determines if the argument appears to be a JSON array. If 318 // so, passes to parseJSON; if not, attempts to parse it as a whitespace 319 // delimited string. 320 func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) { 321 node, attrs, err := parseJSON(rest) 322 323 if err == nil { 324 return node, attrs, nil 325 } 326 if err == errDockerfileNotStringArray { 327 return nil, nil, err 328 } 329 330 return parseStringsWhitespaceDelimited(rest) 331 }