diff --git a/app/dns/dns.go b/app/dns/dns.go index 2f272989..ec350e84 100644 --- a/app/dns/dns.go +++ b/app/dns/dns.go @@ -271,11 +271,11 @@ func (s *DNS) sortClients(domain string) []*Client { // Priority domain matching hasMatch := false - MatchSlice := s.domainMatcher.Match(strings.ToLower(domain)) - sort.Slice(MatchSlice, func(i, j int) bool { - return MatchSlice[i] < MatchSlice[j] + matchSlice := s.domainMatcher.Match(strings.ToLower(domain)) + sort.Slice(matchSlice, func(i, j int) bool { + return matchSlice[i] < matchSlice[j] }) - for _, match := range MatchSlice { + for _, match := range matchSlice { info := s.matcherInfos[match] client := s.clients[info.clientIdx] domainRule := info.domainRule diff --git a/common/geodata/domain_matcher.go b/common/geodata/domain_matcher.go index a3490610..e5e854f9 100644 --- a/common/geodata/domain_matcher.go +++ b/common/geodata/domain_matcher.go @@ -11,7 +11,11 @@ import ( ) type DomainMatcher interface { + // Match returns the indices of all rules that match the input domain. + // The returned slice is owned by the caller and may be safely modified. + // Note: the slice may contain duplicates and the order is unspecified. Match(input string) []uint32 + MatchAny(input string) bool } diff --git a/common/geodata/domain_matcher_test.go b/common/geodata/domain_matcher_test.go index 775b3371..0506df09 100644 --- a/common/geodata/domain_matcher_test.go +++ b/common/geodata/domain_matcher_test.go @@ -48,3 +48,25 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) { t.Fatalf("Match() = %v, want %v", got, want) } } + +func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) { + matcher, err := (&MphDomainMatcherFactory{}).BuildMatcher([]*DomainRule{ + {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}}, + {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}}, + }) + if err != nil { + t.Fatalf("BuildMatcher() failed: %v", err) + } + + got := matcher.Match("example.com") + if !reflect.DeepEqual(got, []uint32{0, 1}) { + t.Fatalf("Match() = %v, want %v", got, []uint32{0, 1}) + } + + got[0] = 1 + + gotAgain := matcher.Match("example.com") + if !reflect.DeepEqual(gotAgain, []uint32{0, 1}) { + t.Fatalf("Match() after caller mutation = %v, want %v", gotAgain, []uint32{0, 1}) + } +} diff --git a/common/geodata/strmatcher/matchers.go b/common/geodata/strmatcher/matchers.go index 7e073764..fa288804 100644 --- a/common/geodata/strmatcher/matchers.go +++ b/common/geodata/strmatcher/matchers.go @@ -3,6 +3,7 @@ package strmatcher import ( "errors" "regexp" + "slices" "strings" "unicode/utf8" @@ -253,13 +254,12 @@ func AddMatcherToGroup(g MatcherGroup, matcher Matcher, value uint32) error { } // CompositeMatches flattens the matches slice to produce a single matched indices slice. -// It is designed to avoid new memory allocation as possible. func CompositeMatches(matches [][]uint32) []uint32 { switch len(matches) { case 0: return nil case 1: - return matches[0] + return slices.Clone(matches[0]) default: result := make([]uint32, 0, 5) for i := 0; i < len(matches); i++ { diff --git a/common/geodata/strmatcher/strmatcher.go b/common/geodata/strmatcher/strmatcher.go index e4187f63..2cc6f252 100644 --- a/common/geodata/strmatcher/strmatcher.go +++ b/common/geodata/strmatcher/strmatcher.go @@ -62,6 +62,7 @@ type IndexMatcher interface { // Match returns the indices of all matchers that matches the input. // * Empty array is returned if no such matcher exists. // * The order of returned matchers should follow priority specification. + // * The returned slice is owned by the caller and may be safely modified. // Priority specification: // 1. Priority between matcher types: full > domain > substr > regex. // 2. Priority of same-priority matchers matching at same position: the early added takes precedence. @@ -89,6 +90,7 @@ type ValueMatcher interface { // * Empty array is returned if no such matcher exists. // * The order of returned values should follow priority specification. // * Same value may appear multiple times if multiple matched matchers were added with that value. + // * The returned slice is owned by the caller and may be safely modified. // Priority specification: // 1. Priority between matcher types: full > domain > substr > regex. // 2. Priority of same-priority matchers matching at same position: the early added takes precedence.