@marcel91200 / caddy Mirror · read-only / commits / 7dedd148
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, 12:15 UTC

fix(caddyfile): {block} in snippet (#7558)

* fix(caddyfile): {block} in snippet Resolve issue #7557 So, here is the situation: - Pull request #7206 included some changes to the doImport's function of Caddyfile's parser. What it does is that if there is no token within a block that follows the import, and the import contains `{block}`, then the `{block}` token is discarded. - After this pull request: - Issue #7518 noticed that in cases that `{block}` was not imported, a runtime error was raised due to the assumption that tokens were always added to `tokensCopy` on every iteration of `importedTokens`. This was fixed by pull request #7543. - Issue #7557 notices that {block} can be ignored when imported from a certain file. There, it's again an issue with how the import works. When `import snippets` is called, this import instruction doesn't contains any nested blocks. And when the argument replacer that is the `importedTokens` loop is called and finds `{block}`, it uses the block from the file's import (which in this case is nothing), `{block}` is erased, and unavailable when the import directive is called for the imported snippet. The changed in this commit addresses the second issue by checking before replacing `{block}` if we're currently in a snippet definition, and appending the `{block}` token to `tokensCopy` if we are. With this changes, when importing those snippets, the `{block}` token will be available to be replaced by the nested blocks in `tokensToAdd` if needed, or erased if there are no nested blocks and `tokensToAdd` is empty. Tests added in pull requests #7206 and #7543 passes with this new implementation, confirming that unused `{block}` are accepted if nothing is passed to `import`, as well as the other usual tests. A new test was also added based on issue #7557 reporting, and also passes. Signed-off-by: prettysunflower <me@prettysunflower.moe> * caddyfile: add imported snippet block placeholder coverage --------- Signed-off-by: prettysunflower <me@prettysunflower.moe> Co-authored-by: Zen Dodd <mail@steadytao.com>

prettysunflower committed Apr 15, 2026 at 02:58 UTC 7dedd1486c252133c9fb0d2d28992c928ffc451d
4 files changed +98 -1
caddyconfig/caddyfile/parse.go
+5 -1
@@ -550,7 +550,11 @@ func (p *parser) doImport(nesting int) error {
550 }
551
552 if foundBlockDirective {
553 - tokensCopy = append(tokensCopy, tokensToAdd...)
553 + if maybeSnippet {
554 + tokensCopy = append(tokensCopy, token)
555 + } else {
556 + tokensCopy = append(tokensCopy, tokensToAdd...)
557 + }
558 continue
559 }
560
caddyconfig/caddyfile/parse_test.go
+71
@@ -960,6 +960,77 @@ import `+importFile2+`
960 }
961 }
962
963 +func TestImportedSnippetDefinitionRetainsBlockPlaceholder(t *testing.T) {
964 + tempDir := t.TempDir()
965 + importFile := filepath.Join(tempDir, "snippets.caddy")
966 +
967 + err := os.WriteFile(importFile, []byte(`
968 + (site) {
969 + http://{args[0]} {
970 + respond "before"
971 + {block}
972 + respond "after"
973 + }
974 + }
975 + `), 0o644)
976 + if err != nil {
977 + t.Fatalf("writing imported snippet file: %v", err)
978 + }
979 +
980 + for _, tc := range []struct {
981 + name string
982 + input string
983 + expectedDirectives []string
984 + }{
985 + {
986 + name: "with nested block",
987 + input: `
988 + import ` + importFile + `
989 +
990 + import site example.com {
991 + redir https://example.net
992 + }
993 + `,
994 + expectedDirectives: []string{"respond", "redir", "respond"},
995 + },
996 + {
997 + name: "without nested block",
998 + input: `
999 + import ` + importFile + `
1000 +
1001 + import site example.com
1002 + `,
1003 + expectedDirectives: []string{"respond", "respond"},
1004 + },
1005 + } {
1006 + t.Run(tc.name, func(t *testing.T) {
1007 + p := testParser(tc.input)
1008 + blocks, err := p.parseAll()
1009 + if err != nil {
1010 + t.Fatalf("parseAll: %v", err)
1011 + }
1012 +
1013 + if len(blocks) != 1 {
1014 + t.Fatalf("expected exactly one server block, got %d", len(blocks))
1015 + }
1016 +
1017 + if actual := blocks[0].GetKeysText(); len(actual) != 1 || actual[0] != "http://example.com" {
1018 + t.Fatalf("expected server block key http://example.com, got %v", actual)
1019 + }
1020 +
1021 + if len(blocks[0].Segments) != len(tc.expectedDirectives) {
1022 + t.Fatalf("expected %d segments, got %d", len(tc.expectedDirectives), len(blocks[0].Segments))
1023 + }
1024 +
1025 + for i, directive := range tc.expectedDirectives {
1026 + if actual := blocks[0].Segments[i].Directive(); actual != directive {
1027 + t.Fatalf("segment %d: expected directive %q, got %q", i, directive, actual)
1028 + }
1029 + }
1030 + })
1031 + }
1032 +}
1033 +
1034 func testParser(input string) parser {
1035 return parser{Dispenser: NewTestDispenser(input)}
1036 }
caddytest/integration/caddyfile_adapt/import_block_snippet_invalid_subdirective.caddyfiletest new
+15
@@ -0,0 +1,15 @@
1 +{
2 + admin off
3 + auto_https off
4 +}
5 +
6 +import testdata/issue_7557_invalid_subdirective_snippet.conf
7 +
8 +:8080 {
9 + import test {
10 + this_is_nonsense
11 + }
12 +}
13 +
14 +----------
15 +parsing caddyfile tokens for 'reverse_proxy': unrecognized subdirective this_is_nonsense
\ No newline at end of file
caddytest/integration/testdata/issue_7557_invalid_subdirective_snippet.conf new
+7
@@ -0,0 +1,7 @@
1 +# Used by import_block_snippet_invalid_subdirective.caddyfiletest
2 +
3 +(test) {
4 + reverse_proxy {
5 + {block}
6 + }
7 +}
\ No newline at end of file