Merge pull request #443 from AdguardTeam/fix/sync_with_bitbucket
Fix/sync with bitbucket
This commit is contained in:
commit
30f3eb446c
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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} />}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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} />}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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, it’s 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;
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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++
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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("", "")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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{
|
||||||
"##",
|
"##",
|
||||||
"#@#",
|
"#@#",
|
||||||
|
"#?#",
|
||||||
|
"#@?#",
|
||||||
"#$#",
|
"#$#",
|
||||||
"#@$#",
|
"#@$#",
|
||||||
|
"#?$#",
|
||||||
|
"#@?$#",
|
||||||
"$$",
|
"$$",
|
||||||
"$@$",
|
"$@$",
|
||||||
"#%#",
|
"#%#",
|
||||||
|
|
Loading…
Reference in New Issue