summaryrefslogtreecommitdiff
path: root/internal/blocklist
diff options
context:
space:
mode:
authorradhitya <alif@radhitya.org>2026-07-05 18:37:52 +0700
committerradhitya <alif@radhitya.org>2026-07-05 18:37:52 +0700
commit01437d0a0afc3533baba26e1f36e66e32ae86ac9 (patch)
tree526ea1dc76e602007fe86eddc819ffae63b7b441 /internal/blocklist
parent1799ef47cedcfed8c979e145783fff7c9d1944cc (diff)
finalize the blocklist feature
Diffstat (limited to 'internal/blocklist')
-rw-r--r--internal/blocklist/blocklist.go52
-rw-r--r--internal/blocklist/blocklist_test.go59
2 files changed, 95 insertions, 16 deletions
diff --git a/internal/blocklist/blocklist.go b/internal/blocklist/blocklist.go
index 34a456d..84d758c 100644
--- a/internal/blocklist/blocklist.go
+++ b/internal/blocklist/blocklist.go
@@ -24,30 +24,50 @@ func Load(path string) (*Blocklist, error) {
sc := bufio.NewScanner(f)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
- if line == "" || line[0] == '#' {
+ if line == "" || line[0] == '#' || line[0] == '!' {
continue
}
- line = strings.ToLower(line)
- line = strings.TrimSuffix(line, ".")
- bl.domains[line] = struct{}{}
+ domain := parseLine(line)
+ if domain != "" && !strings.HasPrefix(domain, "*.") {
+ bl.domains[strings.ToLower(domain)] = struct {}{}
+ }
}
return bl, sc.Err()
}
-func (bl *Blocklist) Blocked(qname string) bool {
- qname = strings.ToLower(qname)
- qname = strings.TrimSuffix(qname, ".")
+func parseLine(line string) string {
+ line = strings.TrimSpace(line)
- if _, ok := bl.domains[qname]; ok {
- return true
+ // 1. Hosts: "0.0.0.0 domain" "127.0.0.1 domain # comment"
+ if strings.HasPrefix(line, "0.0.0.0 ") {
+ line = line[8:]
+ } else if strings.HasPrefix(line, "127.0.0.1 ") {
+ line = line[10:]
+ } else if strings.HasPrefix(line, "||") {
+ // 2. AdGuard / uBlock: "||domain^$attr"
+ line = line[2:]
+ // Cut at ^ or $
+ if idx := strings.IndexAny(line, "^$"); idx >= 0 {
+ line = line[:idx]
+ }
}
- for i := 0; i < len(qname); i++ {
- if qname[i] == '.' {
- if _, ok := bl.domains[qname[i+1:]]; ok {
- return true
- }
- }
+ if idx := strings.IndexAny(line, " \t#"); idx >= 0 {
+ line = line[:idx]
+ }
+
+ line = strings.TrimSpace(line)
+ line = strings.TrimSuffix(line, ".")
+
+ if strings.ContainsAny(line, "/:\\?") {
+ return ""
}
- return false
+ return line
+}
+
+func (bl *Blocklist) Blocked(qname string) bool {
+ qname = strings.ToLower(qname)
+ qname = strings.TrimSuffix(qname, ".")
+ _, ok := bl.domains[qname]
+ return ok
}
diff --git a/internal/blocklist/blocklist_test.go b/internal/blocklist/blocklist_test.go
new file mode 100644
index 0000000..0104bc9
--- /dev/null
+++ b/internal/blocklist/blocklist_test.go
@@ -0,0 +1,59 @@
+package blocklist
+
+import (
+ "os"
+ "testing"
+)
+
+func TestParseLine(t *testing.T) {
+ tests := []struct{in,out string}{
+ {"0.0.0.0 domain.com", "domain.com"},
+ {"0.0.0.0 domain.com # foo", "domain.com"},
+ {"0.0.0.0 *.domain.com", "*.domain.com"},
+ {"||domain.com^", "domain.com"},
+ {"||domain.com^$script", "domain.com"},
+ {"",""},
+ {"# comment", ""},
+ {"||*.domain.com^", "*.domain.com"},
+ }
+ for _, tt := range tests {
+ if got := parseLine(tt.in); got != tt.out {
+ t.Errorf("parseLine(%q) = %q, want %q", tt.in, got, tt.out)
+ }
+ }
+}
+
+func TestBlocked(t *testing.T) {
+ tmp, _ := os.CreateTemp("", "blocklist-*.txt")
+ tmp.WriteString("radhitya.org\n")
+ tmp.WriteString("0.0.0.0 ads.test.com\n")
+ tmp.WriteString("||example.net^\n")
+ tmp.WriteString("||*.domain.com^\n")
+ tmp.Close()
+ defer os.Remove(tmp.Name())
+
+ bl, err := Load(tmp.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !bl.Blocked("radhitya.org") {
+ t.Error("radhitya.org shall be blocked")
+ }
+ if bl.Blocked("alif.radhitya.org") {
+ t.Error("alif.radhitya.org shall NOT be blocked")
+ }
+ if !bl.Blocked("ads.test.com") {
+ t.Error("ads.test.com shall be blocked (hosts format)")
+ }
+ if !bl.Blocked("example.net") {
+ t.Error("example.net shall be blocked (adguard format)")
+ }
+
+ if bl.Blocked("domain.com") {
+ t.Error("domain.com shall NOT be blocked (wildcard skipped)")
+ }
+ if bl.Blocked("sub.domain.com") {
+ t.Error("sub.domain.com shall NOT be blocked (wildcard skipped)")
+ }
+}