diff --git a/caddyconfig/caddyfile/dispenser.go b/caddyconfig/caddyfile/dispenser.go index 325bb54d3..d95196e48 100644 --- a/caddyconfig/caddyfile/dispenser.go +++ b/caddyconfig/caddyfile/dispenser.go @@ -308,9 +308,9 @@ func (d *Dispenser) CountRemainingArgs() int { } // RemainingArgs loads any more arguments (tokens on the same line) -// into a slice and returns them. Open curly brace tokens also indicate -// the end of arguments, and the curly brace is not included in -// the return value nor is it loaded. +// into a slice of strings and returns them. Open curly brace tokens +// also indicate the end of arguments, and the curly brace is not +// included in the return value nor is it loaded. func (d *Dispenser) RemainingArgs() []string { var args []string for d.NextArg() { @@ -320,9 +320,9 @@ func (d *Dispenser) RemainingArgs() []string { } // RemainingArgsRaw loads any more arguments (tokens on the same line, -// retaining quotes) into a slice and returns them. Open curly brace -// tokens also indicate the end of arguments, and the curly brace is -// not included in the return value nor is it loaded. +// retaining quotes) into a slice of strings and returns them. +// Open curly brace tokens also indicate the end of arguments, +// and the curly brace is not included in the return value nor is it loaded. func (d *Dispenser) RemainingArgsRaw() []string { var args []string for d.NextArg() { @@ -331,6 +331,18 @@ func (d *Dispenser) RemainingArgsRaw() []string { return args } +// RemainingArgsAsTokens loads any more arguments (tokens on the same line) +// into a slice of Token-structs and returns them. Open curly brace tokens +// also indicate the end of arguments, and the curly brace is not included +// in the return value nor is it loaded. +func (d *Dispenser) RemainingArgsAsTokens() []Token { + var args []Token + for d.NextArg() { + args = append(args, d.Token()) + } + return args +} + // NewFromNextSegment returns a new dispenser with a copy of // the tokens from the current token until the end of the // "directive" whether that be to the end of the line or diff --git a/caddyconfig/caddyfile/dispenser_test.go b/caddyconfig/caddyfile/dispenser_test.go index 0f6ee5043..f5d226005 100644 --- a/caddyconfig/caddyfile/dispenser_test.go +++ b/caddyconfig/caddyfile/dispenser_test.go @@ -274,6 +274,66 @@ func TestDispenser_RemainingArgs(t *testing.T) { } } +func TestDispenser_RemainingArgsAsTokens(t *testing.T) { + input := `dir1 arg1 arg2 arg3 + dir2 arg4 arg5 + dir3 arg6 { arg7 + dir4` + d := NewTestDispenser(input) + + d.Next() // dir1 + + args := d.RemainingArgsAsTokens() + + tokenTexts := make([]string, 0, len(args)) + for _, arg := range args { + tokenTexts = append(tokenTexts, arg.Text) + } + + if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(tokenTexts, expected) { + t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts) + } + + d.Next() // dir2 + + args = d.RemainingArgsAsTokens() + + tokenTexts = tokenTexts[:0] + for _, arg := range args { + tokenTexts = append(tokenTexts, arg.Text) + } + + if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(tokenTexts, expected) { + t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts) + } + + d.Next() // dir3 + + args = d.RemainingArgsAsTokens() + tokenTexts = tokenTexts[:0] + for _, arg := range args { + tokenTexts = append(tokenTexts, arg.Text) + } + + if expected := []string{"arg6"}; !reflect.DeepEqual(tokenTexts, expected) { + t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", expected, tokenTexts) + } + + d.Next() // { + d.Next() // arg7 + d.Next() // dir4 + + args = d.RemainingArgsAsTokens() + tokenTexts = tokenTexts[:0] + for _, arg := range args { + tokenTexts = append(tokenTexts, arg.Text) + } + + if len(args) != 0 { + t.Errorf("RemainingArgsAsTokens(): Expected %v, got %v", []string{}, tokenTexts) + } +} + func TestDispenser_ArgErr_Err(t *testing.T) { input := `dir1 { } diff --git a/caddyconfig/caddyfile/parse.go b/caddyconfig/caddyfile/parse.go index d04a1ac46..2bc52aded 100644 --- a/caddyconfig/caddyfile/parse.go +++ b/caddyconfig/caddyfile/parse.go @@ -379,28 +379,23 @@ func (p *parser) doImport(nesting int) error { if len(blockTokens) > 0 { // use such tokens to create a new dispenser, and then use it to parse each block bd := NewDispenser(blockTokens) + + // one iteration processes one sub-block inside the import for bd.Next() { - // see if we can grab a key - var currentMappingKey string - if bd.Val() == "{" { + currentMappingKey := bd.Val() + + if currentMappingKey == "{" { return p.Err("anonymous blocks are not supported") } - currentMappingKey = bd.Val() - currentMappingTokens := []Token{} - // read all args until end of line / { - if bd.NextArg() { + + // load up all arguments (if there even are any) + currentMappingTokens := bd.RemainingArgsAsTokens() + + // load up the entire block + for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); { currentMappingTokens = append(currentMappingTokens, bd.Token()) - for bd.NextArg() { - currentMappingTokens = append(currentMappingTokens, bd.Token()) - } - // TODO(elee1766): we don't enter another mapping here because it's annoying to extract the { and } properly. - // maybe someone can do that in the future - } else { - // attempt to enter a block and add tokens to the currentMappingTokens - for mappingNesting := bd.Nesting(); bd.NextBlock(mappingNesting); { - currentMappingTokens = append(currentMappingTokens, bd.Token()) - } } + blockMapping[currentMappingKey] = currentMappingTokens } } diff --git a/caddyconfig/caddyfile/parse_test.go b/caddyconfig/caddyfile/parse_test.go index d3fada4e0..bf149e635 100644 --- a/caddyconfig/caddyfile/parse_test.go +++ b/caddyconfig/caddyfile/parse_test.go @@ -18,6 +18,7 @@ import ( "bytes" "os" "path/filepath" + "strings" "testing" ) @@ -884,6 +885,51 @@ func TestRejectsGlobalMatcher(t *testing.T) { } } +func TestRejectAnonymousImportBlock(t *testing.T) { + p := testParser(` + (site) { + http://{args[0]} https://{args[0]} { + {block} + } + } + + import site test.domain { + { + header_up Host {host} + header_up X-Real-IP {remote_host} + } + } + `) + _, err := p.parseAll() + if err == nil { + t.Fatal("Expected an error, but got nil") + } + expected := "anonymous blocks are not supported" + if !strings.HasPrefix(err.Error(), "anonymous blocks are not supported") { + t.Errorf("Expected error to start with '%s' but got '%v'", expected, err) + } +} + +func TestAcceptSiteImportWithBraces(t *testing.T) { + p := testParser(` + (site) { + http://{args[0]} https://{args[0]} { + {block} + } + } + + import site test.domain { + reverse_proxy http://192.168.1.1:8080 { + header_up Host {host} + } + } + `) + _, err := p.parseAll() + if err != nil { + t.Errorf("Expected error to be nil but got '%v'", err) + } +} + func testParser(input string) parser { return parser{Dispenser: NewTestDispenser(input)} } diff --git a/caddytest/integration/caddyfile_adapt/import_block_anonymous.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_block_anonymous.caddyfiletest new file mode 100644 index 000000000..3ab1745e9 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_block_anonymous.caddyfiletest @@ -0,0 +1,13 @@ +(site) { + http://{args[0]} https://{args[0]} { + {block} + } +} +import site test.domain { + { + header_up Host {host} + header_up X-Real-IP {remote_host} + } +} +---------- +anonymous blocks are not supported \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/import_block_with_site_block.caddyfiletest b/caddytest/integration/caddyfile_adapt/import_block_with_site_block.caddyfiletest new file mode 100644 index 000000000..7527ce4d6 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/import_block_with_site_block.caddyfiletest @@ -0,0 +1,65 @@ +(site) { + https://{args[0]} { + {block} + } +} + +import site test.domain { + reverse_proxy http://192.168.1.1:8080 { + header_up Host {host} + } +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" + ], + "routes": [ + { + "match": [ + { + "host": [ + "test.domain" + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "headers": { + "request": { + "set": { + "Host": [ + "{http.request.host}" + ] + } + } + }, + "upstreams": [ + { + "dial": "192.168.1.1:8080" + } + ] + } + ] + } + ] + } + ], + "terminal": true + } + ] + } + } + } + } +}