diff --git a/internal/filtering/rewrite/item.go b/internal/filtering/rewrite/item.go index e5693143..d67798d7 100644 --- a/internal/filtering/rewrite/item.go +++ b/internal/filtering/rewrite/item.go @@ -2,7 +2,7 @@ package rewrite import ( "fmt" - "net" + "net/netip" "strings" "github.com/miekg/dns" @@ -26,11 +26,15 @@ func (rw *Item) equal(other *Item) (ok bool) { return false } - return rw.Domain == other.Domain && rw.Answer == other.Answer + return *rw == *other } // toRule converts rw to a filter rule. func (rw *Item) toRule() (res string) { + if rw == nil { + return "" + } + domain := strings.ToLower(rw.Domain) dType, exception := rw.rewriteParams() @@ -53,13 +57,13 @@ func (rw *Item) rewriteParams() (dType uint16, exception bool) { // Go on. } - ip := net.ParseIP(rw.Answer) - if ip == nil { + addr, err := netip.ParseAddr(rw.Answer) + if err != nil { + // TODO(d.kolyshev): Validate rw.Answer as a domain name. return dns.TypeCNAME, false } - ip4 := ip.To4() - if ip4 != nil { + if addr.Is4() { dType = dns.TypeA } else { dType = dns.TypeAAAA diff --git a/internal/filtering/rewrite/item_internal_test.go b/internal/filtering/rewrite/item_internal_test.go new file mode 100644 index 00000000..68d88223 --- /dev/null +++ b/internal/filtering/rewrite/item_internal_test.go @@ -0,0 +1,124 @@ +package rewrite + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestItem_equal(t *testing.T) { + const ( + testDomain = "example.org" + testAnswer = "1.1.1.1" + ) + + testItem := &Item{ + Domain: testDomain, + Answer: testAnswer, + } + + testCases := []struct { + name string + left *Item + right *Item + want bool + }{{ + name: "nil_left", + left: nil, + right: testItem, + want: false, + }, { + name: "nil_right", + left: testItem, + right: nil, + want: false, + }, { + name: "nils", + left: nil, + right: nil, + want: true, + }, { + name: "equal", + left: testItem, + right: testItem, + want: true, + }, { + name: "distinct", + left: testItem, + right: &Item{ + Domain: "other", + Answer: "other", + }, + want: false, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := tc.left.equal(tc.right) + assert.Equal(t, tc.want, res) + }) + } +} + +func TestItem_toRule(t *testing.T) { + const testDomain = "example.org" + + testCases := []struct { + name string + item *Item + want string + }{{ + name: "nil", + item: nil, + want: "", + }, { + name: "a_rule", + item: &Item{ + Domain: testDomain, + Answer: "1.1.1.1", + }, + want: "|example.org^$dnsrewrite=NOERROR;A;1.1.1.1", + }, { + name: "aaaa_rule", + item: &Item{ + Domain: testDomain, + Answer: "1:2:3::4", + }, + want: "|example.org^$dnsrewrite=NOERROR;AAAA;1:2:3::4", + }, { + name: "cname_rule", + item: &Item{ + Domain: testDomain, + Answer: "other.org", + }, + want: "|example.org^$dnsrewrite=NOERROR;CNAME;other.org", + }, { + name: "wildcard_rule", + item: &Item{ + Domain: "*.example.org", + Answer: "other.org", + }, + want: "|*.example.org^$dnsrewrite=NOERROR;CNAME;other.org", + }, { + name: "aaaa_exception", + item: &Item{ + Domain: testDomain, + Answer: "A", + }, + want: "@@||example.org^$dnstype=A,dnsrewrite", + }, { + name: "aaaa_exception", + item: &Item{ + Domain: testDomain, + Answer: "AAAA", + }, + want: "@@||example.org^$dnstype=AAAA,dnsrewrite", + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := tc.item.toRule() + assert.Equal(t, tc.want, res) + }) + } +} diff --git a/internal/filtering/rewrite/storage.go b/internal/filtering/rewrite/storage.go index 77355db5..0afb8b6e 100644 --- a/internal/filtering/rewrite/storage.go +++ b/internal/filtering/rewrite/storage.go @@ -7,15 +7,18 @@ import ( "sync" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/urlfilter" "github.com/AdguardTeam/urlfilter/filterlist" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" "golang.org/x/exp/slices" ) // Storage is a storage for rewrite rules. type Storage interface { - // MatchRequest finds a matching rule for the specified request. - MatchRequest(dReq *urlfilter.DNSRequest) (res *urlfilter.DNSResult, matched bool) + // MatchRequest returns matching dnsrewrites for the specified request. + MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite) // Add adds item to the storage. Add(item *Item) (err error) @@ -38,14 +41,14 @@ type DefaultStorage struct { // ruleList is the filtering rule ruleList used by the engine. ruleList filterlist.RuleList + // rewrites stores the rewrite entries from configuration. + rewrites []*Item + // urlFilterID is the synthetic integer identifier for the urlfilter engine. // // TODO(a.garipov): Change the type to a string in module urlfilter and // remove this crutch. urlFilterID int - - // rewrites stores the rewrite entries from configuration. - rewrites []*Item } // NewDefaultStorage returns new rewrites storage. listID is used as an @@ -72,11 +75,81 @@ func NewDefaultStorage(listID int, rewrites []*Item) (s *DefaultStorage, err err var _ Storage = (*DefaultStorage)(nil) // MatchRequest implements the [Storage] interface for *DefaultStorage. -func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (res *urlfilter.DNSResult, matched bool) { +func (s *DefaultStorage) MatchRequest(dReq *urlfilter.DNSRequest) (rws []*rules.DNSRewrite) { s.mu.RLock() defer s.mu.RUnlock() - return s.engine.MatchRequest(dReq) + rrules := s.rewriteRulesForReq(dReq) + if len(rrules) == 0 { + return nil + } + + // TODO(a.garipov): Check cnames for cycles on initialisation. + cnames := stringutil.NewSet() + host := dReq.Hostname + for len(rrules) > 0 && rrules[0].DNSRewrite != nil && rrules[0].DNSRewrite.NewCNAME != "" { + rule := rrules[0] + rwAns := rule.DNSRewrite.NewCNAME + + log.Debug("rewrite: cname for %s is %s", host, rwAns) + + if dReq.Hostname == rwAns { + // A request for the hostname itself is an exception rule. + // TODO(d.kolyshev): Check rewrite of a pattern onto itself. + + return nil + } + + if host == rwAns && isWildcard(rule.RuleText) { + // An "*.example.com → sub.example.com" rewrite matching in a loop. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/4016. + + return []*rules.DNSRewrite{rule.DNSRewrite} + } + + if cnames.Has(rwAns) { + log.Info("rewrite: cname loop for %q on %q", dReq.Hostname, rwAns) + + return nil + } + + cnames.Add(rwAns) + + drules := s.rewriteRulesForReq(&urlfilter.DNSRequest{ + Hostname: rwAns, + DNSType: dReq.DNSType, + }) + if drules != nil { + rrules = drules + } + + host = rwAns + } + + return s.collectDNSRewrites(rrules, dReq.DNSType) +} + +// collectDNSRewrites filters DNSRewrite by question type. +func (s *DefaultStorage) collectDNSRewrites( + rewrites []*rules.NetworkRule, + qtyp uint16, +) (rws []*rules.DNSRewrite) { + for _, rewrite := range rewrites { + dnsRewrite := rewrite.DNSRewrite + if matchesQType(dnsRewrite, qtyp) { + rws = append(rws, dnsRewrite) + } + } + + return rws +} + +// rewriteRulesForReq returns matching dnsrewrite rules. +func (s *DefaultStorage) rewriteRulesForReq(dReq *urlfilter.DNSRequest) (rules []*rules.NetworkRule) { + res, _ := s.engine.MatchRequest(dReq) + + return res.DNSRewrites() } // Add implements the [Storage] interface for *DefaultStorage. @@ -122,6 +195,7 @@ func (s *DefaultStorage) List() (items []*Item) { // resetRules resets the filtering rules. func (s *DefaultStorage) resetRules() (err error) { + // TODO(a.garipov): Use strings.Builder. var rulesText []string for _, rewrite := range s.rewrites { rulesText = append(rulesText, rewrite.toRule()) @@ -141,7 +215,27 @@ func (s *DefaultStorage) resetRules() (err error) { s.ruleList = strList s.engine = urlfilter.NewDNSEngine(rs) - log.Info("filter %d: reset %d rules", s.urlFilterID, s.engine.RulesCount) + log.Info("rewrite: filter %d: reset %d rules", s.urlFilterID, s.engine.RulesCount) return nil } + +// matchesQType returns true if dnsrewrite matches the question type qt. +func matchesQType(dnsrr *rules.DNSRewrite, qt uint16) (ok bool) { + // Add CNAMEs, since they match for all types requests. + if dnsrr.RRType == dns.TypeCNAME { + return true + } + + // Reject types other than A and AAAA. + if qt != dns.TypeA && qt != dns.TypeAAAA { + return false + } + + return dnsrr.RRType == qt +} + +// isWildcard returns true if pat is a wildcard domain pattern. +func isWildcard(pat string) (res bool) { + return strings.HasPrefix(pat, "|*.") +} diff --git a/internal/filtering/rewrite/storage_test.go b/internal/filtering/rewrite/storage_test.go index c240cca0..4db06a4e 100644 --- a/internal/filtering/rewrite/storage_test.go +++ b/internal/filtering/rewrite/storage_test.go @@ -1,8 +1,13 @@ package rewrite import ( + "net" "testing" + "github.com/AdguardTeam/urlfilter" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,3 +43,416 @@ func TestDefaultStorage_CRUD(t *testing.T) { require.NoError(t, err) require.Len(t, s.List(), 0) } + +func TestDefaultStorage_MatchRequest(t *testing.T) { + items := []*Item{{ + // This one and below are about CNAME, A and AAAA. + Domain: "somecname", + Answer: "somehost.com", + }, { + Domain: "somehost.com", + Answer: "0.0.0.0", + }, { + Domain: "host.com", + Answer: "1.2.3.4", + }, { + Domain: "host.com", + Answer: "1.2.3.5", + }, { + Domain: "host.com", + Answer: "1:2:3::4", + }, { + Domain: "www.host.com", + Answer: "host.com", + }, { + // This one is a wildcard. + Domain: "*.host.com", + Answer: "1.2.3.5", + }, { + // This one and below are about wildcard overriding. + Domain: "a.host.com", + Answer: "1.2.3.4", + }, { + // This one is about CNAME and wildcard interacting. + Domain: "*.host2.com", + Answer: "host.com", + }, { + // This one and below are about 2 level CNAME. + Domain: "b.host.com", + Answer: "somecname", + }, { + // This one and below are about 2 level CNAME and wildcard. + Domain: "b.host3.com", + Answer: "a.host3.com", + }, { + Domain: "a.host3.com", + Answer: "x.host.com", + }, { + Domain: "*.hostboth.com", + Answer: "1.2.3.6", + }, { + Domain: "*.hostboth.com", + Answer: "1234::5678", + }, { + Domain: "BIGHOST.COM", + Answer: "1.2.3.7", + }, { + Domain: "*.issue4016.com", + Answer: "sub.issue4016.com", + }} + + s, err := NewDefaultStorage(-1, items) + require.NoError(t, err) + + testCases := []struct { + name string + host string + wantDNSRewrites []*rules.DNSRewrite + dtyp uint16 + }{{ + name: "not_filtered_not_found", + host: "hoost.com", + wantDNSRewrites: nil, + dtyp: dns.TypeA, + }, { + name: "not_filtered_qtype", + host: "www.host.com", + wantDNSRewrites: nil, + dtyp: dns.TypeMX, + }, { + name: "rewritten_a", + host: "www.host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 2, 3, 4}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }, { + Value: net.IP{1, 2, 3, 5}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "rewritten_aaaa", + host: "www.host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.ParseIP("1:2:3::4"), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeAAAA, + }}, + dtyp: dns.TypeAAAA, + }, { + name: "wildcard_match", + host: "abc.host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 2, 3, 5}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + //}, { + // TODO(d.kolyshev): This is about matching in urlfilter. + // name: "wildcard_override", + // host: "a.host.com", + // wantDNSRewrites: []*rules.DNSRewrite{{ + // Value: net.IP{1, 2, 3, 4}.To16(), + // NewCNAME: "", + // RCode: dns.RcodeSuccess, + // RRType: dns.TypeA, + // }}, + // dtyp: dns.TypeA, + }, { + name: "wildcard_cname_interaction", + host: "www.host2.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 2, 3, 4}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }, { + Value: net.IP{1, 2, 3, 5}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "two_cnames", + host: "b.host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{0, 0, 0, 0}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "two_cnames_and_wildcard", + host: "b.host3.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 2, 3, 5}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "issue3343", + host: "www.hostboth.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.ParseIP("1234::5678"), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeAAAA, + }}, + dtyp: dns.TypeAAAA, + }, { + name: "issue3351", + host: "bighost.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 2, 3, 7}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "issue4008", + host: "somehost.com", + wantDNSRewrites: nil, + dtyp: dns.TypeHTTPS, + }, { + name: "issue4016", + host: "www.issue4016.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: nil, + NewCNAME: "sub.issue4016.com", + RCode: dns.RcodeSuccess, + RRType: dns.TypeNone, + }}, + dtyp: dns.TypeA, + }, { + name: "issue4016_self", + host: "sub.issue4016.com", + wantDNSRewrites: nil, + dtyp: dns.TypeA, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{ + Hostname: tc.host, + DNSType: tc.dtyp, + }) + + assert.Equal(t, tc.wantDNSRewrites, dnsRewrites) + }) + } +} + +func TestDefaultStorage_MatchRequest_Levels(t *testing.T) { + // Exact host, wildcard L2, wildcard L3. + items := []*Item{{ + Domain: "host.com", + Answer: "1.1.1.1", + }, { + Domain: "*.host.com", + Answer: "2.2.2.2", + }, { + Domain: "*.sub.host.com", + Answer: "3.3.3.3", + }} + + s, err := NewDefaultStorage(-1, items) + require.NoError(t, err) + + testCases := []struct { + name string + host string + wantDNSRewrites []*rules.DNSRewrite + dtyp uint16 + }{{ + name: "exact_match", + host: "host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 1, 1, 1}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "l2_match", + host: "sub.host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{2, 2, 2, 2}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + //}, { + // TODO(d.kolyshev): This is about matching in urlfilter. + // name: "l3_match", + // host: "my.sub.host.com", + // wantDNSRewrites: []*rules.DNSRewrite{{ + // Value: net.IP{3, 3, 3, 3}.To16(), + // NewCNAME: "", + // RCode: dns.RcodeSuccess, + // RRType: dns.TypeA, + // }}, + // dtyp: dns.TypeA, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{ + Hostname: tc.host, + DNSType: tc.dtyp, + }) + + assert.Equal(t, tc.wantDNSRewrites, dnsRewrites) + }) + } +} + +func TestDefaultStorage_MatchRequest_ExceptionCNAME(t *testing.T) { + // Wildcard and exception for a sub-domain. + items := []*Item{{ + Domain: "*.host.com", + Answer: "2.2.2.2", + }, { + Domain: "sub.host.com", + Answer: "sub.host.com", + }, { + Domain: "*.sub.host.com", + Answer: "*.sub.host.com", + }} + + s, err := NewDefaultStorage(-1, items) + require.NoError(t, err) + + testCases := []struct { + name string + host string + wantDNSRewrites []*rules.DNSRewrite + dtyp uint16 + }{{ + name: "match_subdomain", + host: "my.host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{2, 2, 2, 2}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "exception_cname", + host: "sub.host.com", + wantDNSRewrites: nil, + dtyp: dns.TypeA, + //}, { + // TODO(d.kolyshev): This is about matching in urlfilter. + // name: "exception_wildcard", + // host: "my.sub.host.com", + // wantDNSRewrites: nil, + // dtyp: dns.TypeA, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{ + Hostname: tc.host, + DNSType: tc.dtyp, + }) + + assert.Equal(t, tc.wantDNSRewrites, dnsRewrites) + }) + } +} + +func TestDefaultStorage_MatchRequest_ExceptionIP(t *testing.T) { + // Exception for AAAA record. + items := []*Item{{ + Domain: "host.com", + Answer: "1.2.3.4", + }, { + Domain: "host.com", + Answer: "AAAA", + }, { + Domain: "host2.com", + Answer: "::1", + }, { + Domain: "host2.com", + Answer: "A", + }, { + Domain: "host3.com", + Answer: "A", + }} + + s, err := NewDefaultStorage(-1, items) + require.NoError(t, err) + + testCases := []struct { + name string + host string + wantDNSRewrites []*rules.DNSRewrite + dtyp uint16 + }{{ + name: "match_A", + host: "host.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.IP{1, 2, 3, 4}.To16(), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeA, + }}, + dtyp: dns.TypeA, + }, { + name: "exception_AAAA_host.com", + host: "host.com", + wantDNSRewrites: nil, + dtyp: dns.TypeAAAA, + }, { + name: "exception_A_host2.com", + host: "host2.com", + wantDNSRewrites: nil, + dtyp: dns.TypeA, + }, { + name: "match_AAAA_host2.com", + host: "host2.com", + wantDNSRewrites: []*rules.DNSRewrite{{ + Value: net.ParseIP("::1"), + NewCNAME: "", + RCode: dns.RcodeSuccess, + RRType: dns.TypeAAAA, + }}, + dtyp: dns.TypeAAAA, + }, { + name: "exception_A_host3.com", + host: "host3.com", + wantDNSRewrites: nil, + dtyp: dns.TypeA, + }, { + name: "match_AAAA_host3.com", + host: "host3.com", + wantDNSRewrites: nil, + dtyp: dns.TypeAAAA, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dnsRewrites := s.MatchRequest(&urlfilter.DNSRequest{ + Hostname: tc.host, + DNSType: tc.dtyp, + }) + + assert.Equal(t, tc.wantDNSRewrites, dnsRewrites) + }) + } +} diff --git a/internal/filtering/rewritehttp.go b/internal/filtering/rewritehttp.go new file mode 100644 index 00000000..752979fe --- /dev/null +++ b/internal/filtering/rewritehttp.go @@ -0,0 +1,93 @@ +package filtering + +import ( + "encoding/json" + "net/http" + + "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" + "github.com/AdguardTeam/golibs/log" +) + +// TODO(d.kolyshev): Use [rewrite.Item] instead. +type rewriteEntryJSON struct { + Domain string `json:"domain"` + Answer string `json:"answer"` +} + +func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { + arr := []*rewriteEntryJSON{} + + d.confLock.Lock() + for _, ent := range d.Config.Rewrites { + jsent := rewriteEntryJSON{ + Domain: ent.Domain, + Answer: ent.Answer, + } + arr = append(arr, &jsent) + } + d.confLock.Unlock() + + _ = aghhttp.WriteJSONResponse(w, r, arr) +} + +func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { + rwJSON := rewriteEntryJSON{} + err := json.NewDecoder(r.Body).Decode(&rwJSON) + if err != nil { + aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) + + return + } + + rw := &LegacyRewrite{ + Domain: rwJSON.Domain, + Answer: rwJSON.Answer, + } + + err = rw.normalize() + if err != nil { + // Shouldn't happen currently, since normalize only returns a non-nil + // error when a rewrite is nil, but be change-proof. + aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err) + + return + } + + d.confLock.Lock() + d.Config.Rewrites = append(d.Config.Rewrites, rw) + d.confLock.Unlock() + log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites)) + + d.Config.ConfigModified() +} + +func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { + jsent := rewriteEntryJSON{} + err := json.NewDecoder(r.Body).Decode(&jsent) + if err != nil { + aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) + + return + } + + entDel := &LegacyRewrite{ + Domain: jsent.Domain, + Answer: jsent.Answer, + } + arr := []*LegacyRewrite{} + + d.confLock.Lock() + for _, ent := range d.Config.Rewrites { + if ent.equal(entDel) { + log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer) + + continue + } + + arr = append(arr, ent) + } + d.Config.Rewrites = arr + d.confLock.Unlock() + + d.Config.ConfigModified() +} diff --git a/internal/filtering/rewrites.go b/internal/filtering/rewrites.go index d5033900..057a9c4e 100644 --- a/internal/filtering/rewrites.go +++ b/internal/filtering/rewrites.go @@ -3,22 +3,16 @@ package filtering import ( - "encoding/json" "fmt" "net" - "net/http" "sort" "strings" - "github.com/AdguardTeam/AdGuardHome/internal/aghhttp" "github.com/AdguardTeam/golibs/errors" - "github.com/AdguardTeam/golibs/log" "github.com/miekg/dns" "golang.org/x/exp/slices" ) -// TODO(d.kolyshev): Rename this file to rewritehttp.go. - // LegacyRewrite is a single legacy DNS rewrite record. // // Instances of *LegacyRewrite must never be nil. @@ -223,86 +217,3 @@ func max(a, b int) int { return b } - -type rewriteEntryJSON struct { - Domain string `json:"domain"` - Answer string `json:"answer"` -} - -func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { - arr := []*rewriteEntryJSON{} - - d.confLock.Lock() - for _, ent := range d.Config.Rewrites { - jsent := rewriteEntryJSON{ - Domain: ent.Domain, - Answer: ent.Answer, - } - arr = append(arr, &jsent) - } - d.confLock.Unlock() - - _ = aghhttp.WriteJSONResponse(w, r, arr) -} - -func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { - rwJSON := rewriteEntryJSON{} - err := json.NewDecoder(r.Body).Decode(&rwJSON) - if err != nil { - aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) - - return - } - - rw := &LegacyRewrite{ - Domain: rwJSON.Domain, - Answer: rwJSON.Answer, - } - - err = rw.normalize() - if err != nil { - // Shouldn't happen currently, since normalize only returns a non-nil - // error when a rewrite is nil, but be change-proof. - aghhttp.Error(r, w, http.StatusBadRequest, "normalizing: %s", err) - - return - } - - d.confLock.Lock() - d.Config.Rewrites = append(d.Config.Rewrites, rw) - d.confLock.Unlock() - log.Debug("rewrite: added element: %s -> %s [%d]", rw.Domain, rw.Answer, len(d.Config.Rewrites)) - - d.Config.ConfigModified() -} - -func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { - jsent := rewriteEntryJSON{} - err := json.NewDecoder(r.Body).Decode(&jsent) - if err != nil { - aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err) - - return - } - - entDel := &LegacyRewrite{ - Domain: jsent.Domain, - Answer: jsent.Answer, - } - arr := []*LegacyRewrite{} - - d.confLock.Lock() - for _, ent := range d.Config.Rewrites { - if ent.equal(entDel) { - log.Debug("rewrite: removed element: %s -> %s", ent.Domain, ent.Answer) - - continue - } - - arr = append(arr, ent) - } - d.Config.Rewrites = arr - d.confLock.Unlock() - - d.Config.ConfigModified() -} diff --git a/internal/filtering/rewrites_test.go b/internal/filtering/rewrites_test.go index 629df5cc..17caa167 100644 --- a/internal/filtering/rewrites_test.go +++ b/internal/filtering/rewrites_test.go @@ -10,7 +10,6 @@ import ( ) // TODO(e.burkov): All the tests in this file may and should me merged together. -// TODO(d.kolyshev): Move these tests to rewrite package. func TestRewrites(t *testing.T) { d, _ := newForTest(t, nil, nil) diff --git a/scripts/make/go-lint.sh b/scripts/make/go-lint.sh index 31aba8c3..245211be 100644 --- a/scripts/make/go-lint.sh +++ b/scripts/make/go-lint.sh @@ -227,9 +227,9 @@ gocyclo --over 13 ./internal/dhcpd ./internal/filtering/ ./internal/home/ # Apply stricter standards to new or somewhat refactored code. gocyclo --over 10 ./internal/aghio/ ./internal/aghnet/ ./internal/aghos/\ - ./internal/aghtest/ ./internal/dnsforward/ ./internal/stats/\ - ./internal/tools/ ./internal/updater/ ./internal/next/ ./internal/version/\ - ./scripts/vetted-filters/ ./main.go + ./internal/aghtest/ ./internal/dnsforward/ ./internal/filtering/rewrite/\ + ./internal/stats/ ./internal/tools/ ./internal/updater/ ./internal/next/\ + ./internal/version/ ./scripts/vetted-filters/ ./main.go ineffassign ./...