Skip to content

Commit 551f793

Browse files
authored
caddyfile: Fix importing nested tokens for {block} (#7189)
1 parent 4564261 commit 551f793

File tree

6 files changed

+214
-23
lines changed

6 files changed

+214
-23
lines changed

caddyconfig/caddyfile/dispenser.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,9 @@ func (d *Dispenser) CountRemainingArgs() int {
308308
}
309309

310310
// RemainingArgs loads any more arguments (tokens on the same line)
311-
// into a slice and returns them. Open curly brace tokens also indicate
312-
// the end of arguments, and the curly brace is not included in
313-
// the return value nor is it loaded.
311+
// into a slice of strings and returns them. Open curly brace tokens
312+
// also indicate the end of arguments, and the curly brace is not
313+
// included in the return value nor is it loaded.
314314
func (d *Dispenser) RemainingArgs() []string {
315315
var args []string
316316
for d.NextArg() {
@@ -320,9 +320,9 @@ func (d *Dispenser) RemainingArgs() []string {
320320
}
321321

322322
// RemainingArgsRaw loads any more arguments (tokens on the same line,
323-
// retaining quotes) into a slice and returns them. Open curly brace
324-
// tokens also indicate the end of arguments, and the curly brace is
325-
// not included in the return value nor is it loaded.
323+
// retaining quotes) into a slice of strings and returns them.
324+
// Open curly brace tokens also indicate the end of arguments,
325+
// and the curly brace is not included in the return value nor is it loaded.
326326
func (d *Dispenser) RemainingArgsRaw() []string {
327327
var args []string
328328
for d.NextArg() {
@@ -331,6 +331,18 @@ func (d *Dispenser) RemainingArgsRaw() []string {
331331
return args
332332
}
333333

334+
// RemainingArgsAsTokens loads any more arguments (tokens on the same line)
335+
// into a slice of Token-structs and returns them. Open curly brace tokens
336+
// also indicate the end of arguments, and the curly brace is not included
337+
// in the return value nor is it loaded.
338+
func (d *Dispenser) RemainingArgsAsTokens() []Token {
339+
var args []Token
340+
for d.NextArg() {
341+
args = append(args, d.Token())
342+
}
343+
return args
344+
}
345+
334346
// NewFromNextSegment returns a new dispenser with a copy of
335347
// the tokens from the current token until the end of the
336348
// "directive" whether that be to the end of the line or

caddyconfig/caddyfile/dispenser_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,66 @@ func TestDispenser_RemainingArgs(t *testing.T) {
274274
}
275275
}
276276

277+
func TestDispenser_RemainingArgsAsTokens(t *testing.T) {
278+
input := `dir1 arg1 arg2 arg3
279+
dir2 arg4 arg5
280+
dir3 arg6 { arg7
281+
dir4`
282+
d := NewTestDispenser(input)
283+
284+
d.Next() // dir1
285+
286+
args := d.RemainingArgsAsTokens()
287+
288+
tokenTexts := make([]string, 0, len(args))
289+
for _, arg := range args {
290+
tokenTexts = append(tokenTexts, arg.Text)
291+
}
292+
293+
if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(tokenTexts, expected) {
294+
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
295+
}
296+
297+
d.Next() // dir2
298+
299+
args = d.RemainingArgsAsTokens()
300+
301+
tokenTexts = tokenTexts[:0]
302+
for _, arg := range args {
303+
tokenTexts = append(tokenTexts, arg.Text)
304+
}
305+
306+
if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(tokenTexts, expected) {
307+
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
308+
}
309+
310+
d.Next() // dir3
311+
312+
args = d.RemainingArgsAsTokens()
313+
tokenTexts = tokenTexts[:0]
314+
for _, arg := range args {
315+
tokenTexts = append(tokenTexts, arg.Text)
316+
}
317+
318+
if expected := []string{"arg6"}; !reflect.DeepEqual(tokenTexts, expected) {
319+
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts)
320+
}
321+
322+
d.Next() // {
323+
d.Next() // arg7
324+
d.Next() // dir4
325+
326+
args = d.RemainingArgsAsTokens()
327+
tokenTexts = tokenTexts[:0]
328+
for _, arg := range args {
329+
tokenTexts = append(tokenTexts, arg.Text)
330+
}
331+
332+
if len(args) != 0 {
333+
t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", []string{}, tokenTexts)
334+
}
335+
}
336+
277337
func TestDispenser_ArgErr_Err(t *testing.T) {
278338
input := `dir1 {
279339
}

caddyconfig/caddyfile/parse.go

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -379,28 +379,23 @@ func (p *parser) doImport(nesting int) error {
379379
if len(blockTokens) > 0 {
380380
// use such tokens to create a new dispenser, and then use it to parse each block
381381
bd := NewDispenser(blockTokens)
382+
383+
// one iteration processes one sub-block inside the import
382384
for bd.Next() {
383-
// see if we can grab a key
384-
var currentMappingKey string
385-
if bd.Val() == "{" {
385+
currentMappingKey := bd.Val()
386+
387+
if currentMappingKey == "{" {
386388
return p.Err("anonymous blocks are not supported")
387389
}
388-
currentMappingKey = bd.Val()
389-
currentMappingTokens := []Token{}
390-
// read all args until end of line / {
391-
if bd.NextArg() {
390+
391+
// load up all arguments (if there even are any)
392+
currentMappingTokens := bd.RemainingArgsAsTokens()
393+
394+
// load up the entire block
395+
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
392396
currentMappingTokens = append(currentMappingTokens, bd.Token())
393-
for bd.NextArg() {
394-
currentMappingTokens = append(currentMappingTokens, bd.Token())
395-
}
396-
// TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly.
397-
// maybe someone can do that in the future
398-
} else {
399-
// attempt to enter a block and add tokens to the currentMappingTokens
400-
for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); {
401-
currentMappingTokens = append(currentMappingTokens, bd.Token())
402-
}
403397
}
398+
404399
blockMapping[currentMappingKey] = currentMappingTokens
405400
}
406401
}

caddyconfig/caddyfile/parse_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"bytes"
1919
"os"
2020
"path/filepath"
21+
"strings"
2122
"testing"
2223
)
2324

@@ -884,6 +885,51 @@ func TestRejectsGlobalMatcher(t *testing.T) {
884885
}
885886
}
886887

888+
func TestRejectAnonymousImportBlock(t *testing.T) {
889+
p := testParser(`
890+
(site) {
891+
http://{args[0]} https://{args[0]} {
892+
{block}
893+
}
894+
}
895+
896+
import site test.domain {
897+
{
898+
header_up Host {host}
899+
header_up X-Real-IP {remote_host}
900+
}
901+
}
902+
`)
903+
_, err := p.parseAll()
904+
if err == nil {
905+
t.Fatal("Expected an error, but got nil")
906+
}
907+
expected := "anonymous blocks are not supported"
908+
if !strings.HasPrefix(err.Error(), "anonymous blocks are not supported") {
909+
t.Errorf("Expected error to start with '%s' but got '%v'", expected, err)
910+
}
911+
}
912+
913+
func TestAcceptSiteImportWithBraces(t *testing.T) {
914+
p := testParser(`
915+
(site) {
916+
http://{args[0]} https://{args[0]} {
917+
{block}
918+
}
919+
}
920+
921+
import site test.domain {
922+
reverse_proxy http://192.168.1.1:8080 {
923+
header_up Host {host}
924+
}
925+
}
926+
`)
927+
_, err := p.parseAll()
928+
if err != nil {
929+
t.Errorf("Expected error to be nil but got '%v'", err)
930+
}
931+
}
932+
887933
func testParser(input string) parser {
888934
return parser{Dispenser: NewTestDispenser(input)}
889935
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(site) {
2+
http://{args[0]} https://{args[0]} {
3+
{block}
4+
}
5+
}
6+
import site test.domain {
7+
{
8+
header_up Host {host}
9+
header_up X-Real-IP {remote_host}
10+
}
11+
}
12+
----------
13+
anonymous blocks are not supported
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
(site) {
2+
https://{args[0]} {
3+
{block}
4+
}
5+
}
6+
7+
import site test.domain {
8+
reverse_proxy http://192.168.1.1:8080 {
9+
header_up Host {host}
10+
}
11+
}
12+
----------
13+
{
14+
"apps": {
15+
"http": {
16+
"servers": {
17+
"srv0": {
18+
"listen": [
19+
":443"
20+
],
21+
"routes": [
22+
{
23+
"match": [
24+
{
25+
"host": [
26+
"test.domain"
27+
]
28+
}
29+
],
30+
"handle": [
31+
{
32+
"handler": "subroute",
33+
"routes": [
34+
{
35+
"handle": [
36+
{
37+
"handler": "reverse_proxy",
38+
"headers": {
39+
"request": {
40+
"set": {
41+
"Host": [
42+
"{http.request.host}"
43+
]
44+
}
45+
}
46+
},
47+
"upstreams": [
48+
{
49+
"dial": "192.168.1.1:8080"
50+
}
51+
]
52+
}
53+
]
54+
}
55+
]
56+
}
57+
],
58+
"terminal": true
59+
}
60+
]
61+
}
62+
}
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)