Merge pull request #443 from AdguardTeam/fix/sync_with_bitbucket

Fix/sync with bitbucket
This commit is contained in:
Andrey Meshkov 2018-10-29 13:50:41 +00:00 committed by GitHub
commit 30f3eb446c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 254 additions and 124 deletions

5
.gitignore vendored
View File

@ -1,5 +1,6 @@
.DS_Store .DS_Store
.vscode .vscode
.idea
debug debug
/AdGuardHome /AdGuardHome
/AdGuardHome.yaml /AdGuardHome.yaml
@ -10,3 +11,7 @@ debug
/dnsfilter.txt /dnsfilter.txt
/querylog.json /querylog.json
/querylog.json.1 /querylog.json.1
# Test output
dnsfilter/dnsfilter.TestLotsOfRules*.pprof
tests/top-1m.csv

11
client/package-lock.json generated vendored
View File

@ -6551,7 +6551,7 @@
}, },
"html-webpack-plugin": { "html-webpack-plugin": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
"integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -6601,7 +6601,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "1.0.34", "version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true, "dev": true,
"requires": { "requires": {
@ -14930,7 +14930,7 @@
}, },
"through": { "through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true "dev": true
}, },
@ -14965,11 +14965,6 @@
"setimmediate": "^1.0.4" "setimmediate": "^1.0.4"
} }
}, },
"tiny-version-compare": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/tiny-version-compare/-/tiny-version-compare-0.9.1.tgz",
"integrity": "sha512-kYim94l7ptSmj9rqxUMkrcMCJ448CS+hwqjA7OFcRi0ISdi0zjgdSUklQ4velVVECCjCo5frU3tNZ3oSgIKzsA=="
},
"tmp": { "tmp": {
"version": "0.0.33", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

1
client/package.json vendored
View File

@ -30,7 +30,6 @@
"redux-actions": "^2.4.0", "redux-actions": "^2.4.0",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"svg-url-loader": "^2.3.2", "svg-url-loader": "^2.3.2",
"tiny-version-compare": "^0.9.1",
"whatwg-fetch": "2.0.3" "whatwg-fetch": "2.0.3"
}, },
"devDependencies": { "devDependencies": {

View File

@ -20,8 +20,8 @@ class BlockedDomains extends Component {
const trackerData = getTrackerData(value); const trackerData = getTrackerData(value);
return ( return (
<div className="logs__row" title={value}> <div className="logs__row">
<div className="logs__text"> <div className="logs__text" title={value}>
{value} {value}
</div> </div>
{trackerData && <Popover data={trackerData} />} {trackerData && <Popover data={trackerData} />}

View File

@ -23,6 +23,7 @@ class Clients extends Component {
Header: 'IP', Header: 'IP',
accessor: 'ip', accessor: 'ip',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>), Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
}, { }, {
Header: 'Requests count', Header: 'Requests count',
accessor: 'count', accessor: 'count',

View File

@ -29,8 +29,8 @@ class QueriedDomains extends Component {
const trackerData = getTrackerData(value); const trackerData = getTrackerData(value);
return ( return (
<div className="logs__row" title={value}> <div className="logs__row">
<div className="logs__text"> <div className="logs__text" title={value}>
{value} {value}
</div> </div>
{trackerData && <Popover data={trackerData} />} {trackerData && <Popover data={trackerData} />}

View File

@ -19,11 +19,11 @@ const Line = props => (
curve='linear' curve='linear'
axisBottom={{ axisBottom={{
tickSize: 0, tickSize: 0,
tickPadding: 0, tickPadding: 10,
}} }}
axisLeft={{ axisLeft={{
tickSize: 0, tickSize: 0,
tickPadding: 0, tickPadding: 10,
}} }}
enableGridX={false} enableGridX={false}
enableGridY={false} enableGridY={false}

View File

@ -61,6 +61,11 @@
"name": "Branch.io", "name": "Branch.io",
"categoryId": 101, "categoryId": 101,
"url": "https://branch.io/" "url": "https://branch.io/"
},
"adocean": {
"name": "Gemius Adocean",
"categoryId": 4,
"url": "https://adocean-global.com/"
} }
}, },
"trackerDomains": { "trackerDomains": {
@ -72,6 +77,7 @@
"appsflyer.com": "appsflyer", "appsflyer.com": "appsflyer",
"appmetrica.yandex.com": "yandex_appmetrica", "appmetrica.yandex.com": "yandex_appmetrica",
"adjust.com": "adjust", "adjust.com": "adjust",
"mobileapptracking.com": "branch" "mobileapptracking.com": "branch",
"adocean.cz": "adocean"
} }
} }

View File

@ -0,0 +1,63 @@
/*
* Project: tiny-version-compare https://github.com/bfred-it/tiny-version-compare
* License (MIT) https://github.com/bfred-it/tiny-version-compare/blob/master/LICENSE
*/
const split = v => String(v).replace(/^[vr]/, '') // Drop initial 'v' or 'r'
.replace(/([a-z]+)/gi, '.$1.') // Sort each word separately
.replace(/[-.]+/g, '.') // Consider dashes as separators (+ trim multiple separators)
.split('.');
// Development versions are considered "negative",
// but localeCompare doesn't handle negative numbers.
// This offset is applied to reset the lowest development version to 0
const offset = (part) => {
// Not numeric, return as is
if (Number.isNaN(part)) {
return part;
}
return 5 + Number(part);
};
const parsePart = (part) => {
// Missing, consider it zero
if (typeof part === 'undefined') {
return 0;
}
// Sort development versions
switch (part.toLowerCase()) {
case 'dev':
return -5;
case 'alpha':
return -4;
case 'beta':
return -3;
case 'rc':
return -2;
case 'pre':
return -1;
default:
}
// Return as is, its either a plain number or text that will be sorted alphabetically
return part;
};
const versionCompare = (prev, next) => {
const a = split(prev);
const b = split(next);
for (let i = 0; i < a.length || i < b.length; i += 1) {
const ai = offset(parsePart(a[i]));
const bi = offset(parsePart(b[i]));
const sort = String(ai).localeCompare(bi, 'en', {
numeric: true,
});
// Once the difference is found,
// stop comparing the rest of the parts
if (sort !== 0) {
return sort;
}
}
// No difference found
return 0;
};
export default versionCompare;

View File

@ -1,8 +1,8 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { handleActions } from 'redux-actions'; import { handleActions } from 'redux-actions';
import { loadingBarReducer } from 'react-redux-loading-bar'; import { loadingBarReducer } from 'react-redux-loading-bar';
import versionCompare from 'tiny-version-compare';
import nanoid from 'nanoid'; import nanoid from 'nanoid';
import versionCompare from '../helpers/versionCompare';
import * as actions from '../actions'; import * as actions from '../actions';

View File

@ -731,12 +731,13 @@ func (filter *filter) update(now time.Time) (bool, error) {
} }
} else if len(line) != 0 { } else if len(line) != 0 {
err = d.AddRule(line, 0) err = d.AddRule(line, 0)
if err == dnsfilter.ErrInvalidSyntax { if err == dnsfilter.ErrAlreadyExists || err == dnsfilter.ErrInvalidSyntax {
continue continue
} }
if err != nil { if err != nil {
log.Printf("Couldn't add rule %s: %s", filter.URL, err) log.Printf("Cannot add rule %s from %s: %s", line, filter.URL, err)
return false, err // Just ignore invalid rules
continue
} }
rulesCount++ rulesCount++
} }

