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, 03:30 UTC
caddytls: keep repeated matchers in nested matcher sets (#8137)
Simon Sudarushkin committed
Oct 4, 2026 at 21:13 UTC
1706dc8f722c0542433a7b280f756ff8dd62315e
2 files changed
+70
-4
modules/caddytls/connpolicy.go
+13
-4
@@ -1097,6 +1097,10 @@ var (
1097
func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
1098
matcherMap := make(map[string]ConnectionMatcher)
1099
1100
+ // in case there are multiple instances of the same matcher, concatenate
1101
+ // their tokens (we expect that UnmarshalCaddyfile should be able to
1102
+ // handle more than one segment); otherwise, we'd overwrite other
1103
+ // instances of the matcher in this set
1104
tokensByMatcherName := make(map[string][]caddyfile.Token)
1105
for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
1106
matcherName := d.Val()
@@ -1104,10 +1108,15 @@ func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, er
1108
}
1109
1110
for matcherName, tokens := range tokensByMatcherName {
1107
- dd := caddyfile.NewDispenser(tokens)
1108
- dd.Next() // consume wrapper name
1109
-
1110
- unm, err := caddyfile.UnmarshalModule(dd, "tls.handshake_match."+matcherName)
1111
+ mod, err := caddy.GetModule("tls.handshake_match." + matcherName)
1112
+ if err != nil {
1113
+ return nil, d.Errf("getting matcher module '%s': %v", matcherName, err)
1114
+ }
1115
+ unm, ok := mod.New().(caddyfile.Unmarshaler)
1116
+ if !ok {
1117
+ return nil, d.Errf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
1118
+ }
1119
+ err = unm.UnmarshalCaddyfile(caddyfile.NewDispenser(tokens))
1120
if err != nil {
1121
return nil, err
1122
}
modules/caddytls/connpolicy_test.go
+57
@@ -361,3 +361,60 @@ func TestClientAuthenticationProvision(t *testing.T) {
361
})
362
}
363
}
364
+
365
+func TestParseCaddyfileNestedMatcherSet(t *testing.T) {
366
+ for i, tc := range []struct {
367
+ input string
368
+ expect string
369
+ wantErr bool
370
+ }{
371
+ {
372
+ input: `match sni example.com`,
373
+ expect: `{"sni":["example.com"]}`,
374
+ },
375
+ {
376
+ input: `match {
377
+ sni example.com
378
+ sni example.net
379
+ }`,
380
+ expect: `{"sni":["example.com","example.net"]}`,
381
+ },
382
+ {
383
+ input: `match {
384
+ remote_ip 10.0.0.0/8
385
+ sni example.com
386
+ remote_ip !10.1.2.3
387
+ }`,
388
+ expect: `{"remote_ip":{"ranges":["10.0.0.0/8"],"not_ranges":["10.1.2.3"]},"sni":["example.com"]}`,
389
+ },
390
+ {
391
+ input: `match {
392
+ sni_regexp ^a
393
+ sni_regexp ^b
394
+ }`,
395
+ wantErr: true,
396
+ },
397
+ } {
398
+ d := caddyfile.NewTestDispenser(tc.input)
399
+ d.Next() // consume "match"
400
+ matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
401
+ if tc.wantErr {
402
+ if err == nil {
403
+ t.Errorf("Test %d: expected error but got none", i)
404
+ }
405
+ continue
406
+ }
407
+ if err != nil {
408
+ t.Errorf("Test %d: unexpected error: %v", i, err)
409
+ continue
410
+ }
411
+
412
+ actual, err := json.Marshal(matcherSet)
413
+ if err != nil {
414
+ t.Fatalf("Test %d: marshaling matcher set: %v", i, err)
415
+ }
416
+ if string(actual) != tc.expect {
417
+ t.Errorf("Test %d: expected %s, got %s", i, tc.expect, actual)
418
+ }
419
+ }
420
+}