dnscrypt-proxy/dnscrypt-proxy/pattern_matcher.go

162 lines
4.4 KiB
Go

package main
import (
"fmt"
"path/filepath"
"strings"
"github.com/k-sone/critbitgo"
"github.com/jedisct1/dlog"
)
type PatternType int
const (
PatternTypeNone PatternType = iota
PatternTypePrefix
PatternTypeSuffix
PatternTypeSubstring
PatternTypePattern
PatternTypeExact
)
type PatternMatcher struct {
blockedPrefixes *critbitgo.Trie
blockedSuffixes *critbitgo.Trie
blockedSubstrings []string
blockedPatterns []string
blockedExact map[string]interface{}
indirectVals map[string]interface{}
}
func NewPatternMatcher() *PatternMatcher {
patternMatcher := PatternMatcher{
blockedPrefixes: critbitgo.NewTrie(),
blockedSuffixes: critbitgo.NewTrie(),
blockedExact: make(map[string]interface{}),
indirectVals: make(map[string]interface{}),
}
return &patternMatcher
}
func isGlobCandidate(str string) bool {
for i, c := range str {
if c == '?' || c == '[' {
return true
} else if c == '*' && i != 0 && i != len(str)-1 {
return true
}
}
return false
}
func (patternMatcher *PatternMatcher) Add(pattern string, val interface{}, position int) error {
leadingStar := strings.HasPrefix(pattern, "*")
trailingStar := strings.HasSuffix(pattern, "*")
exact := strings.HasPrefix(pattern, "=")
patternType := PatternTypeNone
if isGlobCandidate(pattern) {
patternType = PatternTypePattern
_, err := filepath.Match(pattern, "example.com")
if len(pattern) < 2 || err != nil {
return fmt.Errorf("Syntax error in block rules at pattern %d", position)
}
} else if leadingStar && trailingStar {
patternType = PatternTypeSubstring
if len(pattern) < 3 {
return fmt.Errorf("Syntax error in block rules at pattern %d", position)
}
pattern = pattern[1 : len(pattern)-1]
} else if trailingStar {
patternType = PatternTypePrefix
if len(pattern) < 2 {
return fmt.Errorf("Syntax error in block rules at pattern %d", position)
}
pattern = pattern[:len(pattern)-1]
} else if exact {
patternType = PatternTypeExact
if len(pattern) < 2 {
return fmt.Errorf("Syntax error in block rules at pattern %d", position)
}
pattern = pattern[1:]
} else {
patternType = PatternTypeSuffix
if leadingStar {
pattern = pattern[1:]
}
pattern = strings.TrimPrefix(pattern, ".")
}
if len(pattern) == 0 {
dlog.Errorf("Syntax error in block rule at line %d", position)
}
pattern = strings.ToLower(pattern)
switch patternType {
case PatternTypeSubstring:
patternMatcher.blockedSubstrings = append(patternMatcher.blockedSubstrings, pattern)
if val != nil {
patternMatcher.indirectVals[pattern] = val
}
case PatternTypePattern:
patternMatcher.blockedPatterns = append(patternMatcher.blockedPatterns, pattern)
if val != nil {
patternMatcher.indirectVals[pattern] = val
}
case PatternTypePrefix:
patternMatcher.blockedPrefixes.Insert([]byte(pattern), val)
case PatternTypeSuffix:
patternMatcher.blockedSuffixes.Insert([]byte(StringReverse(pattern)), val)
case PatternTypeExact:
patternMatcher.blockedExact[pattern] = val
default:
dlog.Fatal("Unexpected block type")
}
return nil
}
func (patternMatcher *PatternMatcher) Eval(qName string) (reject bool, reason string, val interface{}) {
if len(qName) < 2 {
return false, "", nil
}
if xval := patternMatcher.blockedExact[qName]; xval != nil {
return true, qName, xval
}
revQname := StringReverse(qName)
if match, xval, found := patternMatcher.blockedSuffixes.LongestPrefix([]byte(revQname)); found {
if len(match) == len(revQname) || revQname[len(match)] == '.' {
return true, "*." + StringReverse(string(match)), xval
}
if len(match) < len(revQname) && len(revQname) > 0 {
if i := strings.LastIndex(revQname, "."); i > 0 {
pName := revQname[:i]
if match, _, found := patternMatcher.blockedSuffixes.LongestPrefix([]byte(pName)); found {
if len(match) == len(pName) || pName[len(match)] == '.' {
return true, "*." + StringReverse(string(match)), xval
}
}
}
}
}
if match, xval, found := patternMatcher.blockedPrefixes.LongestPrefix([]byte(qName)); found {
return true, string(match) + "*", xval
}
for _, substring := range patternMatcher.blockedSubstrings {
if strings.Contains(qName, substring) {
return true, "*" + substring + "*", patternMatcher.indirectVals[substring]
}
}
for _, pattern := range patternMatcher.blockedPatterns {
if found, _ := filepath.Match(pattern, qName); found {
return true, pattern, patternMatcher.indirectVals[pattern]
}
}
return false, "", nil
}