|
|
|
package strmatcher
|
|
|
|
|
|
|
|
import (
|
|
|
|
"regexp"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"v2ray.com/core/common/task"
|
|
|
|
)
|
|
|
|
|
|
|
|
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 ®exMatcher{
|
|
|
|
pattern: r,
|
|
|
|
}, nil
|
|
|
|
default:
|
|
|
|
panic("Unknown type")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type IndexMatcher interface {
|
|
|
|
Match(pattern string) uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
type matcherEntry struct {
|
|
|
|
m Matcher
|
|
|
|
id uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
type MatcherGroup struct {
|
|
|
|
count uint32
|
|
|
|
fullMatchers map[string]uint32
|
|
|
|
domainMatcher DomainMatcherGroup
|
|
|
|
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++
|
|
|
|
|
|
|
|
switch tm := m.(type) {
|
|
|
|
case fullMatcher:
|
|
|
|
g.fullMatchers[string(tm)] = c
|
|
|
|
case domainMatcher:
|
|
|
|
g.domainMatcher.Add(string(tm), c)
|
|
|
|
default:
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
type cacheEntry struct {
|
|
|
|
timestamp time.Time
|
|
|
|
result uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
type CachedMatcherGroup struct {
|
|
|
|
sync.Mutex
|
|
|
|
group *MatcherGroup
|
|
|
|
cache map[string]cacheEntry
|
|
|
|
cleanup *task.Periodic
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
expire := time.Now().Add(-1 * time.Second * 60)
|
|
|
|
for p, e := range r.cache {
|
|
|
|
if e.timestamp.Before(expire) {
|
|
|
|
delete(r.cache, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *CachedMatcherGroup) Match(pattern string) uint32 {
|
|
|
|
g.Lock()
|
|
|
|
defer g.Unlock()
|
|
|
|
|
|
|
|
r, f := g.cache[pattern]
|
|
|
|
if f {
|
|
|
|
r.timestamp = time.Now()
|
|
|
|
g.cache[pattern] = r
|
|
|
|
return r.result
|
|
|
|
}
|
|
|
|
|
|
|
|
mr := g.group.Match(pattern)
|
|
|
|
|
|
|
|
g.cache[pattern] = cacheEntry{
|
|
|
|
result: mr,
|
|
|
|
timestamp: time.Now(),
|
|
|
|
}
|
|
|
|
|
|
|
|
return mr
|
|
|
|
}
|