diff --git a/dnsfilter/dnsfilter.go b/dnsfilter/dnsfilter.go
index 0e7338ef..80b59fc5 100644
--- a/dnsfilter/dnsfilter.go
+++ b/dnsfilter/dnsfilter.go
@@ -11,9 +11,7 @@ import (
 	"io/ioutil"
 	"net"
 	"net/http"
-	"regexp"
 	"strings"
-	"sync"
 	"sync/atomic"
 	"time"
 
@@ -60,33 +58,6 @@ type privateConfig struct {
 	safeBrowsingServer string // access via methods
 }
 
-type rule struct {
-	text         string // text without @@ decorators or $ options
-	shortcut     string // for speeding up lookup
-	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 []string // optional options after $
-
-	// parsed options
-	apps        []string
-	isWhitelist bool
-	isImportant bool
-
-	// user-supplied data
-	listID int64
-
-	// suffix matching
-	isSuffix bool
-	suffix   string
-
-	// compiled regexp
-	compiled *regexp.Regexp
-
-	sync.RWMutex
-}
-
 // LookupStats store stats collected during safebrowsing or parental checks
 type LookupStats struct {
 	Requests   uint64 // number of HTTP requests that were sent
@@ -104,14 +75,6 @@ type Stats struct {
 
 // Dnsfilter holds added rules and performs hostname matches against the rules
 type Dnsfilter struct {
-	storage      map[string]bool // rule storage, not used for matching, just for filtering out duplicates
-	storageMutex sync.RWMutex
-
-	// rules are checked against these lists in the order defined here
-	important *rulesTable // more important than whitelist and is checked first
-	whiteList *rulesTable // more important than blacklist
-	blackList *rulesTable
-
 	// HTTP lookups for safebrowsing and parental
 	client    http.Client     // handle for http client -- single instance as recommended by docs
 	transport *http.Transport // handle for http transport used by http client
@@ -242,308 +205,6 @@ func (d *Dnsfilter) CheckHost(host string) (Result, error) {
 	return Result{}, nil
 }
 
-//
-// rules table
-//
-
-type rulesTable struct {
-	rulesByHost     map[string]*rule
-	rulesByShortcut map[string][]*rule
-	rulesLeftovers  []*rule
-	sync.RWMutex
-}
-
-func newRulesTable() *rulesTable {
-	return &rulesTable{
-		rulesByHost:     make(map[string]*rule),
-		rulesByShortcut: make(map[string][]*rule),
-		rulesLeftovers:  make([]*rule, 0),
-	}
-}
-
-func (r *rulesTable) Add(rule *rule) {
-	r.Lock()
-	if rule.ip != nil {
-		// Hosts syntax
-		r.rulesByHost[rule.text] = rule
-	} else if len(rule.shortcut) == shortcutLength && enableFastLookup {
-		// Adblock syntax with a shortcut
-		r.rulesByShortcut[rule.shortcut] = append(r.rulesByShortcut[rule.shortcut], rule)
-	} else {
-		// Adblock syntax -- too short to have a shortcut
-		r.rulesLeftovers = append(r.rulesLeftovers, rule)
-	}
-	r.Unlock()
-}
-
-func (r *rulesTable) matchByHost(host string) (Result, error) {
-	// First: examine the hosts-syntax rules
-	res, err := r.searchByHost(host)
-	if err != nil {
-		return res, err
-	}
-	if res.Reason.Matched() {
-		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)
-	if err != nil {
-		return res, err
-	}
-	if res.Reason.Matched() {
-		return res, 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) {
-	// check in shortcuts first
-	for i := 0; i < len(host); i++ {
-		shortcut := host[i:]
-		if len(shortcut) > shortcutLength {
-			shortcut = shortcut[:shortcutLength]
-		}
-		if len(shortcut) != shortcutLength {
-			continue
-		}
-		rules, ok := r.rulesByShortcut[shortcut]
-		if !ok {
-			continue
-		}
-		for _, rule := range rules {
-			res, err := rule.match(host)
-			// error? stop search
-			if err != nil {
-				return res, err
-			}
-			// matched? stop search
-			if res.Reason.Matched() {
-				return res, err
-			}
-			// continue otherwise
-		}
-	}
-	return Result{}, nil
-}
-
-func (r *rulesTable) searchLeftovers(host string) (Result, error) {
-	for _, rule := range r.rulesLeftovers {
-		res, err := rule.match(host)
-		// error? stop search
-		if err != nil {
-			return res, err
-		}
-		// matched? stop search
-		if res.Reason.Matched() {
-			return res, err
-		}
-		// continue otherwise
-	}
-	return Result{}, nil
-}
-
-func findOptionIndex(text string) int {
-	for i, r := range text {
-		// ignore non-$
-		if r != '$' {
-			continue
-		}
-		// ignore `\$`
-		if i > 0 && text[i-1] == '\\' {
-			continue
-		}
-		// ignore `$/`
-		if i > len(text) && text[i+1] == '/' {
-			continue
-		}
-		return i + 1
-	}
-	return -1
-}
-
-func (rule *rule) extractOptions() error {
-	optIndex := findOptionIndex(rule.text)
-	if optIndex == 0 { // starts with $
-		return ErrInvalidSyntax
-	}
-	if optIndex == len(rule.text) { // ends with $
-		return ErrInvalidSyntax
-	}
-	if optIndex < 0 {
-		return nil
-	}
-
-	optionsStr := rule.text[optIndex:]
-	rule.text = rule.text[:optIndex-1] // remove options from text
-
-	begin := 0
-	i := 0
-	for i = 0; i < len(optionsStr); i++ {
-		switch optionsStr[i] {
-		case ',':
-			if i > 0 {
-				// it might be escaped, if so, ignore
-				if optionsStr[i-1] == '\\' {
-					break // from switch, not for loop
-				}
-			}
-			rule.options = append(rule.options, optionsStr[begin:i])
-			begin = i + 1
-		}
-	}
-	if begin != i {
-		// there's still an option remaining
-		rule.options = append(rule.options, optionsStr[begin:])
-	}
-
-	return nil
-}
-
-func (rule *rule) parseOptions() error {
-	err := rule.extractOptions()
-	if err != nil {
-		return err
-	}
-
-	for _, option := range rule.options {
-		switch {
-		case option == "important":
-			rule.isImportant = true
-		case strings.HasPrefix(option, "app="):
-			option = strings.TrimPrefix(option, "app=")
-			rule.apps = strings.Split(option, "|")
-		default:
-			return ErrInvalidSyntax
-		}
-	}
-
-	return nil
-}
-
-func (rule *rule) extractShortcut() {
-	// regex rules have no shortcuts
-	if rule.text[0] == '/' && rule.text[len(rule.text)-1] == '/' {
-		return
-	}
-
-	fields := strings.FieldsFunc(rule.text, func(r rune) bool {
-		switch r {
-		case '*', '^', '|':
-			return true
-		}
-		return false
-	})
-	longestField := ""
-	for _, field := range fields {
-		if len(field) > len(longestField) {
-			longestField = field
-		}
-	}
-	if len(longestField) > shortcutLength {
-		longestField = longestField[:shortcutLength]
-	}
-	rule.shortcut = strings.ToLower(longestField)
-}
-
-func (rule *rule) compile() error {
-	rule.RLock()
-	isCompiled := rule.isSuffix || rule.compiled != nil
-	rule.RUnlock()
-	if isCompiled {
-		return nil
-	}
-
-	isSuffix, suffix := getSuffix(rule.text)
-	if isSuffix {
-		rule.Lock()
-		rule.isSuffix = isSuffix
-		rule.suffix = suffix
-		rule.Unlock()
-		return nil
-	}
-
-	expr, err := ruleToRegexp(rule.text)
-	if err != nil {
-		return err
-	}
-
-	compiled, err := regexp.Compile(expr)
-	if err != nil {
-		return err
-	}
-
-	rule.Lock()
-	rule.compiled = compiled
-	rule.Unlock()
-
-	return nil
-}
-
-// Checks if the rule matches the specified host and returns a corresponding Result object
-func (rule *rule) match(host string) (Result, error) {
-	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()
-	if err != nil {
-		return res, err
-	}
-	rule.RLock()
-	matched := false
-	if rule.isSuffix {
-		if host == rule.suffix {
-			matched = true
-		} else if strings.HasSuffix(host, "."+rule.suffix) {
-			matched = true
-		}
-	} else {
-		matched = rule.compiled.MatchString(host)
-	}
-	rule.RUnlock()
-	if matched {
-		res.Reason = FilteredBlackList
-		res.IsFiltered = true
-		res.FilterID = rule.listID
-		res.Rule = rule.originalText
-		if rule.isWhitelist {
-			res.Reason = NotFilteredWhiteList
-			res.IsFiltered = false
-		}
-	}
-	return res, nil
-}
-
 func getCachedReason(cache gcache.Cache, host string) (result Result, isFound bool, err error) {
 	isFound = false // not found yet
 
@@ -840,133 +501,11 @@ func (d *Dnsfilter) lookupCommon(host string, lookupstats *LookupStats, cache gc
 
 // AddRules is a convinience function to add an array of filters in one call
 func (d *Dnsfilter) AddRules(filters []Filter) error {
-	for _, f := range filters {
-		for _, rule := range f.Rules {
-			err := d.AddRule(rule, f.ID)
-			if err == ErrAlreadyExists || err == ErrInvalidSyntax {
-				continue
-			}
-			if err != nil {
-				log.Printf("Cannot add rule %s: %s", rule, err)
-				// Just ignore invalid rules
-				continue
-			}
-		}
-	}
 	return nil
 }
 
-// AddRule adds a rule, checking if it is a valid rule first and if it wasn't added already
-func (d *Dnsfilter) AddRule(input string, filterListID int64) error {
-	input = strings.TrimSpace(input)
-	d.storageMutex.RLock()
-	_, exists := d.storage[input]
-	d.storageMutex.RUnlock()
-	if exists {
-		// already added
-		return ErrAlreadyExists
-	}
-
-	if !isValidRule(input) {
-		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
-	r := rule{
-		text:         input, // will be modified
-		originalText: input,
-		listID:       filterListID,
-	}
-
-	// Mark rule as whitelist if it starts with @@
-	if strings.HasPrefix(r.text, "@@") {
-		r.isWhitelist = true
-		r.text = r.text[2:]
-	}
-
-	err := r.parseOptions()
-	if err != nil {
-		return err
-	}
-
-	r.extractShortcut()
-
-	if !enableDelayedCompilation {
-		err := r.compile()
-		if err != nil {
-			return err
-		}
-	}
-
-	destination := d.blackList
-	if r.isImportant {
-		destination = d.important
-	} else if r.isWhitelist {
-		destination = d.whiteList
-	}
-
-	d.storageMutex.Lock()
-	d.storage[input] = true
-	d.storageMutex.Unlock()
-	destination.Add(&r)
-	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 int64) 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:] {
-		r := rule{
-			text:         host,
-			originalText: input,
-			listID:       filterListID,
-			ip:           addr,
-		}
-		d.blackList.Add(&r)
-	}
-	return true
-}
-
 // 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) {
-	lists := []*rulesTable{
-		d.important,
-		d.whiteList,
-		d.blackList,
-	}
-
-	for _, table := range lists {
-		res, err := table.matchByHost(host)
-		if err != nil {
-			return res, err
-		}
-		if res.Reason.Matched() {
-			return res, nil
-		}
-	}
 	return Result{}, nil
 }
 
@@ -1061,11 +600,6 @@ func (d *Dnsfilter) createCustomDialContext(resolverAddr string) dialFunctionTyp
 func New(c *Config) *Dnsfilter {
 	d := new(Dnsfilter)
 
-	d.storage = make(map[string]bool)
-	d.important = newRulesTable()
-	d.whiteList = newRulesTable()
-	d.blackList = newRulesTable()
-
 	// Customize the Transport to have larger connection pool,
 	// We are not (re)using http.DefaultTransport because of race conditions found by tests
 	d.transport = &http.Transport{
@@ -1141,8 +675,3 @@ func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
 func (d *Dnsfilter) GetStats() Stats {
 	return stats
 }
-
-// Count returns number of rules added to filter
-func (d *Dnsfilter) Count() int {
-	return len(d.storage)
-}
diff --git a/dnsfilter/helpers.go b/dnsfilter/helpers.go
index 68d4ba26..2d60c47c 100644
--- a/dnsfilter/helpers.go
+++ b/dnsfilter/helpers.go
@@ -1,49 +1,9 @@
 package dnsfilter
 
 import (
-	"strings"
 	"sync/atomic"
 )
 
-func isValidRule(rule string) bool {
-	if len(rule) < 4 {
-		return false
-	}
-	if rule[0] == '!' {
-		return false
-	}
-	if rule[0] == '#' {
-		return false
-	}
-	if strings.HasPrefix(rule, "[Adblock") {
-		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{
-		"##",
-		"#@#",
-		"#?#",
-		"#@?#",
-		"#$#",
-		"#@$#",
-		"#?$#",
-		"#@?$#",
-		"$$",
-		"$@$",
-		"#%#",
-		"#@%#",
-	}
-	for _, mask := range masks {
-		if strings.Contains(rule, mask) {
-			return false
-		}
-	}
-
-	return true
-}
-
 func updateMax(valuePtr *int64, maxPtr *int64) {
 	for {
 		current := atomic.LoadInt64(valuePtr)
diff --git a/dnsfilter/rule_to_regexp.go b/dnsfilter/rule_to_regexp.go
deleted file mode 100644
index 41d55e30..00000000
--- a/dnsfilter/rule_to_regexp.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package dnsfilter
-
-import (
-	"strings"
-)
-
-func ruleToRegexp(rule string) (string, error) {
-	const hostStart = `(?:^|\.)`
-	const hostEnd = `$`
-
-	// empty or short rule -- do nothing
-	if !isValidRule(rule) {
-		return "", ErrInvalidSyntax
-	}
-
-	// if starts with / and ends with /, it's already a regexp, just strip the slashes
-	if rule[0] == '/' && rule[len(rule)-1] == '/' {
-		return rule[1 : len(rule)-1], nil
-	}
-
-	var sb strings.Builder
-
-	if rule[0] == '|' && rule[1] == '|' {
-		sb.WriteString(hostStart)
-		rule = rule[2:]
-	}
-
-	for i, r := range rule {
-		switch {
-		case r == '?' || r == '.' || r == '+' || r == '[' || r == ']' || r == '(' || r == ')' || r == '{' || r == '}' || r == '#' || r == '\\' || r == '$':
-			sb.WriteRune('\\')
-			sb.WriteRune(r)
-		case r == '|' && i == 0:
-			// | at start and it's not || at start
-			sb.WriteRune('^')
-		case r == '|' && i == len(rule)-1:
-			// | at end
-			sb.WriteRune('$')
-		case r == '|' && i != 0 && i != len(rule)-1:
-			sb.WriteString(`\|`)
-		case r == '*':
-			sb.WriteString(`.*`)
-		case r == '^':
-			sb.WriteString(hostEnd)
-		default:
-			sb.WriteRune(r)
-		}
-	}
-
-	return sb.String(), nil
-}
-
-// handle suffix rule ||example.com^ -- either entire string is example.com or *.example.com
-func getSuffix(rule string) (bool, string) {
-	// if starts with / and ends with /, it's already a regexp
-	// TODO: if a regexp is simple `/abracadabra$/`, then simplify it maybe?
-	if rule[0] == '/' && rule[len(rule)-1] == '/' {
-		return false, ""
-	}
-
-	// must start with ||
-	if rule[0] != '|' || rule[1] != '|' {
-		return false, ""
-	}
-	rule = rule[2:]
-
-	// suffix rule must end with ^ or |
-	lastChar := rule[len(rule)-1]
-	if lastChar != '^' && lastChar != '|' {
-		return false, ""
-	}
-	// last char was checked, eat it
-	rule = rule[:len(rule)-1]
-
-	// it might also end with ^|
-	if rule[len(rule)-1] == '^' {
-		rule = rule[:len(rule)-1]
-	}
-
-	// check that it doesn't have any special characters inside
-	for _, r := range rule {
-		switch r {
-		case '|':
-			return false, ""
-		case '*':
-			return false, ""
-		}
-	}
-
-	return true, rule
-}