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": {