@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 948 lines 28.9 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddyfile
16
17 import (
18 "bytes"
19 "fmt"
20 "io"
21 "os"
22 "path/filepath"
23 "strings"
24
25 "go.uber.org/zap"
26
27 "github.com/caddyserver/caddy/v2"
28 )
29
30 // Parse parses the input just enough to group tokens, in
31 // order, by server block. No further parsing is performed.
32 // Server blocks are returned in the order in which they appear.
33 // Directives that do not appear in validDirectives will cause
34 // an error. If you do not want to check for valid directives,
35 // pass in nil instead.
36 //
37 // Environment variables in {$ENVIRONMENT_VARIABLE} notation
38 // will be replaced before parsing begins.
39 func Parse(filename string, input []byte) ([]ServerBlock, error) {
40 // unfortunately, we must copy the input because parsing must
41 // remain a read-only operation, but we have to expand environment
42 // variables before we parse, which changes the underlying array (#4422)
43 inputCopy := make([]byte, len(input))
44 copy(inputCopy, input)
45
46 tokens, err := allTokens(filename, inputCopy)
47 if err != nil {
48 return nil, err
49 }
50 p := parser{
51 Dispenser: NewDispenser(tokens),
52 importGraph: importGraph{
53 nodes: make(map[string]struct{}),
54 edges: make(adjacency),
55 },
56 }
57 return p.parseAll()
58 }
59
60 // allTokens lexes the entire input, but does not parse it.
61 // It returns all the tokens from the input, unstructured
62 // and in order. It may mutate input as it expands env vars.
63 func allTokens(filename string, input []byte) ([]Token, error) {
64 return Tokenize(replaceEnvVars(input), filename)
65 }
66
67 // replaceEnvVars replaces all occurrences of environment variables.
68 // It mutates the underlying array and returns the updated slice.
69 func replaceEnvVars(input []byte) []byte {
70 var offset int
71 for {
72 begin := bytes.Index(input[offset:], spanOpen)
73 if begin < 0 {
74 break
75 }
76 begin += offset // make beginning relative to input, not offset
77 end := bytes.Index(input[begin+len(spanOpen):], spanClose)
78 if end < 0 {
79 break
80 }
81 end += begin + len(spanOpen) // make end relative to input, not begin
82
83 // get the name; if there is no name, skip it
84 envString := input[begin+len(spanOpen) : end]
85 if len(envString) == 0 {
86 offset = end + len(spanClose)
87 continue
88 }
89
90 // split the string into a key and an optional default
91 envParts := strings.SplitN(string(envString), envVarDefaultDelimiter, 2)
92
93 // do a lookup for the env var, replace with the default if not found
94 envVarValue, found := os.LookupEnv(envParts[0])
95 if !found && len(envParts) == 2 {
96 envVarValue = envParts[1]
97 }
98
99 // get the value of the environment variable
100 // note that this causes one-level deep chaining
101 envVarBytes := []byte(envVarValue)
102
103 // splice in the value
104 input = append(input[:begin],
105 append(envVarBytes, input[end+len(spanClose):]...)...)
106
107 // continue at the end of the replacement
108 offset = begin + len(envVarBytes)
109 }
110 return input
111 }
112
113 // importAncestor tracks active physical files during formatting import discovery.
114 // Nodes are immutable and shared by all tokens from one import expansion.
115 type importAncestor struct {
116 info os.FileInfo
117 parent *importAncestor
118 }
119
120 type parser struct {
121 *Dispenser
122 block ServerBlock // current server block being parsed
123 eof bool // if we encounter a valid EOF in a hard place
124 definedSnippets map[string][]Token
125 nesting int
126 importGraph importGraph
127 // importObserver, when set, runs after an imported file is opened and read but
128 // before its tokens are inserted. Returning true skips token insertion. This
129 // internal hook lets FormatImports observe the parser's actual import execution
130 // and suppress physical-file cycles without duplicating parser semantics.
131 importObserver func(string, os.FileInfo, []byte, *importAncestor) (bool, error)
132 }
133
134 func (p *parser) parseAll() ([]ServerBlock, error) {
135 var blocks []ServerBlock
136
137 for p.Next() {
138 err := p.parseOne()
139 if err != nil {
140 return blocks, err
141 }
142 if len(p.block.Keys) > 0 || len(p.block.Segments) > 0 {
143 blocks = append(blocks, p.block)
144 }
145 if p.nesting > 0 {
146 return blocks, p.EOFErr()
147 }
148 }
149
150 return blocks, nil
151 }
152
153 func (p *parser) parseOne() error {
154 p.block = ServerBlock{}
155 return p.begin()
156 }
157
158 func (p *parser) begin() error {
159 if len(p.tokens) == 0 {
160 return nil
161 }
162
163 err := p.addresses()
164 if err != nil {
165 return err
166 }
167
168 if p.eof {
169 // this happens if the Caddyfile consists of only
170 // a line of addresses and nothing else
171 return nil
172 }
173
174 if ok, name := p.isNamedRoute(); ok {
175 // we just need a dummy leading token to ease parsing later
176 nameToken := p.Token()
177 nameToken.Text = name
178
179 // named routes only have one key, the route name
180 p.block.Keys = []Token{nameToken}
181 p.block.IsNamedRoute = true
182
183 // get all the tokens from the block, including the braces
184 tokens, err := p.blockTokens(true)
185 if err != nil {
186 return err
187 }
188
189 // expand any import directives inside the named route block
190 expandedTokens, err := p.expandImportsInBlock(tokens)
191 if err != nil {
192 return err
193 }
194
195 expandedTokens = append([]Token{nameToken}, expandedTokens...)
196 p.block.Segments = []Segment{expandedTokens}
197 return nil
198 }
199
200 if ok, name := p.isSnippet(); ok {
201 if p.definedSnippets == nil {
202 p.definedSnippets = map[string][]Token{}
203 }
204 if _, found := p.definedSnippets[name]; found {
205 return p.Errf("redeclaration of previously declared snippet %s", name)
206 }
207 // consume all tokens til matched close brace
208 tokens, err := p.blockTokens(false)
209 if err != nil {
210 return err
211 }
212 // Just as we need to track which file the token comes from, we need to
213 // keep track of which snippet the token comes from. This is helpful
214 // in tracking import cycles across files/snippets by namespacing them.
215 // Without this, we end up with false-positives in cycle-detection.
216 for k, v := range tokens {
217 v.snippetName = name
218 tokens[k] = v
219 }
220 p.definedSnippets[name] = tokens
221 // empty block keys so we don't save this block as a real server.
222 p.block.Keys = nil
223 return nil
224 }
225
226 return p.blockContents()
227 }
228
229 func (p *parser) addresses() error {
230 var expectingAnother bool
231
232 for {
233 value := p.Val()
234 token := p.Token()
235
236 // Reject request matchers if trying to define them globally
237 if strings.HasPrefix(value, "@") {
238 return p.Errf("request matchers may not be defined globally, they must be in a site block; found %s", value)
239 }
240
241 // Special case: import directive replaces tokens during parse-time
242 if value == "import" && p.isNewLine() {
243 err := p.doImport(0)
244 if err != nil {
245 return err
246 }
247 continue
248 }
249
250 // Open brace definitely indicates end of addresses
251 if isOpenCurlyBrace(token) {
252 if expectingAnother {
253 return p.Errf("Expected another address but had '%s' - check for extra comma", value)
254 }
255 // Mark this server block as being defined with braces.
256 // This is used to provide a better error message when
257 // the user may have tried to define two server blocks
258 // without having used braces, which are required in
259 // that case.
260 p.block.HasBraces = true
261 break
262 }
263
264 // Users commonly forget to place a space between the address and the '{'
265 if strings.HasSuffix(value, "{") && token.wasQuoted == 0 {
266 return p.Errf("Site addresses cannot end with a curly brace: '%s' - put a space between the token and the brace", value)
267 }
268
269 if value != "" { // empty token possible if user typed ""
270 // Trailing comma indicates another address will follow, which
271 // may possibly be on the next line
272 if value[len(value)-1] == ',' {
273 value = value[:len(value)-1]
274 expectingAnother = true
275 } else {
276 expectingAnother = false // but we may still see another one on this line
277 }
278
279 // If there's a comma here, it's probably because they didn't use a space
280 // between their two domains, e.g. "foo.com,bar.com", which would not be
281 // parsed as two separate site addresses.
282 if strings.Contains(value, ",") {
283 return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value)
284 }
285
286 // After the above, a comma surrounded by spaces would result
287 // in an empty token which we should ignore
288 if value != "" {
289 // Add the token as a site address
290 token.Text = value
291 p.block.Keys = append(p.block.Keys, token)
292 }
293 }
294
295 // Advance token and possibly break out of loop or return error
296 hasNext := p.Next()
297 if expectingAnother && !hasNext {
298 return p.EOFErr()
299 }
300 if !hasNext {
301 p.eof = true
302 break // EOF
303 }
304 if !expectingAnother && p.isNewLine() {
305 break
306 }
307 }
308
309 return nil
310 }
311
312 func (p *parser) blockContents() error {
313 errOpenCurlyBrace := p.openCurlyBrace()
314 if errOpenCurlyBrace != nil {
315 // single-server configs don't need curly braces
316 p.cursor--
317 }
318 braced := errOpenCurlyBrace == nil
319
320 closed, err := p.directives(braced)
321 if err != nil {
322 return err
323 }
324
325 // only look for close curly brace if there was an opening
326 if braced {
327 if !closed {
328 // the tokens ran out before the server block was closed;
329 // the current token may be the closing brace of a nested
330 // directive block, which does not close this block
331 return p.EOFErr()
332 }
333 err = p.closeCurlyBrace()
334 if err != nil {
335 return err
336 }
337 }
338
339 return nil
340 }
341
342 // directives parses through all the lines for directives
343 // and it expects the next token to be the first
344 // directive. It goes until EOF or closing curly brace
345 // which ends the server block. It reports whether it
346 // stopped at the closing curly brace of a braced server
347 // block (braced); a closing curly brace is an error if
348 // the server block was not opened with one.
349 func (p *parser) directives(braced bool) (bool, error) {
350 for p.Next() {
351 // end of server block
352 if isCloseCurlyBrace(p.Token()) {
353 if !braced {
354 return false, p.Err("Unexpected '}' because no matching opening brace")
355 }
356 // p.nesting has already been decremented
357 return true, nil
358 }
359
360 // special case: import directive replaces tokens during parse-time
361 if p.Val() == "import" {
362 err := p.doImport(1)
363 if err != nil {
364 return false, err
365 }
366 p.cursor-- // cursor is advanced when we continue, so roll back one more
367 continue
368 }
369
370 // normal case: parse a directive as a new segment
371 // (a "segment" is a line which starts with a directive
372 // and which ends at the end of the line or at the end of
373 // the block that is opened at the end of the line)
374 if err := p.directive(); err != nil {
375 return false, err
376 }
377 }
378
379 return false, nil
380 }
381
382 // doImport swaps out the import directive and its argument
383 // (a total of 2 tokens) with the tokens in the specified file
384 // or globbing pattern. When the function returns, the cursor
385 // is on the token before where the import directive was. In
386 // other words, call Next() to access the first token that was
387 // imported.
388 func (p *parser) doImport(nesting int) error {
389 // syntax checks
390 if !p.NextArg() {
391 return p.ArgErr()
392 }
393 importPattern := p.Val()
394 if importPattern == "" {
395 return p.Err("Import requires a non-empty filepath")
396 }
397
398 // grab remaining args as placeholder replacements
399 args := p.RemainingArgs()
400
401 // set up a replacer for non-variadic args replacement
402 repl := makeArgsReplacer(args)
403
404 // grab all the tokens (if it exists) from within a block that follows the import
405 var blockTokens []Token
406 for currentNesting := p.Nesting(); p.NextBlock(currentNesting); {
407 blockTokens = append(blockTokens, p.Token())
408 }
409 // initialize with size 1
410 blockMapping := make(map[string][]Token, 1)
411 if len(blockTokens) > 0 {
412 // use such tokens to create a new dispenser, and then use it to parse each block
413 bd := NewDispenser(blockTokens)
414
415 // one iteration processes one sub-block inside the import
416 for bd.Next() {
417 currentMappingKey := bd.Val()
418
419 if isOpenCurlyBrace(bd.Token()) {
420 return p.Err("anonymous blocks are not supported")
421 }
422
423 // load up all arguments (if there even are any)
424 currentMappingTokens := bd.RemainingArgsAsTokens()
425
426 // load up the entire block
427 for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
428 currentMappingTokens = append(currentMappingTokens, bd.Token())
429 }
430
431 blockMapping[currentMappingKey] = currentMappingTokens
432 }
433 }
434
435 // splice out the import directive and its arguments
436 // (2 tokens, plus the length of args)
437 tokensBefore := p.tokens[:p.cursor-1-len(args)-len(blockTokens)]
438 tokensAfter := p.tokens[p.cursor+1:]
439 var importedTokens []Token
440 var nodes []string
441
442 // first check snippets. That is a simple, non-recursive replacement
443 if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
444 importedTokens = p.definedSnippets[importPattern]
445 if len(importedTokens) > 0 {
446 // just grab the first one
447 nodes = append(nodes, fmt.Sprintf("%s:%s", importedTokens[0].File, importedTokens[0].snippetName))
448 }
449 } else {
450 // make path relative to the file of the _token_ being processed rather
451 // than current working directory (issue #867) and then use glob to get
452 // list of matching filenames
453 matches, globPattern, err := resolveImportGlob(p.Dispenser.File(), importPattern)
454 if err != nil {
455 return p.WrapErr(err)
456 }
457 if len(matches) == 0 {
458 if strings.ContainsAny(globPattern, "*?[]") {
459 caddy.Log().Warn("No files matching import glob pattern", zap.String("pattern", importPattern))
460 } else {
461 // filepath.Glob ignores file system errors (it only
462 // returns ErrBadPattern), so a file that exists but
463 // cannot be accessed - e.g. permission denied - looks
464 // the same as a missing file. Stat the path directly
465 // so the real error is reported instead (issue #8161).
466 if _, err := os.Stat(globPattern); err != nil && !os.IsNotExist(err) {
467 return p.Errf("Failed to import file %s: %v", importPattern, err)
468 }
469 return p.Errf("File to import not found: %s", importPattern)
470 }
471 }
472
473 // collect all the imported tokens
474 for _, importFile := range matches {
475 newTokens, err := p.doSingleImport(importFile)
476 if err != nil {
477 return err
478 }
479 importedTokens = append(importedTokens, newTokens...)
480 }
481 nodes = matches
482 }
483
484 nodeName := p.File()
485 if p.Token().snippetName != "" {
486 nodeName += fmt.Sprintf(":%s", p.Token().snippetName)
487 }
488 p.importGraph.addNode(nodeName)
489 p.importGraph.addNodes(nodes)
490 if err := p.importGraph.addEdges(nodeName, nodes); err != nil {
491 p.importGraph.removeNodes(nodes)
492 return err
493 }
494
495 // copy the tokens so we don't overwrite p.definedSnippets
496 tokensCopy := make([]Token, 0, len(importedTokens))
497
498 var (
499 maybeSnippet bool
500 maybeSnippetId bool
501 index int
502 )
503
504 // run the argument replacer on the tokens
505 // golang for range slice return a copy of value
506 // similarly, append also copy value
507 for i, token := range importedTokens {
508 if p.importObserver != nil && p.definedSnippets[importPattern] != nil {
509 // A snippet executes in its caller's ancestry, not the ancestry
510 // of the file expansion where it was originally declared.
511 token.importAncestry = p.Token().importAncestry
512 }
513 // update the token's imports to refer to import directive filename, line number and snippet name if there is one
514 if token.snippetName != "" {
515 token.imports = append(token.imports, fmt.Sprintf("%s:%d (import %s)", p.File(), p.Line(), token.snippetName))
516 } else {
517 token.imports = append(token.imports, fmt.Sprintf("%s:%d (import)", p.File(), p.Line()))
518 }
519
520 // naive way of determine snippets, as snippets definition can only follow name + block
521 // format, won't check for nesting correctness or any other error, that's what parser does.
522 if !maybeSnippet && nesting == 0 {
523 // first of the line
524 if i == 0 || isNextOnNewLine(tokensCopy[len(tokensCopy)-1], token) {
525 index = 0
526 } else {
527 index++
528 }
529
530 if index == 0 && len(token.Text) >= 3 && strings.HasPrefix(token.Text, "(") && strings.HasSuffix(token.Text, ")") {
531 maybeSnippetId = true
532 }
533 }
534
535 switch {
536 case isOpenCurlyBrace(token):
537 nesting++
538 if index == 1 && maybeSnippetId && nesting == 1 {
539 maybeSnippet = true
540 maybeSnippetId = false
541 }
542 case isCloseCurlyBrace(token):
543 nesting--
544 if nesting == 0 && maybeSnippet {
545 maybeSnippet = false
546 }
547 }
548 // if it is {block}, we substitute with all tokens in the block
549 // if it is {blocks.*}, we substitute with the tokens in the mapping for the *
550 var tokensToAdd []Token
551 foundBlockDirective := false
552 switch {
553 case token.Text == "{block}":
554 foundBlockDirective = true
555 tokensToAdd = blockTokens
556 case strings.HasPrefix(token.Text, "{blocks.") && strings.HasSuffix(token.Text, "}"):
557 foundBlockDirective = true
558 // {blocks.foo.bar} will be extracted to key `foo.bar`
559 blockKey := strings.TrimPrefix(strings.TrimSuffix(token.Text, "}"), "{blocks.")
560 val, ok := blockMapping[blockKey]
561 if ok {
562 tokensToAdd = val
563 }
564 }
565
566 if foundBlockDirective {
567 if maybeSnippet {
568 tokensCopy = append(tokensCopy, token)
569 } else {
570 start := len(tokensCopy)
571 tokensCopy = append(tokensCopy, tokensToAdd...)
572 if p.importObserver != nil {
573 for i := range tokensToAdd {
574 tokensCopy[start+i].importAncestry = token.importAncestry
575 }
576 }
577 }
578 continue
579 }
580
581 if maybeSnippet {
582 tokensCopy = append(tokensCopy, token)
583 continue
584 }
585
586 foundVariadic, startIndex, endIndex := parseVariadic(token, len(args))
587 if foundVariadic {
588 for _, arg := range args[startIndex:endIndex] {
589 token.Text = arg
590 tokensCopy = append(tokensCopy, token)
591 }
592 } else {
593 token.Text = repl.ReplaceKnown(token.Text, "")
594 tokensCopy = append(tokensCopy, token)
595 }
596 }
597
598 // splice the imported tokens in the place of the import statement
599 // and rewind cursor so Next() will land on first imported token
600 p.tokens = append(tokensBefore, append(tokensCopy, tokensAfter...)...)
601 p.cursor -= len(args) + len(blockTokens) + 1
602
603 return nil
604 }
605
606 // expandImportsInBlock takes a slice of tokens (typically the contents of a
607 // named route block including its outer curly braces) and expands any import
608 // directives found at the beginning of a line. The expansion is done by
609 // creating a temporary parser that shares the same snippets and import graph,
610 // then looping through the tokens and calling doImport whenever an import
611 // directive is encountered. All other tokens are left untouched.
612 func (p *parser) expandImportsInBlock(tokens []Token) ([]Token, error) {
613 // Create a temporary parser that operates on the provided tokens.
614 // The Dispenser is initialized with the token slice; snippets and the
615 // import graph are shared so that imports and cycle detection work
616 // consistently across the whole Caddyfile.
617 tempParser := &parser{
618 Dispenser: NewDispenser(tokens),
619 definedSnippets: p.definedSnippets,
620 importGraph: p.importGraph,
621 // Carry the observer so callers watching import execution (such as
622 // FormatImports) also see files imported from inside a named route.
623 importObserver: p.importObserver,
624 }
625
626 // Loop through the tokens. We only care about import directives that
627 // appear at the start of a line (same logic as directives()).
628 for tempParser.Next() {
629 if tempParser.Val() == "import" && tempParser.isNewLine() {
630 if err := tempParser.doImport(1); err != nil {
631 return nil, err
632 }
633 // Roll back the cursor so the next iteration sees the first
634 // token of the imported content (or the next token after it).
635 tempParser.cursor--
636 }
637 }
638
639 // The temporary parser's token slice has been modified in place by
640 // doImport, so we return it directly.
641 return tempParser.tokens, nil
642 }
643
644 // doSingleImport lexes the individual file at importFile and returns
645 // its tokens or an error, if any.
646 func (p *parser) doSingleImport(importFile string) ([]Token, error) {
647 file, err := os.Open(importFile)
648 if err != nil {
649 return nil, p.Errf("Could not import %s: %v", importFile, err)
650 }
651 defer file.Close()
652
653 info, err := file.Stat()
654 if err != nil {
655 return nil, p.Errf("Could not import %s: %v", importFile, err)
656 }
657 if info.IsDir() {
658 return nil, p.Errf("Could not import %s: is a directory", importFile)
659 }
660
661 input, err := io.ReadAll(file)
662 if err != nil {
663 return nil, p.Errf("Could not read imported file %s: %v", importFile, err)
664 }
665 // Notify only after a successful read so observers receive bytes and metadata
666 // from the same open descriptor. A skipped cyclic import contributes no
667 // tokens at this occurrence.
668 if p.importObserver != nil {
669 skip, err := p.importObserver(importFile, info, input, p.Token().importAncestry)
670 if err != nil {
671 return nil, p.Errf("Could not import %s: %v", importFile, err)
672 }
673 if skip {
674 return nil, nil
675 }
676 }
677
678 // only warning in case of empty files
679 if len(input) == 0 || len(strings.TrimSpace(string(input))) == 0 {
680 caddy.Log().Warn("Import file is empty", zap.String("file", importFile))
681 return []Token{}, nil
682 }
683
684 importedTokens, err := allTokens(importFile, input)
685 if err != nil {
686 return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
687 }
688
689 // Tack the file path onto these tokens so errors show the imported file's name
690 // (we use full, absolute path to avoid bugs: issue #1892)
691 filename, err := caddy.FastAbs(importFile)
692 if err != nil {
693 return nil, p.Errf("Failed to get absolute path of file: %s: %v", importFile, err)
694 }
695 var ancestry *importAncestor
696 if p.importObserver != nil {
697 ancestry = &importAncestor{info: info, parent: p.Token().importAncestry}
698 }
699 for i := range importedTokens {
700 importedTokens[i].File = filename
701 importedTokens[i].importAncestry = ancestry
702 }
703
704 return importedTokens, nil
705 }
706
707 // directive collects tokens until the directive's scope
708 // closes (either end of line or end of curly brace block).
709 // It expects the currently-loaded token to be a directive
710 // (or } that ends a server block). The collected tokens
711 // are loaded into the current server block for later use
712 // by directive setup functions.
713 func (p *parser) directive() error {
714 // a segment is a list of tokens associated with this directive
715 var segment Segment
716
717 // the directive itself is appended as a relevant token
718 segment = append(segment, p.Token())
719
720 for p.Next() {
721 if isOpenCurlyBrace(p.Token()) {
722 p.nesting++
723 if !p.isNextOnNewLine() {
724 return p.Err("Unexpected next token after '{' on same line")
725 }
726 if p.isNewLine() {
727 return p.Err("Unexpected '{' on a new line; did you mean to place the '{' on the previous line?")
728 }
729 } else if p.Val() == "{}" && p.Token().wasQuoted == 0 {
730 if p.isNextOnNewLine() {
731 return p.Err("Unexpected '{}' at end of line")
732 }
733 } else if p.isNewLine() && p.nesting == 0 {
734 p.cursor-- // read too far
735 break
736 } else if isCloseCurlyBrace(p.Token()) && p.nesting > 0 {
737 p.nesting--
738 } else if isCloseCurlyBrace(p.Token()) && p.nesting == 0 {
739 return p.Err("Unexpected '}' because no matching opening brace")
740 } else if p.Val() == "import" && p.isNewLine() {
741 if err := p.doImport(1); err != nil {
742 return err
743 }
744 p.cursor-- // cursor is advanced when we continue, so roll back one more
745 continue
746 }
747
748 segment = append(segment, p.Token())
749 }
750
751 p.block.Segments = append(p.block.Segments, segment)
752
753 if p.nesting > 0 {
754 return p.EOFErr()
755 }
756
757 return nil
758 }
759
760 // openCurlyBrace expects the current token to be an
761 // opening curly brace. This acts like an assertion
762 // because it returns an error if the token is not
763 // an opening curly brace. It does NOT advance the token.
764 func (p *parser) openCurlyBrace() error {
765 if !isOpenCurlyBrace(p.Token()) {
766 if p.valLooksLikeGlobalOptionsAfterImportedSnippets() {
767 return p.Err("global options block must appear before import directives; move the global options block to the top of the Caddyfile")
768 }
769 return p.SyntaxErr("{")
770 }
771 return nil
772 }
773
774 func (p *parser) valLooksLikeGlobalOptionsAfterImportedSnippets() bool {
775 if p.Val() != "import" || len(p.block.Keys) == 0 {
776 return false
777 }
778
779 for _, key := range p.block.Keys {
780 if !strings.HasPrefix(key.Text, "(") || !strings.HasSuffix(key.Text, ")") {
781 return false
782 }
783 }
784
785 return true
786 }
787
788 // closeCurlyBrace expects the current token to be
789 // a closing curly brace. This acts like an assertion
790 // because it returns an error if the token is not
791 // a closing curly brace. It does NOT advance the token.
792 func (p *parser) closeCurlyBrace() error {
793 if !isCloseCurlyBrace(p.Token()) {
794 return p.SyntaxErr("}")
795 }
796 return nil
797 }
798
799 func (p *parser) isNamedRoute() (bool, string) {
800 keys := p.block.Keys
801 // A named route block is a single key with parens, prefixed with &.
802 if len(keys) == 1 && strings.HasPrefix(keys[0].Text, "&(") && strings.HasSuffix(keys[0].Text, ")") {
803 return true, strings.TrimSuffix(keys[0].Text[2:], ")")
804 }
805 return false, ""
806 }
807
808 func (p *parser) isSnippet() (bool, string) {
809 keys := p.block.Keys
810 // A snippet block is a single key with parens. Nothing else qualifies.
811 if len(keys) == 1 && strings.HasPrefix(keys[0].Text, "(") && strings.HasSuffix(keys[0].Text, ")") {
812 return true, strings.TrimSuffix(keys[0].Text[1:], ")")
813 }
814 return false, ""
815 }
816
817 // read and store everything in a block for later replay.
818 func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
819 // block must have curlies.
820 err := p.openCurlyBrace()
821 if err != nil {
822 return nil, err
823 }
824 nesting := 1 // count our own nesting
825 tokens := []Token{}
826 if retainCurlies {
827 tokens = append(tokens, p.Token())
828 }
829 for p.Next() {
830 if isCloseCurlyBrace(p.Token()) {
831 nesting--
832 if nesting == 0 {
833 if retainCurlies {
834 tokens = append(tokens, p.Token())
835 }
836 break
837 }
838 }
839 if isOpenCurlyBrace(p.Token()) {
840 nesting++
841 }
842 tokens = append(tokens, p.tokens[p.cursor])
843 }
844 // make sure we're matched up
845 if nesting != 0 {
846 return nil, p.SyntaxErr("}")
847 }
848 return tokens, nil
849 }
850
851 // ServerBlock associates any number of keys from the
852 // head of the server block with tokens, which are
853 // grouped by segments.
854 type ServerBlock struct {
855 HasBraces bool
856 Keys []Token
857 Segments []Segment
858 IsNamedRoute bool
859 }
860
861 func (sb ServerBlock) GetKeysText() []string {
862 res := make([]string, 0, len(sb.Keys))
863 for _, k := range sb.Keys {
864 res = append(res, k.Text)
865 }
866 return res
867 }
868
869 // DispenseDirective returns a dispenser that contains
870 // all the tokens in the server block.
871 func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
872 var tokens []Token
873 for _, seg := range sb.Segments {
874 if len(seg) > 0 && seg[0].Text == dir {
875 tokens = append(tokens, seg...)
876 }
877 }
878 return NewDispenser(tokens)
879 }
880
881 // Segment is a list of tokens which begins with a directive
882 // and ends at the end of the directive (either at the end of
883 // the line, or at the end of a block it opens).
884 type Segment []Token
885
886 // Directive returns the directive name for the segment.
887 // The directive name is the text of the first token.
888 func (s Segment) Directive() string {
889 if len(s) > 0 {
890 return s[0].Text
891 }
892 return ""
893 }
894
895 // resolveImportGlob resolves importPattern relative to importerFile into a
896 // list of matching file paths. It handles making the pattern absolute (issue
897 // #867), enforces the single-wildcard limit (issue #2096), runs filepath.Glob,
898 // and skips dot-files for glob matches (issue #5295). It returns the matched
899 // paths, the resolved glob pattern (so callers can distinguish glob vs literal
900 // for warning/error purposes), and any hard error. Empty-match handling (the
901 // Warn vs "not found" decision) is left to the caller.
902 func resolveImportGlob(importerFile, importPattern string) (matches []string, globPattern string, err error) {
903 absFile, err := caddy.FastAbs(importerFile)
904 if err != nil {
905 return nil, "", fmt.Errorf("failed to get absolute path of file: %s: %v", importerFile, err)
906 }
907
908 if !filepath.IsAbs(importPattern) {
909 globPattern = filepath.Join(filepath.Dir(absFile), importPattern)
910 } else {
911 globPattern = importPattern
912 }
913
914 if strings.Count(globPattern, "*") > 1 || strings.Count(globPattern, "?") > 1 ||
915 (strings.Contains(globPattern, "[") && strings.Contains(globPattern, "]")) {
916 // See issue #2096 - a pattern with many glob expansions can hang for too long
917 return nil, globPattern, fmt.Errorf("glob pattern may only contain one wildcard (*), but has others: %s", globPattern)
918 }
919
920 matches, err = filepath.Glob(globPattern)
921 if err != nil {
922 return nil, globPattern, fmt.Errorf("failed to use import pattern %s: %v", importPattern, err)
923 }
924
925 // See issue #5295 - skip files that start with a . when the last path
926 // segment of the glob pattern starts with *.
927 sep := string(filepath.Separator)
928 segGlobPattern := strings.Split(globPattern, sep)
929 if strings.HasPrefix(segGlobPattern[len(segGlobPattern)-1], "*") {
930 var filtered []string
931 for _, m := range matches {
932 seg := strings.Split(m, sep)
933 if !strings.HasPrefix(seg[len(seg)-1], ".") {
934 filtered = append(filtered, m)
935 }
936 }
937 matches = filtered
938 }
939
940 return matches, globPattern, nil
941 }
942
943 // spanOpen and spanClose are used to bound spans that
944 // contain the name of an environment variable.
945 var (
946 spanOpen, spanClose = []byte{'{', '$'}, []byte{'}'}
947 envVarDefaultDelimiter = ":"
948 )