@marcel91200 / caddy Mirror · read-only / commits / 960150bb
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 13, 05:30 UTC

caddyfile: Implement heredoc support (#5385)

Francis Lavoie committed Feb 25, 2023 at 19:34 UTC 960150bb034dc9a549ee7289b1a4eb4abafeb30a
12 files changed +397 -135
caddyconfig/caddyfile/dispenser.go
+22 -36
@@ -20,7 +20,6 @@ import (
20 "io"
21 "log"
22 "strconv"
23 - "strings"
23 )
24
25 // Dispenser is a type that dispenses tokens, similarly to a lexer,
@@ -101,12 +100,12 @@ func (d *Dispenser) nextOnSameLine() bool {
100 d.cursor++
101 return true
102 }
104 - if d.cursor >= len(d.tokens) {
103 + if d.cursor >= len(d.tokens)-1 {
104 return false
105 }
107 - if d.cursor < len(d.tokens)-1 &&
108 - d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
109 - d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
106 + curr := d.tokens[d.cursor]
107 + next := d.tokens[d.cursor+1]
108 + if curr.File == next.File && curr.Line+curr.NumLineBreaks() == next.Line {
109 d.cursor++
110 return true
111 }
@@ -122,12 +121,12 @@ func (d *Dispenser) NextLine() bool {
121 d.cursor++
122 return true
123 }
125 - if d.cursor >= len(d.tokens) {
124 + if d.cursor >= len(d.tokens)-1 {
125 return false
126 }
128 - if d.cursor < len(d.tokens)-1 &&
129 - (d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
130 - d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
127 + curr := d.tokens[d.cursor]
128 + next := d.tokens[d.cursor+1]
129 + if curr.File != next.File || curr.Line+curr.NumLineBreaks() < next.Line {
130 d.cursor++
131 return true
132 }
@@ -203,14 +202,17 @@ func (d *Dispenser) Val() string {
202 }
203
204 // ValRaw gets the raw text of the current token (including quotes).
205 +// If the token was a heredoc, then the delimiter is not included,
206 +// because that is not relevant to any unmarshaling logic at this time.
207 // If there is no token loaded, it returns empty string.
208 func (d *Dispenser) ValRaw() string {
209 if d.cursor < 0 || d.cursor >= len(d.tokens) {
210 return ""
211 }
212 quote := d.tokens[d.cursor].wasQuoted
212 - if quote > 0 {
213 - return string(quote) + d.tokens[d.cursor].Text + string(quote) // string literal
213 + if quote > 0 && quote != '<' {
214 + // string literal
215 + return string(quote) + d.tokens[d.cursor].Text + string(quote)
216 }
217 return d.tokens[d.cursor].Text
218 }
@@ -438,14 +440,14 @@ func (d *Dispenser) Delete() []Token {
440 return d.tokens
441 }
442
441 -// numLineBreaks counts how many line breaks are in the token
442 -// value given by the token index tknIdx. It returns 0 if the
443 -// token does not exist or there are no line breaks.
444 -func (d *Dispenser) numLineBreaks(tknIdx int) int {
445 - if tknIdx < 0 || tknIdx >= len(d.tokens) {
446 - return 0
443 +// DeleteN is the same as Delete, but can delete many tokens at once.
444 +// If there aren't N tokens available to delete, none are deleted.
445 +func (d *Dispenser) DeleteN(amount int) []Token {
446 + if amount > 0 && d.cursor >= (amount-1) && d.cursor <= len(d.tokens)-1 {
447 + d.tokens = append(d.tokens[:d.cursor-(amount-1)], d.tokens[d.cursor+1:]...)
448 + d.cursor -= amount
449 }
448 - return strings.Count(d.tokens[tknIdx].Text, "\n")
450 + return d.tokens
451 }
452
453 // isNewLine determines whether the current token is on a different
@@ -468,18 +470,10 @@ func (d *Dispenser) isNewLine() bool {
470 return true
471 }
472
471 - // The previous token may contain line breaks if
472 - // it was quoted and spanned multiple lines. e.g:
473 - //
474 - // dir "foo
475 - // bar
476 - // baz"
477 - prevLineBreaks := d.numLineBreaks(d.cursor - 1)
478 -
473 // If the previous token (incl line breaks) ends
474 // on a line earlier than the current token,
475 // then the current token is on a new line
482 - return prev.Line+prevLineBreaks < curr.Line
476 + return prev.Line+prev.NumLineBreaks() < curr.Line
477 }
478
479 // isNextOnNewLine determines whether the current token is on a different
@@ -502,16 +496,8 @@ func (d *Dispenser) isNextOnNewLine() bool {
496 return true
497 }
498
505 - // The current token may contain line breaks if
506 - // it was quoted and spanned multiple lines. e.g:
507 - //
508 - // dir "foo
509 - // bar
510 - // baz"
511 - currLineBreaks := d.numLineBreaks(d.cursor)
512 -
499 // If the current token (incl line breaks) ends
500 // on a line earlier than the next token,
501 // then the next token is on a new line
516 - return curr.Line+currLineBreaks < next.Line
502 + return curr.Line+curr.NumLineBreaks() < next.Line
503 }
caddyconfig/caddyfile/lexer.go
+181 -45
@@ -17,7 +17,10 @@ package caddyfile
17 import (
18 "bufio"
19 "bytes"
20 + "fmt"
21 "io"
22 + "regexp"
23 + "strings"
24 "unicode"
25 )
26
@@ -35,30 +38,39 @@ type (
38
39 // Token represents a single parsable unit.
40 Token struct {
38 - File string
39 - origFile string
40 - Line int
41 - Text string
42 - wasQuoted rune // enclosing quote character, if any
43 - snippetName string
41 + File string
42 + origFile string
43 + Line int
44 + Text string
45 + wasQuoted rune // enclosing quote character, if any
46 + heredocMarker string
47 + snippetName string
48 }
49 )
50
47 -// originalFile gets original filename before import modification.
48 -func (t Token) originalFile() string {
49 - if t.origFile != "" {
50 - return t.origFile
51 +// Tokenize takes bytes as input and lexes it into
52 +// a list of tokens that can be parsed as a Caddyfile.
53 +// Also takes a filename to fill the token's File as
54 +// the source of the tokens, which is important to
55 +// determine relative paths for `import` directives.
56 +func Tokenize(input []byte, filename string) ([]Token, error) {
57 + l := lexer{}
58 + if err := l.load(bytes.NewReader(input)); err != nil {
59 + return nil, err
60 }
52 - return t.File
53 -}
54 -
55 -// updateFile updates the token's source filename for error display
56 -// and remembers the original filename. Used during "import" processing.
57 -func (t *Token) updateFile(file string) {
58 - if t.origFile == "" {
59 - t.origFile = t.File
61 + var tokens []Token
62 + for {
63 + found, err := l.next()
64 + if err != nil {
65 + return nil, err
66 + }
67 + if !found {
68 + break
69 + }
70 + l.token.File = filename
71 + tokens = append(tokens, l.token)
72 }
61 - t.File = file
73 + return tokens, nil
74 }
75
76 // load prepares the lexer to scan an input for tokens.
@@ -92,28 +104,93 @@ func (l *lexer) load(input io.Reader) error {
104 // may be escaped. The rest of the line is skipped
105 // if a "#" character is read in. Returns true if
106 // a token was loaded; false otherwise.
95 -func (l *lexer) next() bool {
107 +func (l *lexer) next() (bool, error) {
108 var val []rune
97 - var comment, quoted, btQuoted, escaped bool
109 + var comment, quoted, btQuoted, inHeredoc, heredocEscaped, escaped bool
110 + var heredocMarker string
111
112 makeToken := func(quoted rune) bool {
113 l.token.Text = string(val)
114 l.token.wasQuoted = quoted
115 + l.token.heredocMarker = heredocMarker
116 return true
117 }
118
119 for {
120 + // Read a character in; if err then if we had
121 + // read some characters, make a token. If we
122 + // reached EOF, then no more tokens to read.
123 + // If no EOF, then we had a problem.
124 ch, _, err := l.reader.ReadRune()
125 if err != nil {
126 if len(val) > 0 {
109 - return makeToken(0)
127 + if inHeredoc {
128 + return false, fmt.Errorf("incomplete heredoc <<%s on line #%d, expected ending marker %s", heredocMarker, l.line+l.skippedLines, heredocMarker)
129 + }
130 +
131 + return makeToken(0), nil
132 }
133 if err == io.EOF {
112 - return false
134 + return false, nil
135 }
114 - panic(err)
136 + return false, err
137 }
138
139 + // detect whether we have the start of a heredoc
140 + if !inHeredoc && !heredocEscaped && len(val) > 1 && string(val[:2]) == "<<" {
141 + if ch == '<' {
142 + return false, fmt.Errorf("too many '<' for heredoc on line #%d; only use two, for example <<END", l.line)
143 + }
144 + if ch == '\r' {
145 + continue
146 + }
147 + // after hitting a newline, we know that the heredoc marker
148 + // is the characters after the two << and the newline.
149 + // we reset the val because the heredoc is syntax we don't
150 + // want to keep.
151 + if ch == '\n' {
152 + heredocMarker = string(val[2:])
153 + if !heredocMarkerRegexp.Match([]byte(heredocMarker)) {
154 + return false, fmt.Errorf("heredoc marker on line #%d must contain only alpha-numeric characters, dashes and underscores; got '%s'", l.line, heredocMarker)
155 + }
156 +
157 + inHeredoc = true
158 + l.skippedLines++
159 + val = nil
160 + continue
161 + }
162 + val = append(val, ch)
163 + continue
164 + }
165 +
166 + // if we're in a heredoc, all characters are read as-is
167 + if inHeredoc {
168 + val = append(val, ch)
169 +
170 + if ch == '\n' {
171 + l.skippedLines++
172 + }
173 +
174 + // check if we're done, i.e. that the last few characters are the marker
175 + if len(val) > len(heredocMarker) && heredocMarker == string(val[len(val)-len(heredocMarker):]) {
176 + // set the final value
177 + val, err = l.finalizeHeredoc(val, heredocMarker)
178 + if err != nil {
179 + return false, err
180 + }
181 +
182 + // set the line counter, and make the token
183 + l.line += l.skippedLines
184 + l.skippedLines = 0
185 + return makeToken('<'), nil
186 + }
187 +
188 + // stay in the heredoc until we find the ending marker
189 + continue
190 + }
191 +
192 + // track whether we found an escape '\' for the next
193 + // iteration to be contextually aware
194 if !escaped && !btQuoted && ch == '\\' {
195 escaped = true
196 continue
@@ -128,26 +205,29 @@ func (l *lexer) next() bool {
205 }
206 escaped = false
207 } else {
131 - if quoted && ch == '"' {
132 - return makeToken('"')
133 - }
134 - if btQuoted && ch == '`' {
135 - return makeToken('`')
208 + if (quoted && ch == '"') || (btQuoted && ch == '`') {
209 + return makeToken(ch), nil
210 }
211 }
212 + // allow quoted text to wrap continue on multiple lines
213 if ch == '\n' {
214 l.line += 1 + l.skippedLines
215 l.skippedLines = 0
216 }
217 + // collect this character as part of the quoted token
218 val = append(val, ch)
219 continue
220 }
221
222 if unicode.IsSpace(ch) {
223 + // ignore CR altogether, we only actually care about LF (\n)
224 if ch == '\r' {
225 continue
226 }
227 + // end of the line
228 if ch == '\n' {
229 + // newlines can be escaped to chain arguments
230 + // onto multiple lines; else, increment the line count
231 if escaped {
232 l.skippedLines++
233 escaped = false
@@ -155,14 +235,18 @@ func (l *lexer) next() bool {
235 l.line += 1 + l.skippedLines
236 l.skippedLines = 0
237 }
238 + // comments (#) are single-line only
239 comment = false
240 }
241 + // any kind of space means we're at the end of this token
242 if len(val) > 0 {
161 - return makeToken(0)
243 + return makeToken(0), nil
244 }
245 continue
246 }
247
248 + // comments must be at the start of a token,
249 + // in other words, preceded by space or newline
250 if ch == '#' && len(val) == 0 {
251 comment = true
252 }
@@ -183,7 +267,12 @@ func (l *lexer) next() bool {
267 }
268
269 if escaped {
186 - val = append(val, '\\')
270 + // allow escaping the first < to skip the heredoc syntax
271 + if ch == '<' {
272 + heredocEscaped = true
273 + } else {
274 + val = append(val, '\\')
275 + }
276 escaped = false
277 }
278
@@ -191,24 +280,71 @@ func (l *lexer) next() bool {
280 }
281 }
282
194 -// Tokenize takes bytes as input and lexes it into
195 -// a list of tokens that can be parsed as a Caddyfile.
196 -// Also takes a filename to fill the token's File as
197 -// the source of the tokens, which is important to
198 -// determine relative paths for `import` directives.
199 -func Tokenize(input []byte, filename string) ([]Token, error) {
200 - l := lexer{}
201 - if err := l.load(bytes.NewReader(input)); err != nil {
202 - return nil, err
283 +// finalizeHeredoc takes the runes read as the heredoc text and the marker,
284 +// and processes the text to strip leading whitespace, returning the final
285 +// value without the leading whitespace.
286 +func (l *lexer) finalizeHeredoc(val []rune, marker string) ([]rune, error) {
287 + // find the last newline of the heredoc, which is where the contents end
288 + lastNewline := strings.LastIndex(string(val), "\n")
289 +
290 + // collapse the content, then split into separate lines
291 + lines := strings.Split(string(val[:lastNewline+1]), "\n")
292 +
293 + // figure out how much whitespace we need to strip from the front of every line
294 + // by getting the string that precedes the marker, on the last line
295 + paddingToStrip := string(val[lastNewline+1 : len(val)-len(marker)])
296 +
297 + // iterate over each line and strip the whitespace from the front
298 + var out string
299 + for lineNum, lineText := range lines[:len(lines)-1] {
300 + // find an exact match for the padding
301 + index := strings.Index(lineText, paddingToStrip)
302 +
303 + // if the padding doesn't match exactly at the start then we can't safely strip
304 + if index != 0 {
305 + return nil, fmt.Errorf("mismatched leading whitespace in heredoc <<%s on line #%d [%s], expected whitespace [%s] to match the closing marker", marker, l.line+lineNum+1, lineText, paddingToStrip)
306 + }
307 +
308 + // strip, then append the line, with the newline, to the output.
309 + // also removes all "\r" because Windows.
310 + out += strings.ReplaceAll(lineText[len(paddingToStrip):]+"\n", "\r", "")
311 }
204 - var tokens []Token
205 - for l.next() {
206 - l.token.File = filename
207 - tokens = append(tokens, l.token)
312 +
313 + // return the final value
314 + return []rune(out), nil
315 +}
316 +
317 +// originalFile gets original filename before import modification.
318 +func (t Token) originalFile() string {
319 + if t.origFile != "" {
320 + return t.origFile
321 }
209 - return tokens, nil
322 + return t.File
323 +}
324 +
325 +// updateFile updates the token's source filename for error display
326 +// and remembers the original filename. Used during "import" processing.
327 +func (t *Token) updateFile(file string) {
328 + if t.origFile == "" {
329 + t.origFile = t.File
330 + }
331 + t.File = file
332 }
333
334 func (t Token) Quoted() bool {
335 return t.wasQuoted > 0
336 }
337 +
338 +// NumLineBreaks counts how many line breaks are in the token text.
339 +func (t Token) NumLineBreaks() int {
340 + lineBreaks := strings.Count(t.Text, "\n")
341 + if t.wasQuoted == '<' {
342 + // heredocs have an extra linebreak because the opening
343 + // delimiter is on its own line and is not included in
344 + // the token Text itself
345 + lineBreaks++
346 + }
347 + return lineBreaks
348 +}
349 +
350 +var heredocMarkerRegexp = regexp.MustCompile("^[A-Za-z0-9_-]+$")
caddyconfig/caddyfile/lexer_test.go
+119 -6
@@ -18,13 +18,13 @@ import (
18 "testing"
19 )
20
21 -type lexerTestCase struct {
22 - input []byte
23 - expected []Token
24 -}
25 -
21 func TestLexer(t *testing.T) {
27 - testCases := []lexerTestCase{
22 + testCases := []struct {
23 + input []byte
24 + expected []Token
25 + expectErr bool
26 + errorMessage string
27 + }{
28 {
29 input: []byte(`host:123`),
30 expected: []Token{
@@ -249,10 +249,123 @@ func TestLexer(t *testing.T) {
249 {Line: 1, Text: `quotes`},
250 },
251 },
252 + {
253 + input: []byte(`heredoc <<EOF
254 +content
255 +EOF same-line-arg
256 + `),
257 + expected: []Token{
258 + {Line: 1, Text: `heredoc`},
259 + {Line: 1, Text: "content\n"},
260 + {Line: 3, Text: `same-line-arg`},
261 + },
262 + },
263 + {
264 + input: []byte(`heredoc <<VERY-LONG-MARKER
265 +content
266 +VERY-LONG-MARKER same-line-arg
267 + `),
268 + expected: []Token{
269 + {Line: 1, Text: `heredoc`},
270 + {Line: 1, Text: "content\n"},
271 + {Line: 3, Text: `same-line-arg`},
272 + },
273 + },
274 + {
275 + input: []byte(`heredoc <<EOF
276 + content
277 + EOF same-line-arg
278 + `),
279 + expected: []Token{
280 + {Line: 1, Text: `heredoc`},
281 + {Line: 1, Text: "content\n"},
282 + {Line: 3, Text: `same-line-arg`},
283 + },
284 + },
285 + {
286 + input: []byte(`prev-line
287 + heredoc <<EOF
288 + multi
289 + line
290 + content
291 + EOF same-line-arg
292 + next-line
293 + `),
294 + expected: []Token{
295 + {Line: 1, Text: `prev-line`},
296 + {Line: 2, Text: `heredoc`},
297 + {Line: 2, Text: "\tmulti\n\tline\n\tcontent\n"},
298 + {Line: 6, Text: `same-line-arg`},
299 + {Line: 7, Text: `next-line`},
300 + },
301 + },
302 + {
303 + input: []byte(`heredoc <EOF
304 + content
305 + EOF same-line-arg
306 + `),
307 + expected: []Token{
308 + {Line: 1, Text: `heredoc`},
309 + {Line: 1, Text: `<EOF`},
310 + {Line: 2, Text: `content`},
311 + {Line: 3, Text: `EOF`},
312 + {Line: 3, Text: `same-line-arg`},
313 + },
314 + },
315 + {
316 + input: []byte(`heredoc <<HERE SAME LINE
317 + content
318 + HERE same-line-arg
319 + `),
320 + expectErr: true,
321 + errorMessage: "heredoc marker on line #1 must contain only alpha-numeric characters, dashes and underscores; got 'HERE SAME LINE'",
322 + },
323 + {
324 + input: []byte(`heredoc <<<EOF
325 + content
326 + EOF same-line-arg
327 + `),
328 + expectErr: true,
329 + errorMessage: "too many '<' for heredoc on line #1; only use two, for example <<END",
330 + },
331 + {
332 + input: []byte(`heredoc <<EOF
333 + content
334 + `),
335 + expectErr: true,
336 + errorMessage: "incomplete heredoc <<EOF on line #3, expected ending marker EOF",
337 + },
338 + {
339 + input: []byte(`heredoc <<EOF
340 + content
341 + EOF
342 + `),
343 + expectErr: true,
344 + errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #2 [\tcontent], expected whitespace [\t\t] to match the closing marker",
345 + },
346 + {
347 + input: []byte(`heredoc <<EOF
348 + content
349 + EOF
350 + `),
351 + expectErr: true,
352 + errorMessage: "mismatched leading whitespace in heredoc <<EOF on line #2 [ content], expected whitespace [\t\t] to match the closing marker",
353 + },
354 }
355
356 for i, testCase := range testCases {
357 actual, err := Tokenize(testCase.input, "")
358 + if testCase.expectErr {
359 + if err == nil {
360 + t.Errorf("expected error, got actual: %v", actual)
361 + continue
362 + }
363 + if err.Error() != testCase.errorMessage {
364 + t.Errorf("expected error '%v', got: %v", testCase.errorMessage, err)
365 + }
366 + continue
367 + }
368 +
369 if err != nil {
370 t.Errorf("%v", err)
371 }
caddyconfig/caddyfile/testdata/import_args0.txt
+1 -1
@@ -1 +1 @@
1 -{args.0}
\ No newline at end of file
1 +{args[0]}
\ No newline at end of file
caddyconfig/caddyfile/testdata/import_args1.txt
+1 -1
@@ -1 +1 @@
1 -{args.0} {args.1}
\ No newline at end of file
1 +{args[0]} {args[1]}
\ No newline at end of file
caddytest/integration/caddyfile_adapt/heredoc.txt new
+50
@@ -0,0 +1,50 @@
1 +example.com {
2 + respond <<EOF
3 + <html>
4 + <head><title>Foo</title>
5 + <body>Foo</body>
6 + </html>
7 + EOF 200
8 +}
9 +----------
10 +{
11 + "apps": {
12 + "http": {
13 + "servers": {
14 + "srv0": {
15 + "listen": [
16 + ":443"
17 + ],
18 + "routes": [
19 + {
20 + "match": [
21 + {
22 + "host": [
23 + "example.com"
24 + ]
25 + }
26 + ],
27 + "handle": [
28 + {
29 + "handler": "subroute",
30 + "routes": [
31 + {
32 + "handle": [
33 + {
34 + "body": "\u003chtml\u003e\n \u003chead\u003e\u003ctitle\u003eFoo\u003c/title\u003e\n \u003cbody\u003eFoo\u003c/body\u003e\n\u003c/html\u003e\n",
35 + "handler": "static_response",
36 + "status_code": 200
37 + }
38 + ]
39 + }
40 + ]
41 + }
42 + ],
43 + "terminal": true
44 + }
45 + ]
46 + }
47 + }
48 + }
49 + }
50 +}
\ No newline at end of file
caddytest/integration/caddyfile_adapt/import_args_snippet.txt
+1 -1
@@ -1,6 +1,6 @@
1 (logging) {
2 log {
3 - output file /var/log/caddy/{args.0}.access.log
3 + output file /var/log/caddy/{args[0]}.access.log
4 }
5 }
6
caddytest/integration/caddyfile_adapt/reverse_proxy_options.txt
+2 -2
@@ -6,7 +6,7 @@ https://example.com {
6 method GET
7 rewrite /rewritten?uri={uri}
8
9 - buffer_requests
9 + request_buffers 4KB
10
11 transport http {
12 read_buffer 10MB
@@ -54,7 +54,6 @@ https://example.com {
54 {
55 "handle": [
56 {
57 - "buffer_requests": true,
57 "handler": "reverse_proxy",
58 "headers": {
59 "request": {
@@ -68,6 +67,7 @@ https://example.com {
67 }
68 }
69 },
70 + "request_buffers": 4000,
71 "rewrite": {
72 "method": "GET",
73 "uri": "/rewritten?uri={http.request.uri}"
caddytest/integration/testdata/import_respond.txt
+1 -1
@@ -1 +1 @@
1 -respond "'I am {args.0}', hears {args.1}"
\ No newline at end of file
1 +respond "'I am {args[0]}', hears {args[1]}"
\ No newline at end of file
modules/caddyhttp/celmatcher.go
+4 -2
@@ -191,15 +191,17 @@ func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
191 celReq, ok := lhs.(celHTTPRequest)
192 if !ok {
193 return types.NewErr(
194 - "invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
194 + "invalid request of type '%v' to %s(request, placeholderVarName)",
195 lhs.Type(),
196 + placeholderFuncName,
197 )
198 }
199 phStr, ok := rhs.(types.String)
200 if !ok {
201 return types.NewErr(
201 - "invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
202 + "invalid placeholder variable name of type '%v' to %s(request, placeholderVarName)",
203 rhs.Type(),
204 + placeholderFuncName,
205 )
206 }
207
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+10 -32
@@ -217,25 +217,18 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
217 return nil, dispenser.ArgErr()
218 }
219 fcgiTransport.Root = dispenser.Val()
220 - dispenser.Delete()
221 - dispenser.Delete()
220 + dispenser.DeleteN(2)
221
222 case "split":
223 extensions = dispenser.RemainingArgs()
225 - dispenser.Delete()
226 - for range extensions {
227 - dispenser.Delete()
228 - }
224 + dispenser.DeleteN(len(extensions) + 1)
225 if len(extensions) == 0 {
226 return nil, dispenser.ArgErr()
227 }
228
229 case "env":
230 args := dispenser.RemainingArgs()
235 - dispenser.Delete()
236 - for range args {
237 - dispenser.Delete()
238 - }
231 + dispenser.DeleteN(len(args) + 1)
232 if len(args) != 2 {
233 return nil, dispenser.ArgErr()
234 }
@@ -246,10 +239,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
239
240 case "index":
241 args := dispenser.RemainingArgs()
249 - dispenser.Delete()
250 - for range args {
251 - dispenser.Delete()
252 - }
242 + dispenser.DeleteN(len(args) + 1)
243 if len(args) != 1 {
244 return nil, dispenser.ArgErr()
245 }
@@ -257,10 +247,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
247
248 case "try_files":
249 args := dispenser.RemainingArgs()
260 - dispenser.Delete()
261 - for range args {
262 - dispenser.Delete()
263 - }
250 + dispenser.DeleteN(len(args) + 1)
251 if len(args) < 1 {
252 return nil, dispenser.ArgErr()
253 }
@@ -268,10 +255,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
255
256 case "resolve_root_symlink":
257 args := dispenser.RemainingArgs()
271 - dispenser.Delete()
272 - for range args {
273 - dispenser.Delete()
274 - }
258 + dispenser.DeleteN(len(args) + 1)
259 fcgiTransport.ResolveRootSymlink = true
260
261 case "dial_timeout":
@@ -283,8 +267,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
267 return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err)
268 }
269 fcgiTransport.DialTimeout = caddy.Duration(dur)
286 - dispenser.Delete()
287 - dispenser.Delete()
270 + dispenser.DeleteN(2)
271
272 case "read_timeout":
273 if !dispenser.NextArg() {
@@ -295,8 +278,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
278 return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err)
279 }
280 fcgiTransport.ReadTimeout = caddy.Duration(dur)
298 - dispenser.Delete()
299 - dispenser.Delete()
281 + dispenser.DeleteN(2)
282
283 case "write_timeout":
284 if !dispenser.NextArg() {
@@ -307,15 +289,11 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
289 return nil, dispenser.Errf("bad timeout value %s: %v", dispenser.Val(), err)
290 }
291 fcgiTransport.WriteTimeout = caddy.Duration(dur)
310 - dispenser.Delete()
311 - dispenser.Delete()
292 + dispenser.DeleteN(2)
293
294 case "capture_stderr":
295 args := dispenser.RemainingArgs()
315 - dispenser.Delete()
316 - for range args {
317 - dispenser.Delete()
318 - }
296 + dispenser.DeleteN(len(args) + 1)
297 fcgiTransport.CaptureStderr = true
298 }
299 }
modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
+5 -8
@@ -129,8 +129,7 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
129 return nil, dispenser.ArgErr()
130 }
131 rpHandler.Rewrite.URI = dispenser.Val()
132 - dispenser.Delete()
133 - dispenser.Delete()
132 + dispenser.DeleteN(2)
133
134 case "copy_headers":
135 args := dispenser.RemainingArgs()
@@ -140,13 +139,11 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
139 args = append(args, dispenser.Val())
140 }
141
143 - dispenser.Delete() // directive name
142 + // directive name + args
143 + dispenser.DeleteN(len(args) + 1)
144 if hadBlock {
145 - dispenser.Delete() // opening brace
146 - dispenser.Delete() // closing brace
147 - }
148 - for range args {
149 - dispenser.Delete()
145 + // opening & closing brace
146 + dispenser.DeleteN(2)
147 }
148
149 for _, headerField := range args {