2018-06-26 15:57:41 -04:00
|
|
|
package strmatcher
|
|
|
|
|
2018-08-19 15:04:15 -04:00
|
|
|
import (
|
|
|
|
"regexp"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2018-08-29 11:19:44 -04:00
|
|
|
"v2ray.com/core/common"
|
2018-08-19 15:04:15 -04:00
|
|
|
"v2ray.com/core/common/task"
|
|
|
|
)
|
2018-06-26 15:57:41 -04:00
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// Matcher is the interface to determine a string matches a pattern.
|
2018-06-26 15:57:41 -04:00
|
|
|
type Matcher interface {
|
2018-08-20 09:39:58 -04:00
|
|
|
// Match returns true if the given string matches a predefined pattern.
|
2018-06-26 15:57:41 -04:00
|
|
|
Match(string) bool
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// Type is the type of the matcher.
|
2018-06-26 15:57:41 -04:00
|
|
|
type Type byte
|
|
|
|
|
|
|
|
const (
|
2018-08-20 09:39:58 -04:00
|
|
|
// Full is the type of matcher that the input string must exactly equal to the pattern.
|
2018-06-26 15:57:41 -04:00
|
|
|
Full Type = iota
|
2018-08-20 09:39:58 -04:00
|
|
|
// Substr is the type of matcher that the input string must contain the pattern as a sub-string.
|
2018-06-26 15:57:41 -04:00
|
|
|
Substr
|
2018-08-20 09:39:58 -04:00
|
|
|
// Domain is the type of matcher that the input string must be a sub-domain or itself of the pattern.
|
2018-06-26 15:57:41 -04:00
|
|
|
Domain
|
2018-08-20 09:39:58 -04:00
|
|
|
// Regex is the type of matcher that the input string must matches the regular-expression pattern.
|
2018-06-26 15:57:41 -04:00
|
|
|
Regex
|
|
|
|
)
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// New creates a new Matcher based on the given pattern.
|
2018-06-26 15:57:41 -04:00
|
|
|
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 ®exMatcher{
|
|
|
|
pattern: r,
|
|
|
|
}, nil
|
|
|
|
default:
|
|
|
|
panic("Unknown type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// IndexMatcher is the interface for matching with a group of matchers.
|
2018-08-19 15:04:15 -04:00
|
|
|
type IndexMatcher interface {
|
2018-08-20 09:39:58 -04:00
|
|
|
// Match returns the the index of a matcher that matches the input. It returns 0 if no such matcher exists.
|
|
|
|
Match(input string) uint32
|
2018-08-19 15:04:15 -04:00
|
|
|
}
|
|
|
|
|
2018-06-26 15:57:41 -04:00
|
|
|
type matcherEntry struct {
|
|
|
|
m Matcher
|
|
|
|
id uint32
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// MatcherGroup is an implementation of IndexMatcher.
|
|
|
|
// Empty initialization works.
|
2018-06-26 15:57:41 -04:00
|
|
|
type MatcherGroup struct {
|
|
|
|
count uint32
|
2018-08-20 03:57:06 -04:00
|
|
|
fullMatcher FullMatcherGroup
|
2018-08-19 15:04:15 -04:00
|
|
|
domainMatcher DomainMatcherGroup
|
2018-06-26 15:57:41 -04:00
|
|
|
otherMatchers []matcherEntry
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// Add adds a new Matcher into the MatcherGroup, and returns its index. The index will never be 0.
|
2018-06-26 15:57:41 -04:00
|
|
|
func (g *MatcherGroup) Add(m Matcher) uint32 {
|
|
|
|
g.count++
|
2018-08-20 03:57:06 -04:00
|
|
|
c := g.count
|
2018-06-26 15:57:41 -04:00
|
|
|
|
2018-08-19 15:04:15 -04:00
|
|
|
switch tm := m.(type) {
|
|
|
|
case fullMatcher:
|
2018-08-20 03:57:06 -04:00
|
|
|
g.fullMatcher.addMatcher(tm, c)
|
2018-08-19 15:04:15 -04:00
|
|
|
case domainMatcher:
|
2018-08-20 03:57:06 -04:00
|
|
|
g.domainMatcher.addMatcher(tm, c)
|
2018-08-19 15:04:15 -04:00
|
|
|
default:
|
2018-06-26 15:57:41 -04:00
|
|
|
g.otherMatchers = append(g.otherMatchers, matcherEntry{
|
|
|
|
m: m,
|
|
|
|
id: c,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// Match implements IndexMatcher.Match.
|
2018-06-26 15:57:41 -04:00
|
|
|
func (g *MatcherGroup) Match(pattern string) uint32 {
|
2018-08-20 03:57:06 -04:00
|
|
|
if c := g.fullMatcher.Match(pattern); c > 0 {
|
2018-06-26 15:57:41 -04:00
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2018-08-19 15:07:31 -04:00
|
|
|
if c := g.domainMatcher.Match(pattern); c > 0 {
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2018-06-26 15:57:41 -04:00
|
|
|
for _, e := range g.otherMatchers {
|
|
|
|
if e.m.Match(pattern) {
|
|
|
|
return e.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// Size returns the number of matchers in the MatcherGroup.
|
2018-06-26 15:57:41 -04:00
|
|
|
func (g *MatcherGroup) Size() uint32 {
|
|
|
|
return g.count
|
|
|
|
}
|
2018-08-19 15:04:15 -04:00
|
|
|
|
|
|
|
type cacheEntry struct {
|
|
|
|
timestamp time.Time
|
|
|
|
result uint32
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// CachedMatcherGroup is a IndexMatcher with cachable results.
|
2018-08-19 15:04:15 -04:00
|
|
|
type CachedMatcherGroup struct {
|
2018-08-19 16:16:06 -04:00
|
|
|
sync.RWMutex
|
2018-08-19 15:04:15 -04:00
|
|
|
group *MatcherGroup
|
|
|
|
cache map[string]cacheEntry
|
|
|
|
cleanup *task.Periodic
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// NewCachedMatcherGroup creats a new CachedMatcherGroup.
|
2018-08-19 15:04:15 -04:00
|
|
|
func NewCachedMatcherGroup(g *MatcherGroup) *CachedMatcherGroup {
|
|
|
|
r := &CachedMatcherGroup{
|
|
|
|
group: g,
|
|
|
|
cache: make(map[string]cacheEntry),
|
|
|
|
}
|
|
|
|
r.cleanup = &task.Periodic{
|
|
|
|
Interval: time.Second * 30,
|
|
|
|
Execute: func() error {
|
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2018-08-19 16:16:06 -04:00
|
|
|
expire := time.Now().Add(-1 * time.Second * 120)
|
2018-08-19 15:04:15 -04:00
|
|
|
for p, e := range r.cache {
|
|
|
|
if e.timestamp.Before(expire) {
|
|
|
|
delete(r.cache, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2018-08-29 11:19:44 -04:00
|
|
|
common.Must(r.cleanup.Start())
|
2018-08-19 15:04:15 -04:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2018-08-20 09:39:58 -04:00
|
|
|
// Match implements IndexMatcher.Match.
|
2018-08-19 15:04:15 -04:00
|
|
|
func (g *CachedMatcherGroup) Match(pattern string) uint32 {
|
2018-08-19 16:16:06 -04:00
|
|
|
g.RLock()
|
2018-08-19 15:04:15 -04:00
|
|
|
r, f := g.cache[pattern]
|
2018-08-19 16:16:06 -04:00
|
|
|
g.RUnlock()
|
2018-08-19 15:04:15 -04:00
|
|
|
if f {
|
|
|
|
return r.result
|
|
|
|
}
|
|
|
|
|
|
|
|
mr := g.group.Match(pattern)
|
|
|
|
|
2018-08-19 16:16:06 -04:00
|
|
|
g.Lock()
|
2018-08-19 15:04:15 -04:00
|
|
|
g.cache[pattern] = cacheEntry{
|
|
|
|
result: mr,
|
|
|
|
timestamp: time.Now(),
|
|
|
|
}
|
2018-08-19 16:16:06 -04:00
|
|
|
g.Unlock()
|
2018-08-19 15:04:15 -04:00
|
|
|
|
|
|
|
return mr
|
|
|
|
}
|