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 {