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

caddyfile: Reject directives in the place of site addresses (#6104)

Co-authored-by: Francis Lavoie <lavofr@gmail.com>

Aziz Rmadi committed Feb 18, 2024 at 18:22 UTC b893c8c5f856ba7399ed05c8de82fa5c8158ff00
8 files changed +135 -40
caddyconfig/caddyfile/parse.go
+31 -21
@@ -160,14 +160,14 @@ func (p *parser) begin() error {
160 }
161
162 if ok, name := p.isNamedRoute(); ok {
163 - // named routes only have one key, the route name
164 - p.block.Keys = []string{name}
165 - p.block.IsNamedRoute = true
166 -
163 // we just need a dummy leading token to ease parsing later
164 nameToken := p.Token()
165 nameToken.Text = name
166
167 + // named routes only have one key, the route name
168 + p.block.Keys = []Token{nameToken}
169 + p.block.IsNamedRoute = true
170 +
171 // get all the tokens from the block, including the braces
172 tokens, err := p.blockTokens(true)
173 if err != nil {
@@ -211,10 +211,11 @@ func (p *parser) addresses() error {
211 var expectingAnother bool
212
213 for {
214 - tkn := p.Val()
214 + value := p.Val()
215 + token := p.Token()
216
217 // special case: import directive replaces tokens during parse-time
217 - if tkn == "import" && p.isNewLine() {
218 + if value == "import" && p.isNewLine() {
219 err := p.doImport(0)
220 if err != nil {
221 return err
@@ -223,9 +224,9 @@ func (p *parser) addresses() error {
224 }
225
226 // Open brace definitely indicates end of addresses
226 - if tkn == "{" {
227 + if value == "{" {
228 if expectingAnother {
228 - return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
229 + return p.Errf("Expected another address but had '%s' - check for extra comma", value)
230 }
231 // Mark this server block as being defined with braces.
232 // This is used to provide a better error message when
@@ -237,15 +238,15 @@ func (p *parser) addresses() error {
238 }
239
240 // Users commonly forget to place a space between the address and the '{'
240 - if strings.HasSuffix(tkn, "{") {
241 - return p.Errf("Site addresses cannot end with a curly brace: '%s' - put a space between the token and the brace", tkn)
241 + if strings.HasSuffix(value, "{") {
242 + return p.Errf("Site addresses cannot end with a curly brace: '%s' - put a space between the token and the brace", value)
243 }
244
244 - if tkn != "" { // empty token possible if user typed ""
245 + if value != "" { // empty token possible if user typed ""
246 // Trailing comma indicates another address will follow, which
247 // may possibly be on the next line
247 - if tkn[len(tkn)-1] == ',' {
248 - tkn = tkn[:len(tkn)-1]
248 + if value[len(value)-1] == ',' {
249 + value = value[:len(value)-1]
250 expectingAnother = true
251 } else {
252 expectingAnother = false // but we may still see another one on this line
@@ -254,11 +255,12 @@ func (p *parser) addresses() error {
255 // If there's a comma here, it's probably because they didn't use a space
256 // between their two domains, e.g. "foo.com,bar.com", which would not be
257 // parsed as two separate site addresses.
257 - if strings.Contains(tkn, ",") {
258 - return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", tkn)
258 + if strings.Contains(value, ",") {
259 + return p.Errf("Site addresses cannot contain a comma ',': '%s' - put a space after the comma to separate site addresses", value)
260 }
261
261 - p.block.Keys = append(p.block.Keys, tkn)
262 + token.Text = value
263 + p.block.Keys = append(p.block.Keys, token)
264 }
265
266 // Advance token and possibly break out of loop or return error
@@ -637,8 +639,8 @@ func (p *parser) closeCurlyBrace() error {
639 func (p *parser) isNamedRoute() (bool, string) {
640 keys := p.block.Keys
641 // A named route block is a single key with parens, prefixed with &.
640 - if len(keys) == 1 && strings.HasPrefix(keys[0], "&(") && strings.HasSuffix(keys[0], ")") {
641 - return true, strings.TrimSuffix(keys[0][2:], ")")
642 + if len(keys) == 1 && strings.HasPrefix(keys[0].Text, "&(") && strings.HasSuffix(keys[0].Text, ")") {
643 + return true, strings.TrimSuffix(keys[0].Text[2:], ")")
644 }
645 return false, ""
646 }
@@ -646,8 +648,8 @@ func (p *parser) isNamedRoute() (bool, string) {
648 func (p *parser) isSnippet() (bool, string) {
649 keys := p.block.Keys
650 // A snippet block is a single key with parens. Nothing else qualifies.
649 - if len(keys) == 1 && strings.HasPrefix(keys[0], "(") && strings.HasSuffix(keys[0], ")") {
650 - return true, strings.TrimSuffix(keys[0][1:], ")")
651 + if len(keys) == 1 && strings.HasPrefix(keys[0].Text, "(") && strings.HasSuffix(keys[0].Text, ")") {
652 + return true, strings.TrimSuffix(keys[0].Text[1:], ")")
653 }
654 return false, ""
655 }
@@ -691,11 +693,19 @@ func (p *parser) blockTokens(retainCurlies bool) ([]Token, error) {
693 // grouped by segments.
694 type ServerBlock struct {
695 HasBraces bool
694 - Keys []string
696 + Keys []Token
697 Segments []Segment
698 IsNamedRoute bool
699 }
700
701 +func (sb ServerBlock) GetKeysText() []string {
702 + res := []string{}
703 + for _, k := range sb.Keys {
704 + res = append(res, k.Text)
705 + }
706 + return res
707 +}
708 +
709 // DispenseDirective returns a dispenser that contains
710 // all the tokens in the server block.
711 func (sb ServerBlock) DispenseDirective(dir string) *Dispenser {
caddyconfig/caddyfile/parse_test.go
+10 -8
@@ -347,7 +347,7 @@ func TestParseOneAndImport(t *testing.T) {
347 i, len(test.keys), len(result.Keys))
348 continue
349 }
350 - for j, addr := range result.Keys {
350 + for j, addr := range result.GetKeysText() {
351 if addr != test.keys[j] {
352 t.Errorf("Test %d, key %d: Expected '%s', but was '%s'",
353 i, j, test.keys[j], addr)
@@ -379,8 +379,9 @@ func TestRecursiveImport(t *testing.T) {
379 }
380
381 isExpected := func(got ServerBlock) bool {
382 - if len(got.Keys) != 1 || got.Keys[0] != "localhost" {
383 - t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
382 + textKeys := got.GetKeysText()
383 + if len(textKeys) != 1 || textKeys[0] != "localhost" {
384 + t.Errorf("got keys unexpected: expect localhost, got %v", textKeys)
385 return false
386 }
387 if len(got.Segments) != 2 {
@@ -474,8 +475,9 @@ func TestDirectiveImport(t *testing.T) {
475 }
476
477 isExpected := func(got ServerBlock) bool {
477 - if len(got.Keys) != 1 || got.Keys[0] != "localhost" {
478 - t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
478 + textKeys := got.GetKeysText()
479 + if len(textKeys) != 1 || textKeys[0] != "localhost" {
480 + t.Errorf("got keys unexpected: expect localhost, got %v", textKeys)
481 return false
482 }
483 if len(got.Segments) != 2 {
@@ -616,7 +618,7 @@ func TestParseAll(t *testing.T) {
618 i, len(test.keys[j]), j, len(block.Keys))
619 continue
620 }
619 - for k, addr := range block.Keys {
621 + for k, addr := range block.GetKeysText() {
622 if addr != test.keys[j][k] {
623 t.Errorf("Test %d, block %d, key %d: Expected '%s', but got '%s'",
624 i, j, k, test.keys[j][k], addr)
@@ -769,7 +771,7 @@ func TestSnippets(t *testing.T) {
771 if len(blocks) != 1 {
772 t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
773 }
772 - if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
774 + if actual, expected := blocks[0].GetKeysText()[0], "http://example.com"; expected != actual {
775 t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
776 }
777 if len(blocks[0].Segments) != 2 {
@@ -844,7 +846,7 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
846 if len(blocks) != 1 {
847 t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
848 }
847 - if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
849 + if actual, expected := blocks[0].GetKeysText()[0], "http://example.com"; expected != actual {
850 t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
851 }
852 if len(blocks[0].Segments) != 1 {
caddyconfig/httpcaddyfile/addresses.go
+5 -5
@@ -88,15 +88,15 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
88 // will be served by them; this has the effect of treating each
89 // key of a server block as its own, but without having to repeat its
90 // contents in cases where multiple keys really can be served together
91 - addrToKeys := make(map[string][]string)
91 + addrToKeys := make(map[string][]caddyfile.Token)
92 for j, key := range sblock.block.Keys {
93 // a key can have multiple listener addresses if there are multiple
94 // arguments to the 'bind' directive (although they will all have
95 // the same port, since the port is defined by the key or is implicit
96 // through automatic HTTPS)
97 - addrs, err := st.listenerAddrsForServerBlockKey(sblock, key, options)
97 + addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
98 if err != nil {
99 - return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key, err)
99 + return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
100 }
101
102 // associate this key with each listener address it is served on
@@ -122,9 +122,9 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
122 // parse keys so that we only have to do it once
123 parsedKeys := make([]Address, 0, len(keys))
124 for _, key := range keys {
125 - addr, err := ParseAddress(key)
125 + addr, err := ParseAddress(key.Text)
126 if err != nil {
127 - return nil, fmt.Errorf("parsing key '%s': %v", key, err)
127 + return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
128 }
129 parsedKeys = append(parsedKeys, addr.Normalize())
130 }
caddyconfig/httpcaddyfile/httptype.go
+8 -5
@@ -65,8 +65,11 @@ func (st ServerType) Setup(
65 originalServerBlocks := make([]serverBlock, 0, len(inputServerBlocks))
66 for _, sblock := range inputServerBlocks {
67 for j, k := range sblock.Keys {
68 - if j == 0 && strings.HasPrefix(k, "@") {
69 - return nil, warnings, fmt.Errorf("cannot define a matcher outside of a site block: '%s'", k)
68 + if j == 0 && strings.HasPrefix(k.Text, "@") {
69 + return nil, warnings, fmt.Errorf("%s:%d: cannot define a matcher outside of a site block: '%s'", k.File, k.Line, k.Text)
70 + }
71 + if _, ok := registeredDirectives[k.Text]; ok {
72 + return nil, warnings, fmt.Errorf("%s:%d: parsed '%s' as a site address, but it is a known directive; directives must appear in a site block", k.File, k.Line, k.Text)
73 }
74 }
75 originalServerBlocks = append(originalServerBlocks, serverBlock{
@@ -490,7 +493,7 @@ func (ServerType) extractNamedRoutes(
493 route.HandlersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", subroute.CaddyModule().ID.Name(), h.warnings)}
494 }
495
493 - namedRoutes[sb.block.Keys[0]] = &route
496 + namedRoutes[sb.block.GetKeysText()[0]] = &route
497 }
498 options["named_routes"] = namedRoutes
499
@@ -528,12 +531,12 @@ func (st *ServerType) serversFromPairings(
531 // address), otherwise their routes will improperly be added
532 // to the same server (see issue #4635)
533 for j, sblock1 := range p.serverBlocks {
531 - for _, key := range sblock1.block.Keys {
534 + for _, key := range sblock1.block.GetKeysText() {
535 for k, sblock2 := range p.serverBlocks {
536 if k == j {
537 continue
538 }
536 - if sliceContains(sblock2.block.Keys, key) {
539 + if sliceContains(sblock2.block.GetKeysText(), key) {
540 return nil, fmt.Errorf("ambiguous site definition: %s", key)
541 }
542 }
caddytest/integration/acme_test.go
+1 -1
@@ -23,7 +23,7 @@ import (
23 "go.uber.org/zap"
24 )
25
26 -const acmeChallengePort = 8080
26 +const acmeChallengePort = 9081
27
28 // Test the basic functionality of Caddy's ACME server
29 func TestACMEServerWithDefaults(t *testing.T) {
caddytest/integration/caddyfile_adapt/http_valid_directive_like_site_address.txt new
+46
@@ -0,0 +1,46 @@
1 +http://handle {
2 + file_server
3 +}
4 +----------
5 +{
6 + "apps": {
7 + "http": {
8 + "servers": {
9 + "srv0": {
10 + "listen": [
11 + ":80"
12 + ],
13 + "routes": [
14 + {
15 + "match": [
16 + {
17 + "host": [
18 + "handle"
19 + ]
20 + }
21 + ],
22 + "handle": [
23 + {
24 + "handler": "subroute",
25 + "routes": [
26 + {
27 + "handle": [
28 + {
29 + "handler": "file_server",
30 + "hide": [
31 + "./Caddyfile"
32 + ]
33 + }
34 + ]
35 + }
36 + ]
37 + }
38 + ],
39 + "terminal": true
40 + }
41 + ]
42 + }
43 + }
44 + }
45 + }
46 +}
\ No newline at end of file
caddytest/integration/caddyfile_test.go
+28
@@ -496,6 +496,7 @@ func TestUriReplace(t *testing.T) {
496
497 tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
498 }
499 +
500 func TestHandleErrorSimpleCodes(t *testing.T) {
501 tester := caddytest.NewTester(t)
502 tester.InitServer(`{
@@ -584,3 +585,30 @@ func TestHandleErrorRangeAndCodes(t *testing.T) {
585 tester.AssertGetResponse("http://localhost:9080/threehundred", 301, "Error code is equal to 500 or in the [300..399] range")
586 tester.AssertGetResponse("http://localhost:9080/private", 410, "Error in the [400 .. 499] range")
587 }
588 +
589 +func TestInvalidSiteAddressesAsDirectives(t *testing.T) {
590 + type testCase struct {
591 + config, expectedError string
592 + }
593 +
594 + failureCases := []testCase{
595 + {
596 + config: `
597 + handle {
598 + file_server
599 + }`,
600 + expectedError: `Caddyfile:2: parsed 'handle' as a site address, but it is a known directive; directives must appear in a site block`,
601 + },
602 + {
603 + config: `
604 + reverse_proxy localhost:9000 localhost:9001 {
605 + file_server
606 + }`,
607 + expectedError: `Caddyfile:2: parsed 'reverse_proxy' as a site address, but it is a known directive; directives must appear in a site block`,
608 + },
609 + }
610 +
611 + for _, failureCase := range failureCases {
612 + caddytest.AssertLoadError(t, failureCase.config, "caddyfile", failureCase.expectedError)
613 + }
614 +}
caddytest/integration/pki_test.go
+6
@@ -9,6 +9,9 @@ import (
9 func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
10 caddytest.AssertLoadError(t, `
11 {
12 + "admin": {
13 + "disabled": true
14 + },
15 "apps": {
16 "http": {
17 "servers": {
@@ -56,6 +59,9 @@ func TestLeafCertLifetimeLessThanIntermediate(t *testing.T) {
59 func TestIntermediateLifetimeLessThanRoot(t *testing.T) {
60 caddytest.AssertLoadError(t, `
61 {
62 + "admin": {
63 + "disabled": true
64 + },
65 "apps": {
66 "http": {
67 "servers": {