diff --git a/internal/upstream/uadguard/rules/escaper.go b/internal/upstream/uadguard/rules/escaper.go new file mode 100644 index 0000000..8eee421 --- /dev/null +++ b/internal/upstream/uadguard/rules/escaper.go @@ -0,0 +1,27 @@ +package rules + +import "strings" + +func EscapeString(in string) string { + var out strings.Builder + for _, c := range in { + switch c { + case '\'', '"', ',', '|', '/', '$': + _, _ = out.WriteRune('\\') + } + out.WriteRune(c) + } + return out.String() +} + +func UnescapeString(in string) string { + var out strings.Builder + for _, c := range in { + if c == '\\' { + continue + } + + out.WriteRune(c) + } + return out.String() +} diff --git a/internal/upstream/uadguard/rules/parser_test.go b/internal/upstream/uadguard/rules/parser_test.go index 1c0ece2..0eb939a 100644 --- a/internal/upstream/uadguard/rules/parser_test.go +++ b/internal/upstream/uadguard/rules/parser_test.go @@ -192,6 +192,17 @@ func TestParseRule(t *testing.T) { }, }, }, + { + in: `|k3s-lab-a-yawg.pve.buglloc.cc^$dnsrewrite=NOERROR;TXT;heritage=external-dns\,external-dns\/owner=thailab\,external-dns\/resource=service\/external-services\/pve-mahine-yawg`, + out: Rule{ + Rule: &upstream.Rule{ + Name: "k3s-lab-a-yawg.pve.buglloc.cc.", + Type: dns.TypeTXT, + Value: "heritage=external-dns,external-dns/owner=thailab,external-dns/resource=service/external-services/pve-mahine-yawg", + ValueStr: "heritage=external-dns,external-dns/owner=thailab,external-dns/resource=service/external-services/pve-mahine-yawg", + }, + }, + }, { in: "|ya.ru^$dnsrewrite=REFUSED;;", err: true, @@ -219,3 +230,94 @@ func TestParseRule(t *testing.T) { }) } } + +func TestFormat(t *testing.T) { + cases := []struct { + out string + in Rule + }{ + { + out: "|4.3.2.1.in-addr.arpa^$dnsrewrite=NOERROR;PTR;example.net", + in: Rule{ + Rule: &upstream.Rule{ + Name: "4.3.2.1.in-addr.arpa.", + Type: dns.TypePTR, + Value: "example.net.", + ValueStr: "example.net.", + }, + }, + }, + { + out: "|2.0.0.0.0.0.0.0.4.f.7.0.0.0.0.0.0.0.4.3.0.0.0.0.8.b.6.0.2.0.a.2.ip6.arpa^$dnsrewrite=NOERROR;PTR;example.net", + in: Rule{ + Rule: &upstream.Rule{ + Name: "2.0.0.0.0.0.0.0.4.f.7.0.0.0.0.0.0.0.4.3.0.0.0.0.8.b.6.0.2.0.a.2.ip6.arpa.", + Type: dns.TypePTR, + Value: "example.net.", + ValueStr: "example.net.", + }, + }, + }, + { + out: "|ya.ru^$dnsrewrite=NOERROR;A;1.2.3.3", + in: Rule{ + Rule: &upstream.Rule{ + Name: "ya.ru.", + Type: dns.TypeA, + Value: net.ParseIP("1.2.3.3"), + ValueStr: "1.2.3.3", + }, + }, + }, + { + out: "|ya.ru^$dnsrewrite=NOERROR;AAAA;::1", + in: Rule{ + Rule: &upstream.Rule{ + Name: "ya.ru.", + Type: dns.TypeAAAA, + Value: net.ParseIP("::1"), + ValueStr: "::1", + }, + }, + }, + { + out: "|ya.ru^$dnsrewrite=NOERROR;CNAME;google.com", + in: Rule{ + Rule: &upstream.Rule{ + Name: "ya.ru.", + Type: dns.TypeCNAME, + Value: "google.com.", + ValueStr: "google.com.", + }, + }, + }, + { + out: "||ya.ru^$dnsrewrite=NOERROR;CNAME;google.com", + in: Rule{ + Rule: &upstream.Rule{ + Name: "*.ya.ru.", + Type: dns.TypeCNAME, + Value: "google.com.", + ValueStr: "google.com.", + }, + }, + }, + { + out: `|k3s-lab-a-yawg.pve.buglloc.cc^$dnsrewrite=NOERROR;TXT;heritage=external-dns\,external-dns\/owner=thailab\,external-dns\/resource=service\/external-services\/pve-mahine-yawg`, + in: Rule{ + Rule: &upstream.Rule{ + Name: "k3s-lab-a-yawg.pve.buglloc.cc.", + Type: dns.TypeTXT, + Value: "heritage=external-dns,external-dns/owner=thailab,external-dns/resource=service/external-services/pve-mahine-yawg", + ValueStr: "heritage=external-dns,external-dns/owner=thailab,external-dns/resource=service/external-services/pve-mahine-yawg", + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.out, func(t *testing.T) { + require.Equal(t, tc.out, tc.in.Format()) + }) + } +} diff --git a/internal/upstream/uadguard/rules/rule.go b/internal/upstream/uadguard/rules/rule.go index 2c3f339..f130dfc 100644 --- a/internal/upstream/uadguard/rules/rule.go +++ b/internal/upstream/uadguard/rules/rule.go @@ -28,7 +28,7 @@ func (r *Rule) SameUpstreamRule(other *upstream.Rule) bool { } func (r *Rule) Format() string { - value := fmt.Sprint(r.Value) + value := EscapeString(fmt.Sprint(r.Value)) name := unFqdn(r.Name) if strictName := strings.TrimPrefix(name, "*."); strictName != name { name = "|" + strictName @@ -196,6 +196,7 @@ func newRulePTR(name string, valStr string) (*upstream.Rule, error) { } func newRuleTXT(name string, valStr string) (*upstream.Rule, error) { + valStr = UnescapeString(strings.TrimSpace(valStr)) return &upstream.Rule{ Name: name, Type: dns.TypeTXT,