View File

@ -62,7 +62,6 @@ type plug struct {
d *dnsfilter.Dnsfilter d *dnsfilter.Dnsfilter
Next plugin.Handler Next plugin.Handler
upstream upstream.Upstream upstream upstream.Upstream
hosts map[string]net.IP
settings plugSettings settings plugSettings
sync.RWMutex sync.RWMutex
@ -82,7 +81,6 @@ func setupPlugin(c *caddy.Controller) (*plug, error) {
p := &plug{ p := &plug{
settings: defaultPluginSettings, settings: defaultPluginSettings,
d: dnsfilter.New(), d: dnsfilter.New(),
hosts: make(map[string]net.IP),
} }
filterFileNames := []string{} filterFileNames := []string{}
@ -149,15 +147,15 @@ func setupPlugin(c *caddy.Controller) (*plug, error) {
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
if p.parseEtcHosts(text) {
continue
}
err = p.d.AddRule(text, uint32(i)) err = p.d.AddRule(text, uint32(i))
if err == dnsfilter.ErrInvalidSyntax { if err == dnsfilter.ErrAlreadyExists || err == dnsfilter.ErrInvalidSyntax {
continue continue
} }
if err != nil { if err != nil {
return nil, err log.Printf("Cannot add rule %s: %s", text, err)
// Just ignore invalid rules
continue
} }
count++ count++
} }
@ -231,28 +229,6 @@ func setup(c *caddy.Controller) error {
return nil return nil
} }
func (p *plug) parseEtcHosts(text string) bool {
if pos := strings.IndexByte(text, '#'); pos != -1 {
text = text[0:pos]
}
fields := strings.Fields(text)
if len(fields) < 2 {
return false
}
addr := net.ParseIP(fields[0])
if addr == nil {
return false
}
for _, host := range fields[1:] {
// debug logging for duplicate values, pretty common if you subscribe to many hosts files
// if val, ok := p.hosts[host]; ok {
// log.Printf("warning: host %s already has value %s, will overwrite it with %s", host, val, addr)
// }
p.hosts[host] = addr
}
return true
}
func (p *plug) onShutdown() error { func (p *plug) onShutdown() error {
p.Lock() p.Lock()
p.d.Destroy() p.d.Destroy()
@ -432,27 +408,6 @@ func (p *plug) serveDNSInternal(ctx context.Context, w dns.ResponseWriter, r *dn
} }
p.RUnlock() p.RUnlock()
// is it in hosts?
if val, ok := p.hosts[host]; ok {
// it is, if it's a loopback host, reply with NXDOMAIN
// TODO: research if it's better than 127.0.0.1
if false && val.IsLoopback() {
rcode, err := p.writeNXdomain(ctx, w, r)
if err != nil {
return rcode, dnsfilter.Result{}, err
}
return rcode, dnsfilter.Result{Reason: dnsfilter.FilteredInvalid}, err
}
// it's not a loopback host, replace it with value specified
rcode, err := p.replaceHostWithValAndReply(ctx, w, r, host, val.String(), question)
if err != nil {
return rcode, dnsfilter.Result{}, err
}
// TODO: This must be handled in the dnsfilter and not here!
rule := val.String() + " " + host
return rcode, dnsfilter.Result{Reason: dnsfilter.FilteredBlackList, Rule: rule}, err
}
// needs to be filtered instead // needs to be filtered instead
p.RLock() p.RLock()
result, err := p.d.CheckHost(host) result, err := p.d.CheckHost(host)
@ -482,12 +437,22 @@ func (p *plug) serveDNSInternal(ctx context.Context, w dns.ResponseWriter, r *dn
} }
return rcode, result, err return rcode, result, err
case dnsfilter.FilteredBlackList: case dnsfilter.FilteredBlackList:
// return NXdomain
rcode, err := p.writeNXdomain(ctx, w, r) if result.Ip == nil {
if err != nil { // return NXDomain
return rcode, dnsfilter.Result{}, err rcode, err := p.writeNXdomain(ctx, w, r)
if err != nil {
return rcode, dnsfilter.Result{}, err
}
return rcode, result, err
} else {
// This is a hosts-syntax rule
rcode, err := p.replaceHostWithValAndReply(ctx, w, r, host, result.Ip.String(), question)
if err != nil {
return rcode, dnsfilter.Result{}, err
}
return rcode, result, err
} }
return rcode, result, err
case dnsfilter.FilteredInvalid: case dnsfilter.FilteredInvalid:
// return NXdomain // return NXdomain
rcode, err := p.writeNXdomain(ctx, w, r) rcode, err := p.writeNXdomain(ctx, w, r)

