package matcher import ( "net" "strings" "github.com/gobwas/glob" ) // Matcher is a generic pattern matcher, // it gives the match result of the given pattern for specific v. type Matcher interface { Match(v string) bool } type ipMatcher struct { ips map[string]struct{} } // IPMatcher creates a Matcher with a list of IP addresses. func IPMatcher(ips []net.IP) Matcher { matcher := &ipMatcher{ ips: make(map[string]struct{}), } for _, ip := range ips { matcher.ips[ip.String()] = struct{}{} } return matcher } func (m *ipMatcher) Match(ip string) bool { if m == nil || len(m.ips) == 0 { return false } _, ok := m.ips[ip] return ok } type cidrMatcher struct { inets []*net.IPNet } // CIDRMatcher creates a Matcher for a list of CIDR notation IP addresses. func CIDRMatcher(inets []*net.IPNet) Matcher { return &cidrMatcher{ inets: inets, } } func (m *cidrMatcher) Match(ip string) bool { if m == nil || len(m.inets) == 0 { return false } for _, inet := range m.inets { if inet.Contains(net.ParseIP(ip)) { return true } } return false } type domainMatcher struct { domains map[string]struct{} } // DomainMatcher creates a Matcher for a list of domains, // the domain should be a plain domain such as 'example.com', // or a special pattern '.example.com' that matches 'example.com' // and any subdomain 'abc.example.com', 'def.abc.example.com' etc. func DomainMatcher(domains []string) Matcher { matcher := &domainMatcher{ domains: make(map[string]struct{}), } for _, domain := range domains { matcher.domains[domain] = struct{}{} } return matcher } func (m *domainMatcher) Match(domain string) bool { if m == nil || len(m.domains) == 0 { return false } if _, ok := m.domains[domain]; ok { return true } if _, ok := m.domains["."+domain]; ok { return true } for { if index := strings.IndexByte(domain, '.'); index > 0 { if _, ok := m.domains[domain[index:]]; ok { return true } domain = domain[index+1:] continue } break } return false } type wildcardMatcherPattern struct { pattern string glob glob.Glob } type wildcardMatcher struct { patterns []wildcardMatcherPattern } // WildcardMatcher creates a Matcher for a specific wildcard domain pattern, // the pattern should be a wildcard such as '*.exmaple.com'. func WildcardMatcher(patterns []string) Matcher { matcher := &wildcardMatcher{} for _, pattern := range patterns { matcher.patterns = append(matcher.patterns, wildcardMatcherPattern{ pattern: pattern, glob: glob.MustCompile(pattern), }) } return matcher } func (m *wildcardMatcher) Match(domain string) bool { if m == nil || len(m.patterns) == 0 { return false } for _, pattern := range m.patterns { if pattern.glob.Match(domain) { return true } } return false }