@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 538 lines 15.6 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 "errors"
19 "fmt"
20 "io"
21 "log"
22 "strconv"
23 "strings"
24 )
25
26 // Dispenser is a type that dispenses tokens, similarly to a lexer,
27 // except that it can do so with some notion of structure. An empty
28 // Dispenser is invalid; call NewDispenser to make a proper instance.
29 type Dispenser struct {
30 tokens []Token
31 cursor int
32 nesting int
33
34 // A map of arbitrary context data that can be used
35 // to pass through some information to unmarshalers.
36 context map[string]any
37 }
38
39 // NewDispenser returns a Dispenser filled with the given tokens.
40 func NewDispenser(tokens []Token) *Dispenser {
41 return &Dispenser{
42 tokens: tokens,
43 cursor: -1,
44 }
45 }
46
47 // NewTestDispenser parses input into tokens and creates a new
48 // Dispenser for test purposes only; any errors are fatal.
49 func NewTestDispenser(input string) *Dispenser {
50 tokens, err := allTokens("Testfile", []byte(input))
51 if err != nil && err != io.EOF {
52 log.Fatalf("getting all tokens from input: %v", err)
53 }
54 return NewDispenser(tokens)
55 }
56
57 // Next loads the next token. Returns true if a token
58 // was loaded; false otherwise. If false, all tokens
59 // have been consumed.
60 func (d *Dispenser) Next() bool {
61 if d.cursor < len(d.tokens)-1 {
62 d.cursor++
63 return true
64 }
65 return false
66 }
67
68 // Prev moves to the previous token. It does the inverse
69 // of Next(), except this function may decrement the cursor
70 // to -1 so that the next call to Next() points to the
71 // first token; this allows dispensing to "start over". This
72 // method returns true if the cursor ends up pointing to a
73 // valid token.
74 func (d *Dispenser) Prev() bool {
75 if d.cursor > -1 {
76 d.cursor--
77 return d.cursor > -1
78 }
79 return false
80 }
81
82 // NextArg loads the next token if it is on the same
83 // line and if it is not a block opening (unquoted
84 // open curly brace; a quoted brace is a regular
85 // argument). Returns true if an argument token was
86 // loaded; false otherwise. If false, all tokens on
87 // the line have been consumed except for potentially
88 // a block opening. It handles imported tokens
89 // correctly.
90 func (d *Dispenser) NextArg() bool {
91 if !d.nextOnSameLine() {
92 return false
93 }
94 if isOpenCurlyBrace(d.Token()) {
95 // roll back; a block opening is not an argument
96 d.cursor--
97 return false
98 }
99 return true
100 }
101
102 // nextOnSameLine advances the cursor if the next
103 // token is on the same line of the same file.
104 func (d *Dispenser) nextOnSameLine() bool {
105 if d.cursor < 0 {
106 d.cursor++
107 return true
108 }
109 if d.cursor >= len(d.tokens)-1 {
110 return false
111 }
112 curr := d.tokens[d.cursor]
113 next := d.tokens[d.cursor+1]
114 if !isNextOnNewLine(curr, next) {
115 d.cursor++
116 return true
117 }
118 return false
119 }
120
121 // NextLine loads the next token only if it is not on the same
122 // line as the current token, and returns true if a token was
123 // loaded; false otherwise. If false, there is not another token
124 // or it is on the same line. It handles imported tokens correctly.
125 func (d *Dispenser) NextLine() bool {
126 if d.cursor < 0 {
127 d.cursor++
128 return true
129 }
130 if d.cursor >= len(d.tokens)-1 {
131 return false
132 }
133 curr := d.tokens[d.cursor]
134 next := d.tokens[d.cursor+1]
135 if isNextOnNewLine(curr, next) {
136 d.cursor++
137 return true
138 }
139 return false
140 }
141
142 // NextBlock can be used as the condition of a for loop
143 // to load the next token as long as it opens a block or
144 // is already in a block nested more than initialNestingLevel.
145 // In other words, a loop over NextBlock() will iterate
146 // all tokens in the block assuming the next token is an
147 // open curly brace, until the matching closing brace.
148 // The open and closing brace tokens for the outer-most
149 // block will be consumed internally and omitted from
150 // the iteration.
151 //
152 // Proper use of this method looks like this:
153 //
154 // for nesting := d.Nesting(); d.NextBlock(nesting); {
155 // }
156 //
157 // However, in simple cases where it is known that the
158 // Dispenser is new and has not already traversed state
159 // by a loop over NextBlock(), this will do:
160 //
161 // for d.NextBlock(0) {
162 // }
163 //
164 // As with other token parsing logic, a loop over
165 // NextBlock() should be contained within a loop over
166 // Next(), as it is usually prudent to skip the initial
167 // token.
168 func (d *Dispenser) NextBlock(initialNestingLevel int) bool {
169 if d.nesting > initialNestingLevel {
170 if !d.Next() {
171 return false // should be EOF error
172 }
173 if isCloseCurlyBrace(d.Token()) && !d.nextOnSameLine() {
174 d.nesting--
175 } else if isOpenCurlyBrace(d.Token()) && !d.nextOnSameLine() {
176 d.nesting++
177 }
178 return d.nesting > initialNestingLevel
179 }
180 if !d.nextOnSameLine() { // block must open on same line
181 return false
182 }
183 if !isOpenCurlyBrace(d.Token()) {
184 d.cursor-- // roll back if not opening brace
185 return false
186 }
187 d.Next() // consume open curly brace
188 if isCloseCurlyBrace(d.Token()) {
189 return false // open and then closed right away
190 }
191 d.nesting++
192 return true
193 }
194
195 // Nesting returns the current nesting level. Necessary
196 // if using NextBlock()
197 func (d *Dispenser) Nesting() int {
198 return d.nesting
199 }
200
201 // Val gets the text of the current token. If there is no token
202 // loaded, it returns empty string.
203 func (d *Dispenser) Val() string {
204 if d.cursor < 0 || d.cursor >= len(d.tokens) {
205 return ""
206 }
207 return d.tokens[d.cursor].Text
208 }
209
210 // ValRaw gets the raw text of the current token (including quotes).
211 // If the token was a heredoc, then the delimiter is not included,
212 // because that is not relevant to any unmarshaling logic at this time.
213 // If there is no token loaded, it returns empty string.
214 func (d *Dispenser) ValRaw() string {
215 if d.cursor < 0 || d.cursor >= len(d.tokens) {
216 return ""
217 }
218 quote := d.tokens[d.cursor].wasQuoted
219 if quote > 0 && quote != '<' {
220 // string literal
221 return string(quote) + d.tokens[d.cursor].Text + string(quote)
222 }
223 return d.tokens[d.cursor].Text
224 }
225
226 // ScalarVal gets value of the current token, converted to the closest
227 // scalar type. If there is no token loaded, it returns nil.
228 func (d *Dispenser) ScalarVal() any {
229 if d.cursor < 0 || d.cursor >= len(d.tokens) {
230 return nil
231 }
232 quote := d.tokens[d.cursor].wasQuoted
233 text := d.tokens[d.cursor].Text
234
235 if quote > 0 {
236 return text // string literal
237 }
238 if num, err := strconv.Atoi(text); err == nil {
239 return num
240 }
241 if num, err := strconv.ParseFloat(text, 64); err == nil {
242 return num
243 }
244 if bool, err := strconv.ParseBool(text); err == nil {
245 return bool
246 }
247 return text
248 }
249
250 // Line gets the line number of the current token.
251 // If there is no token loaded, it returns 0.
252 func (d *Dispenser) Line() int {
253 if d.cursor < 0 || d.cursor >= len(d.tokens) {
254 return 0
255 }
256 return d.tokens[d.cursor].Line
257 }
258
259 // File gets the filename where the current token originated.
260 func (d *Dispenser) File() string {
261 if d.cursor < 0 || d.cursor >= len(d.tokens) {
262 return ""
263 }
264 return d.tokens[d.cursor].File
265 }
266
267 // Args is a convenience function that loads the next arguments
268 // (tokens on the same line) into an arbitrary number of strings
269 // pointed to in targets. If there are not enough argument tokens
270 // available to fill targets, false is returned and the remaining
271 // targets are left unchanged. If all the targets are filled,
272 // then true is returned.
273 func (d *Dispenser) Args(targets ...*string) bool {
274 for i := range targets {
275 if !d.NextArg() {
276 return false
277 }
278 *targets[i] = d.Val()
279 }
280 return true
281 }
282
283 // AllArgs is like Args, but if there are more argument tokens
284 // available than there are targets, false is returned. The
285 // number of available argument tokens must match the number of
286 // targets exactly to return true.
287 func (d *Dispenser) AllArgs(targets ...*string) bool {
288 if !d.Args(targets...) {
289 return false
290 }
291 if d.NextArg() {
292 d.Prev()
293 return false
294 }
295 return true
296 }
297
298 // CountRemainingArgs counts the amount of remaining arguments
299 // (tokens on the same line) without consuming the tokens.
300 func (d *Dispenser) CountRemainingArgs() int {
301 count := 0
302 for d.NextArg() {
303 count++
304 }
305 for i := 0; i < count; i++ {
306 d.Prev()
307 }
308 return count
309 }
310
311 // RemainingArgs loads any more arguments (tokens on the same line)
312 // into a slice of strings and returns them. An unquoted open curly
313 // brace also indicates the end of arguments, and it is not included
314 // in the return value nor is it loaded; quoted braces are returned
315 // as regular arguments.
316 func (d *Dispenser) RemainingArgs() []string {
317 var args []string
318 for d.NextArg() {
319 args = append(args, d.Val())
320 }
321 return args
322 }
323
324 // RemainingArgsRaw loads any more arguments (tokens on the same line,
325 // retaining quotes) into a slice of strings and returns them.
326 // An unquoted open curly brace also indicates the end of arguments,
327 // and it is not included in the return value nor is it loaded;
328 // quoted braces are returned as regular arguments.
329 func (d *Dispenser) RemainingArgsRaw() []string {
330 var args []string
331 for d.NextArg() {
332 args = append(args, d.ValRaw())
333 }
334 return args
335 }
336
337 // RemainingArgsAsTokens loads any more arguments (tokens on the same line)
338 // into a slice of Token-structs and returns them. An unquoted open curly
339 // brace also indicates the end of arguments, and it is not included in the
340 // return value nor is it loaded; quoted braces are returned as regular
341 // arguments.
342 func (d *Dispenser) RemainingArgsAsTokens() []Token {
343 var args []Token
344 for d.NextArg() {
345 args = append(args, d.Token())
346 }
347 return args
348 }
349
350 // NewFromNextSegment returns a new dispenser with a copy of
351 // the tokens from the current token until the end of the
352 // "directive" whether that be to the end of the line or
353 // the end of a block that starts at the end of the line;
354 // in other words, until the end of the segment.
355 func (d *Dispenser) NewFromNextSegment() *Dispenser {
356 return NewDispenser(d.NextSegment())
357 }
358
359 // NextSegment returns a copy of the tokens from the current
360 // token until the end of the line or block that starts at
361 // the end of the line.
362 func (d *Dispenser) NextSegment() Segment {
363 tkns := Segment{d.Token()}
364 for d.NextArg() {
365 tkns = append(tkns, d.Token())
366 }
367 var openedBlock bool
368 for nesting := d.Nesting(); d.NextBlock(nesting); {
369 if !openedBlock {
370 // because NextBlock() consumes the initial open
371 // curly brace, we rewind here to append it, since
372 // our case is special in that we want the new
373 // dispenser to have all the tokens including
374 // surrounding curly braces
375 d.Prev()
376 tkns = append(tkns, d.Token())
377 d.Next()
378 openedBlock = true
379 }
380 tkns = append(tkns, d.Token())
381 }
382 if openedBlock {
383 // include closing brace
384 tkns = append(tkns, d.Token())
385
386 // do not consume the closing curly brace; the
387 // next iteration of the enclosing loop will
388 // call Next() and consume it
389 }
390 return tkns
391 }
392
393 // Token returns the current token.
394 func (d *Dispenser) Token() Token {
395 if d.cursor < 0 || d.cursor >= len(d.tokens) {
396 return Token{}
397 }
398 return d.tokens[d.cursor]
399 }
400
401 // Reset sets d's cursor to the beginning, as
402 // if this was a new and unused dispenser.
403 func (d *Dispenser) Reset() {
404 d.cursor = -1
405 d.nesting = 0
406 }
407
408 // ArgErr returns an argument error, meaning that the wrong
409 // number of arguments was given: either a required one was
410 // missing, or an extra one was found. If an argument is
411 // present but its value is invalid, use Errf or SyntaxErr
412 // instead.
413 func (d *Dispenser) ArgErr() error {
414 if isOpenCurlyBrace(d.Token()) {
415 return d.Err("unexpected token '{', expecting argument")
416 }
417 return d.Errf("wrong argument count or unexpected line ending after '%s'", d.Val())
418 }
419
420 // SyntaxErr creates a generic syntax error which explains what was
421 // found and what was expected.
422 func (d *Dispenser) SyntaxErr(expected string) error {
423 msg := fmt.Sprintf("syntax error: unexpected token '%s', expecting '%s', at %s:%d import chain: ['%s']", d.Val(), expected, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
424 return errors.New(msg)
425 }
426
427 // EOFErr returns an error indicating that the dispenser reached
428 // the end of the input when searching for the next token.
429 func (d *Dispenser) EOFErr() error {
430 return d.Errf("unexpected EOF")
431 }
432
433 // Err generates a custom parse-time error with a message of msg.
434 func (d *Dispenser) Err(msg string) error {
435 return d.WrapErr(errors.New(msg))
436 }
437
438 // Errf is like Err, but for formatted error messages
439 func (d *Dispenser) Errf(format string, args ...any) error {
440 return d.WrapErr(fmt.Errorf(format, args...))
441 }
442
443 // WrapErr takes an existing error and adds the Caddyfile file and line number.
444 func (d *Dispenser) WrapErr(err error) error {
445 if len(d.Token().imports) > 0 {
446 return fmt.Errorf("%w, at %s:%d import chain ['%s']", err, d.File(), d.Line(), strings.Join(d.Token().imports, "','"))
447 }
448 return fmt.Errorf("%w, at %s:%d", err, d.File(), d.Line())
449 }
450
451 // Delete deletes the current token and returns the updated slice
452 // of tokens. The cursor is not advanced to the next token.
453 // Because deletion modifies the underlying slice, this method
454 // should only be called if you have access to the original slice
455 // of tokens and/or are using the slice of tokens outside this
456 // Dispenser instance. If you do not re-assign the slice with the
457 // return value of this method, inconsistencies in the token
458 // array will become apparent (or worse, hide from you like they
459 // did me for 3 and a half freaking hours late one night).
460 func (d *Dispenser) Delete() []Token {
461 if d.cursor >= 0 && d.cursor <= len(d.tokens)-1 {
462 d.tokens = append(d.tokens[:d.cursor], d.tokens[d.cursor+1:]...)
463 d.cursor--
464 }
465 return d.tokens
466 }
467
468 // DeleteN is the same as Delete, but can delete many tokens at once.
469 // If there aren't N tokens available to delete, none are deleted.
470 func (d *Dispenser) DeleteN(amount int) []Token {
471 if amount > 0 && d.cursor >= (amount-1) && d.cursor <= len(d.tokens)-1 {
472 d.tokens = append(d.tokens[:d.cursor-(amount-1)], d.tokens[d.cursor+1:]...)
473 d.cursor -= amount
474 }
475 return d.tokens
476 }
477
478 // SetContext sets a key-value pair in the context map.
479 func (d *Dispenser) SetContext(key string, value any) {
480 if d.context == nil {
481 d.context = make(map[string]any)
482 }
483 d.context[key] = value
484 }
485
486 // GetContext gets the value of a key in the context map.
487 func (d *Dispenser) GetContext(key string) any {
488 if d.context == nil {
489 return nil
490 }
491 return d.context[key]
492 }
493
494 // GetContextString gets the value of a key in the context map
495 // as a string, or an empty string if the key does not exist.
496 func (d *Dispenser) GetContextString(key string) string {
497 if d.context == nil {
498 return ""
499 }
500 if val, ok := d.context[key].(string); ok {
501 return val
502 }
503 return ""
504 }
505
506 // isNewLine determines whether the current token is on a different
507 // line (higher line number) than the previous token. It handles imported
508 // tokens correctly. If there isn't a previous token, it returns true.
509 func (d *Dispenser) isNewLine() bool {
510 if d.cursor < 1 {
511 return true
512 }
513 if d.cursor > len(d.tokens)-1 {
514 return false
515 }
516
517 prev := d.tokens[d.cursor-1]
518 curr := d.tokens[d.cursor]
519 return isNextOnNewLine(prev, curr)
520 }
521
522 // isNextOnNewLine determines whether the current token is on a different
523 // line (higher line number) than the next token. It handles imported
524 // tokens correctly. If there isn't a next token, it returns true.
525 func (d *Dispenser) isNextOnNewLine() bool {
526 if d.cursor < 0 {
527 return false
528 }
529 if d.cursor >= len(d.tokens)-1 {
530 return true
531 }
532
533 curr := d.tokens[d.cursor]
534 next := d.tokens[d.cursor+1]
535 return isNextOnNewLine(curr, next)
536 }
537
538 const MatcherNameCtxKey = "matcher_name"