View File

@ -40,41 +40,6 @@ func TestSetup(t *testing.T) {
} }
} }
func TestEtcHostsParse(t *testing.T) {
addr := "216.239.38.120"
text := []byte(fmt.Sprintf(" %s google.com www.google.com # enforce google's safesearch ", addr))
tmpfile, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
if _, err = tmpfile.Write(text); err != nil {
t.Fatal(err)
}
if err = tmpfile.Close(); err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
c := caddy.NewTestController("dns", fmt.Sprintf("dnsfilter %s", tmpfile.Name()))
p, err := setupPlugin(c)
if err != nil {
t.Fatal(err)
}
if len(p.hosts) != 2 {
t.Fatal("Expected p.hosts to have two keys")
}
val, ok := p.hosts["google.com"]
if !ok {
t.Fatal("Expected google.com to be set in p.hosts")
}
if !val.Equal(net.ParseIP(addr)) {
t.Fatalf("Expected google.com's value %s to match %s", val, addr)
}
}
func TestEtcHostsFilter(t *testing.T) { func TestEtcHostsFilter(t *testing.T) {
text := []byte("127.0.0.1 doubleclick.net\n" + "127.0.0.1 example.org example.net www.example.org www.example.net") text := []byte("127.0.0.1 doubleclick.net\n" + "127.0.0.1 example.org example.net www.example.org www.example.net")
tmpfile, err := ioutil.TempFile("", "") tmpfile, err := ioutil.TempFile("", "")

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"net/http" "net/http"
"regexp" "regexp"
"strings" "strings"
@ -32,9 +33,12 @@ const defaultSafebrowsingURL = "http://%s/safebrowsing-lookup-hash.html?prefixes
const defaultParentalServer = "pctrl.adguard.com" const defaultParentalServer = "pctrl.adguard.com"
const defaultParentalURL = "http://%s/check-parental-control-hash?prefixes=%s&sensitivity=%d" const defaultParentalURL = "http://%s/check-parental-control-hash?prefixes=%s&sensitivity=%d"
// ErrInvalidSyntax is returned by AddRule when rule is invalid // ErrInvalidSyntax is returned by AddRule when the rule is invalid
var ErrInvalidSyntax = errors.New("dnsfilter: invalid rule syntax") var ErrInvalidSyntax = errors.New("dnsfilter: invalid rule syntax")
// ErrInvalidSyntax is returned by AddRule when the rule was already added to the filter
var ErrAlreadyExists = errors.New("dnsfilter: rule was already added")
// ErrInvalidParental is returned by EnableParental when sensitivity is not a valid value // ErrInvalidParental is returned by EnableParental when sensitivity is not a valid value
var ErrInvalidParental = errors.New("dnsfilter: invalid parental sensitivity, must be either 3, 10, 13 or 17") var ErrInvalidParental = errors.New("dnsfilter: invalid parental sensitivity, must be either 3, 10, 13 or 17")
@ -56,6 +60,7 @@ type rule struct {
text string // text without @@ decorators or $ options text string // text without @@ decorators or $ options
shortcut string // for speeding up lookup shortcut string // for speeding up lookup
originalText string // original text for reporting back to applications originalText string // original text for reporting back to applications
ip net.IP // IP address (for the case when we're matching a hosts file)
// options // options
options []string // optional options after $ options []string // optional options after $
@ -94,7 +99,7 @@ type Stats struct {
// Dnsfilter holds added rules and performs hostname matches against the rules // Dnsfilter holds added rules and performs hostname matches against the rules
type Dnsfilter struct { type Dnsfilter struct {
storage map[string]*rule // rule storage, not used for matching, needs to be key->value storage map[string]bool // rule storage, not used for matching, just for filtering out duplicates
storageMutex sync.RWMutex storageMutex sync.RWMutex
// rules are checked against these lists in the order defined here // rules are checked against these lists in the order defined here
@ -137,9 +142,11 @@ var (
// Result holds state of hostname check // Result holds state of hostname check
type Result struct { type Result struct {
IsFiltered bool `json:",omitempty"` IsFiltered bool `json:",omitempty"` // True if the host name is filtered
Reason Reason `json:",omitempty"` Reason Reason `json:",omitempty"` // Reason for blocking / unblocking
Rule string `json:",omitempty"` Rule string `json:",omitempty"` // Original rule text
Ip net.IP `json:",omitempty"` // Not nil only in the case of a hosts file syntax
FilterID uint32 `json:",omitempty"` // Filter ID the rule belongs to
} }
// Matched can be used to see if any match at all was found, no matter filtered or not // Matched can be used to see if any match at all was found, no matter filtered or not
@ -199,6 +206,7 @@ func (d *Dnsfilter) CheckHost(host string) (Result, error) {
// //
type rulesTable struct { type rulesTable struct {
rulesByHost map[string]*rule
rulesByShortcut map[string][]*rule rulesByShortcut map[string][]*rule
rulesLeftovers []*rule rulesLeftovers []*rule
sync.RWMutex sync.RWMutex
@ -206,6 +214,7 @@ type rulesTable struct {
func newRulesTable() *rulesTable { func newRulesTable() *rulesTable {
return &rulesTable{ return &rulesTable{
rulesByHost: make(map[string]*rule),
rulesByShortcut: make(map[string][]*rule), rulesByShortcut: make(map[string][]*rule),
rulesLeftovers: make([]*rule, 0), rulesLeftovers: make([]*rule, 0),
} }
@ -213,16 +222,27 @@ func newRulesTable() *rulesTable {
func (r *rulesTable) Add(rule *rule) { func (r *rulesTable) Add(rule *rule) {
r.Lock() r.Lock()
if len(rule.shortcut) == shortcutLength && enableFastLookup {
if rule.ip != nil {
// Hosts syntax
r.rulesByHost[rule.text] = rule
} else if //noinspection GoBoolExpressions
len(rule.shortcut) == shortcutLength && enableFastLookup {
// Adblock syntax with a shortcut
r.rulesByShortcut[rule.shortcut] = append(r.rulesByShortcut[rule.shortcut], rule) r.rulesByShortcut[rule.shortcut] = append(r.rulesByShortcut[rule.shortcut], rule)
} else { } else {
// Adblock syntax -- too short to have a shortcut
r.rulesLeftovers = append(r.rulesLeftovers, rule) r.rulesLeftovers = append(r.rulesLeftovers, rule)
} }
r.Unlock() r.Unlock()
} }
func (r *rulesTable) matchByHost(host string) (Result, error) { func (r *rulesTable) matchByHost(host string) (Result, error) {
res, err := r.searchShortcuts(host)
// First: examine the hosts-syntax rules
res, err := r.searchByHost(host)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -230,6 +250,16 @@ func (r *rulesTable) matchByHost(host string) (Result, error) {
return res, nil return res, nil
} }
// Second: examine the adblock-syntax rules with shortcuts
res, err = r.searchShortcuts(host)
if err != nil {
return res, err
}
if res.Reason.Matched() {
return res, nil
}
// Third: examine the others
res, err = r.searchLeftovers(host) res, err = r.searchLeftovers(host)
if err != nil { if err != nil {
return res, err return res, err
@ -241,6 +271,17 @@ func (r *rulesTable) matchByHost(host string) (Result, error) {
return Result{}, nil return Result{}, nil
} }
func (r *rulesTable) searchByHost(host string) (Result, error) {
rule, ok := r.rulesByHost[host]
if ok {
return rule.match(host)
}
return Result{}, nil
}
func (r *rulesTable) searchShortcuts(host string) (Result, error) { func (r *rulesTable) searchShortcuts(host string) (Result, error) {
// check in shortcuts first // check in shortcuts first
for i := 0; i < len(host); i++ { for i := 0; i < len(host); i++ {
@ -424,8 +465,21 @@ func (rule *rule) compile() error {
return nil return nil
} }
// Checks if the rule matches the specified host and returns a corresponding Result object
func (rule *rule) match(host string) (Result, error) { func (rule *rule) match(host string) (Result, error) {
res := Result{} res := Result{}
if rule.ip != nil && rule.text == host {
// This is a hosts-syntax rule -- just check that the hostname matches and return the result
return Result{
IsFiltered: true,
Reason: FilteredBlackList,
Rule: rule.originalText,
Ip: rule.ip,
FilterID: rule.listID,
}, nil
}
err := rule.compile() err := rule.compile()
if err != nil { if err != nil {
return res, err return res, err
@ -686,20 +740,27 @@ func (d *Dnsfilter) AddRule(input string, filterListID uint32) error {
d.storageMutex.RUnlock() d.storageMutex.RUnlock()
if exists { if exists {
// already added // already added
return ErrInvalidSyntax return ErrAlreadyExists
} }
if !isValidRule(input) { if !isValidRule(input) {
return ErrInvalidSyntax return ErrInvalidSyntax
} }
// First, check if this is a hosts-syntax rule
if d.parseEtcHosts(input, filterListID) {
// This is a valid hosts-syntax rule, no need for further parsing
return nil
}
// Start parsing the rule
rule := rule{ rule := rule{
text: input, // will be modified text: input, // will be modified
originalText: input, originalText: input,
listID: filterListID, listID: filterListID,
} }
// mark rule as whitelist if it starts with @@ // Mark rule as whitelist if it starts with @@
if strings.HasPrefix(rule.text, "@@") { if strings.HasPrefix(rule.text, "@@") {
rule.isWhitelist = true rule.isWhitelist = true
rule.text = rule.text[2:] rule.text = rule.text[2:]
@ -712,6 +773,7 @@ func (d *Dnsfilter) AddRule(input string, filterListID uint32) error {
rule.extractShortcut() rule.extractShortcut()
//noinspection GoBoolExpressions
if !enableDelayedCompilation { if !enableDelayedCompilation {
err := rule.compile() err := rule.compile()
if err != nil { if err != nil {
@ -727,12 +789,44 @@ func (d *Dnsfilter) AddRule(input string, filterListID uint32) error {
} }
d.storageMutex.Lock() d.storageMutex.Lock()
d.storage[input] = &rule d.storage[input] = true
d.storageMutex.Unlock() d.storageMutex.Unlock()
destination.Add(&rule) destination.Add(&rule)
return nil return nil
} }
// Parses the hosts-syntax rules. Returns false if the input string is not of hosts-syntax.
func (d *Dnsfilter) parseEtcHosts(input string, filterListID uint32) bool {
// Strip the trailing comment
ruleText := input
if pos := strings.IndexByte(ruleText, '#'); pos != -1 {
ruleText = ruleText[0:pos]
}
fields := strings.Fields(ruleText)
if len(fields) < 2 {
return false
}
addr := net.ParseIP(fields[0])
if addr == nil {
return false
}
d.storageMutex.Lock()
d.storage[input] = true
d.storageMutex.Unlock()
for _, host := range fields[1:] {
rule := rule{
text: host,
originalText: input,
listID: filterListID,
ip: addr,
}
d.blackList.Add(&rule)
}
return true
}
// matchHost is a low-level way to check only if hostname is filtered by rules, skipping expensive safebrowsing and parental lookups // matchHost is a low-level way to check only if hostname is filtered by rules, skipping expensive safebrowsing and parental lookups
func (d *Dnsfilter) matchHost(host string) (Result, error) { func (d *Dnsfilter) matchHost(host string) (Result, error) {
lists := []*rulesTable{ lists := []*rulesTable{
@ -761,7 +855,7 @@ func (d *Dnsfilter) matchHost(host string) (Result, error) {
func New() *Dnsfilter { func New() *Dnsfilter {
d := new(Dnsfilter) d := new(Dnsfilter)
d.storage = make(map[string]*rule) d.storage = make(map[string]bool)
d.important = newRulesTable() d.important = newRulesTable()
d.whiteList = newRulesTable() d.whiteList = newRulesTable()
d.blackList = newRulesTable() d.blackList = newRulesTable()

View File

@ -261,7 +261,7 @@ func (d *Dnsfilter) checkAddRule(t *testing.T, rule string) {
func (d *Dnsfilter) checkAddRuleFail(t *testing.T, rule string) { func (d *Dnsfilter) checkAddRuleFail(t *testing.T, rule string) {
t.Helper() t.Helper()
err := d.AddRule(rule, 0) err := d.AddRule(rule, 0)
if err == ErrInvalidSyntax { if err == ErrInvalidSyntax || err == ErrAlreadyExists {
return return
} }
if err != nil { if err != nil {
@ -281,6 +281,20 @@ func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) {
} }
} }
func (d *Dnsfilter) checkMatchIp(t *testing.T, hostname string, ip string) {
t.Helper()
ret, err := d.CheckHost(hostname)
if err != nil {
t.Errorf("Error while matching host %s: %s", hostname, err)
}
if !ret.IsFiltered {
t.Errorf("Expected hostname %s to match", hostname)
}
if ret.Ip == nil || ret.Ip.String() != ip {
t.Errorf("Expected ip %s to match, actual: %v", ip, ret.Ip)
}
}
func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) { func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) {
t.Helper() t.Helper()
ret, err := d.CheckHost(hostname) ret, err := d.CheckHost(hostname)
@ -304,7 +318,7 @@ func loadTestRules(d *Dnsfilter) error {
for scanner.Scan() { for scanner.Scan() {
rule := scanner.Text() rule := scanner.Text()
err = d.AddRule(rule, 0) err = d.AddRule(rule, 0)
if err == ErrInvalidSyntax { if err == ErrInvalidSyntax || err == ErrAlreadyExists {
continue continue
} }
if err != nil { if err != nil {
@ -345,6 +359,20 @@ func TestSanityCheck(t *testing.T) {
d.checkAddRuleFail(t, "lkfaojewhoawehfwacoefawr$@#$@3413841384") d.checkAddRuleFail(t, "lkfaojewhoawehfwacoefawr$@#$@3413841384")
} }
func TestEtcHostsMatching(t *testing.T) {
d := NewForTest()
defer d.Destroy()
addr := "216.239.38.120"
text := fmt.Sprintf(" %s google.com www.google.com # enforce google's safesearch ", addr)
d.checkAddRule(t, text)
d.checkMatchIp(t, "google.com", addr)
d.checkMatchIp(t, "www.google.com", addr)
d.checkMatchEmpty(t, "subdomain.google.com")
d.checkMatchEmpty(t, "example.org")
}
func TestSuffixMatching1(t *testing.T) { func TestSuffixMatching1(t *testing.T) {
d := NewForTest() d := NewForTest()
defer d.Destroy() defer d.Destroy()
@ -696,6 +724,7 @@ func BenchmarkAddRule(b *testing.B) {
err := d.AddRule(rule, 0) err := d.AddRule(rule, 0)
switch err { switch err {
case nil: case nil:
case ErrAlreadyExists: // ignore rules which were already added
case ErrInvalidSyntax: // ignore invalid syntax case ErrInvalidSyntax: // ignore invalid syntax
default: default:
b.Fatalf("Error while adding rule %s: %s", rule, err) b.Fatalf("Error while adding rule %s: %s", rule, err)
@ -715,6 +744,7 @@ func BenchmarkAddRuleParallel(b *testing.B) {
} }
switch err { switch err {
case nil: case nil:
case ErrAlreadyExists: // ignore rules which were already added
case ErrInvalidSyntax: // ignore invalid syntax case ErrInvalidSyntax: // ignore invalid syntax
default: default:
b.Fatalf("Error while adding rule %s: %s", rule, err) b.Fatalf("Error while adding rule %s: %s", rule, err)

View File

@ -19,11 +19,17 @@ func isValidRule(rule string) bool {
return false return false
} }
// Filter out all sorts of cosmetic rules:
// https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#cosmetic-rules
masks := []string{ masks := []string{
"##", "##",
"#@#", "#@#",
"#?#",
"#@?#",
"#$#", "#$#",
"#@$#", "#@$#",
"#?$#",
"#@?$#",
"$$", "$$",
"$@$", "$@$",
"#%#", "#%#",