@marcel91200 / caddy Mirror · read-only / commits / 1706dc8f
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, 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 +}