Darien Raymond %!s(int64=7) %!d(string=hai) anos
pai
achega
cb0eb91f2b

+ 21 - 71
app/router/condition.go

@@ -2,13 +2,12 @@ package router
 
 import (
 	"context"
-	"regexp"
-	"strings"
 	"sync"
 	"time"
 
 	"v2ray.com/core/common/net"
 	"v2ray.com/core/common/protocol"
+	"v2ray.com/core/common/strmatcher"
 	"v2ray.com/core/proxy"
 )
 
@@ -73,44 +72,41 @@ type timedResult struct {
 
 type CachableDomainMatcher struct {
 	sync.Mutex
-	matchers []domainMatcher
+	matchers *strmatcher.MatcherGroup
 	cache    map[string]timedResult
 	lastScan time.Time
 }
 
 func NewCachableDomainMatcher() *CachableDomainMatcher {
 	return &CachableDomainMatcher{
-		matchers: make([]domainMatcher, 0, 64),
+		matchers: strmatcher.NewMatcherGroup(),
 		cache:    make(map[string]timedResult, 512),
 	}
 }
 
+var matcherTypeMap = map[Domain_Type]strmatcher.Type{
+	Domain_Plain:  strmatcher.Substr,
+	Domain_Regex:  strmatcher.Regex,
+	Domain_Domain: strmatcher.Domain,
+}
+
 func (m *CachableDomainMatcher) Add(domain *Domain) error {
-	switch domain.Type {
-	case Domain_Plain:
-		m.matchers = append(m.matchers, NewPlainDomainMatcher(domain.Value))
-	case Domain_Regex:
-		rm, err := NewRegexpDomainMatcher(domain.Value)
-		if err != nil {
-			return err
-		}
-		m.matchers = append(m.matchers, rm)
-	case Domain_Domain:
-		m.matchers = append(m.matchers, NewSubDomainMatcher(domain.Value))
-	default:
-		return newError("unknown domain type: ", domain.Type).AtWarning()
+	matcherType, f := matcherTypeMap[domain.Type]
+	if !f {
+		return newError("unsupported domain type", domain.Type)
 	}
+
+	matcher, err := matcherType.New(domain.Value)
+	if err != nil {
+		return newError("failed to create domain matcher").Base(err)
+	}
+
+	m.matchers.Add(matcher)
 	return nil
 }
 
 func (m *CachableDomainMatcher) applyInternal(domain string) bool {
-	for _, matcher := range m.matchers {
-		if matcher.Apply(domain) {
-			return true
-		}
-	}
-
-	return false
+	return m.matchers.Match(domain) > 0
 }
 
 type cacheResult int
@@ -139,7 +135,7 @@ func (m *CachableDomainMatcher) findInCache(domain string) cacheResult {
 }
 
 func (m *CachableDomainMatcher) ApplyDomain(domain string) bool {
-	if len(m.matchers) < 64 {
+	if m.matchers.Size() < 64 {
 		return m.applyInternal(domain)
 	}
 
@@ -190,52 +186,6 @@ func (m *CachableDomainMatcher) Apply(ctx context.Context) bool {
 	return m.ApplyDomain(dest.Address.Domain())
 }
 
-type domainMatcher interface {
-	Apply(domain string) bool
-}
-
-type PlainDomainMatcher string
-
-func NewPlainDomainMatcher(pattern string) PlainDomainMatcher {
-	return PlainDomainMatcher(pattern)
-}
-
-func (v PlainDomainMatcher) Apply(domain string) bool {
-	return strings.Contains(domain, string(v))
-}
-
-type RegexpDomainMatcher struct {
-	pattern *regexp.Regexp
-}
-
-func NewRegexpDomainMatcher(pattern string) (*RegexpDomainMatcher, error) {
-	r, err := regexp.Compile(pattern)
-	if err != nil {
-		return nil, err
-	}
-	return &RegexpDomainMatcher{
-		pattern: r,
-	}, nil
-}
-
-func (v *RegexpDomainMatcher) Apply(domain string) bool {
-	return v.pattern.MatchString(strings.ToLower(domain))
-}
-
-type SubDomainMatcher string
-
-func NewSubDomainMatcher(p string) SubDomainMatcher {
-	return SubDomainMatcher(p)
-}
-
-func (m SubDomainMatcher) Apply(domain string) bool {
-	pattern := string(m)
-	if !strings.HasSuffix(domain, pattern) {
-		return false
-	}
-	return len(domain) == len(pattern) || domain[len(domain)-len(pattern)-1] == '.'
-}
-
 type CIDRMatcher struct {
 	cidr     *net.IPNet
 	onSource bool

+ 0 - 40
app/router/condition_test.go

@@ -20,46 +20,6 @@ import (
 	"v2ray.com/ext/sysio"
 )
 
-func TestSubDomainMatcher(t *testing.T) {
-	assert := With(t)
-
-	cases := []struct {
-		pattern string
-		input   string
-		output  bool
-	}{
-		{
-			pattern: "v2ray.com",
-			input:   "www.v2ray.com",
-			output:  true,
-		},
-		{
-			pattern: "v2ray.com",
-			input:   "v2ray.com",
-			output:  true,
-		},
-		{
-			pattern: "v2ray.com",
-			input:   "www.v3ray.com",
-			output:  false,
-		},
-		{
-			pattern: "v2ray.com",
-			input:   "2ray.com",
-			output:  false,
-		},
-		{
-			pattern: "v2ray.com",
-			input:   "xv2ray.com",
-			output:  false,
-		},
-	}
-	for _, test := range cases {
-		matcher := NewSubDomainMatcher(test.pattern)
-		assert(matcher.Apply(test.input) == test.output, IsTrue)
-	}
-}
-
 func TestRoutingRule(t *testing.T) {
 	assert := With(t)
 

+ 36 - 0
common/strmatcher/matchers.go

@@ -0,0 +1,36 @@
+package strmatcher
+
+import (
+	"regexp"
+	"strings"
+)
+
+type fullMatcher string
+
+func (m fullMatcher) Match(s string) bool {
+	return string(m) == s
+}
+
+type substrMatcher string
+
+func (m substrMatcher) Match(s string) bool {
+	return strings.Contains(s, string(m))
+}
+
+type domainMatcher string
+
+func (m domainMatcher) Match(s string) bool {
+	pattern := string(m)
+	if !strings.HasSuffix(s, pattern) {
+		return false
+	}
+	return len(s) == len(pattern) || s[len(s)-len(pattern)-1] == '.'
+}
+
+type regexMatcher struct {
+	pattern *regexp.Regexp
+}
+
+func (m *regexMatcher) Match(s string) bool {
+	return m.pattern.MatchString(s)
+}

+ 68 - 0
common/strmatcher/matchers_test.go

@@ -0,0 +1,68 @@
+package strmatcher_test
+
+import (
+	"testing"
+
+	"v2ray.com/core/common"
+	. "v2ray.com/core/common/strmatcher"
+	ast "v2ray.com/ext/assert"
+)
+
+func TestMatcher(t *testing.T) {
+	assert := ast.With(t)
+
+	cases := []struct {
+		pattern string
+		mType   Type
+		input   string
+		output  bool
+	}{
+		{
+			pattern: "v2ray.com",
+			mType:   Domain,
+			input:   "www.v2ray.com",
+			output:  true,
+		},
+		{
+			pattern: "v2ray.com",
+			mType:   Domain,
+			input:   "v2ray.com",
+			output:  true,
+		},
+		{
+			pattern: "v2ray.com",
+			mType:   Domain,
+			input:   "www.v3ray.com",
+			output:  false,
+		},
+		{
+			pattern: "v2ray.com",
+			mType:   Domain,
+			input:   "2ray.com",
+			output:  false,
+		},
+		{
+			pattern: "v2ray.com",
+			mType:   Domain,
+			input:   "xv2ray.com",
+			output:  false,
+		},
+		{
+			pattern: "v2ray.com",
+			mType:   Full,
+			input:   "v2ray.com",
+			output:  true,
+		},
+		{
+			pattern: "v2ray.com",
+			mType:   Full,
+			input:   "xv2ray.com",
+			output:  false,
+		},
+	}
+	for _, test := range cases {
+		matcher, err := test.mType.New(test.pattern)
+		common.Must(err)
+		assert(matcher.Match(test.input) == test.output, ast.IsTrue)
+	}
+}

+ 89 - 0
common/strmatcher/strmatcher.go

@@ -0,0 +1,89 @@
+package strmatcher
+
+import "regexp"
+
+type Matcher interface {
+	Match(string) bool
+}
+
+type Type byte
+
+const (
+	Full Type = iota
+	Substr
+	Domain
+	Regex
+)
+
+func (t Type) New(pattern string) (Matcher, error) {
+	switch t {
+	case Full:
+		return fullMatcher(pattern), nil
+	case Substr:
+		return substrMatcher(pattern), nil
+	case Domain:
+		return domainMatcher(pattern), nil
+	case Regex:
+		r, err := regexp.Compile(pattern)
+		if err != nil {
+			return nil, err
+		}
+		return &regexMatcher{
+			pattern: r,
+		}, nil
+	default:
+		panic("Unknown type")
+	}
+}
+
+type matcherEntry struct {
+	m  Matcher
+	id uint32
+}
+
+type MatcherGroup struct {
+	count         uint32
+	fullMatchers  map[string]uint32
+	otherMatchers []matcherEntry
+}
+
+func NewMatcherGroup() *MatcherGroup {
+	return &MatcherGroup{
+		count:        1,
+		fullMatchers: make(map[string]uint32),
+	}
+}
+
+func (g *MatcherGroup) Add(m Matcher) uint32 {
+	c := g.count
+	g.count++
+
+	if fm, ok := m.(fullMatcher); ok {
+		g.fullMatchers[string(fm)] = c
+	} else {
+		g.otherMatchers = append(g.otherMatchers, matcherEntry{
+			m:  m,
+			id: c,
+		})
+	}
+
+	return c
+}
+
+func (g *MatcherGroup) Match(pattern string) uint32 {
+	if c, f := g.fullMatchers[pattern]; f {
+		return c
+	}
+
+	for _, e := range g.otherMatchers {
+		if e.m.Match(pattern) {
+			return e.id
+		}
+	}
+
+	return 0
+}
+
+func (g *MatcherGroup) Size() uint32 {
+	return g.count
+}