package blocking import ( "errors" "os" "path/filepath" "strings" "testing" ) func TestParse(t *testing.T) { src := "\uFEFF[Adblock Plus]\r\n" + `! Title: test list # a comment ||ads.example^ ||Tracker.Example.^$important ||cosmetic.example^$third-party @@||allowed.example^ example.org##.banner |https://url.example/path 0.0.0.0 hosts.example other.example # two names 127.0.0.1 localhost 127.0.0.1 localhost.localdomain ::1 ip6-localhost 0.0.0.0 0.0.0.0 plain.example *.wild.example single 1.2.3.4 bad_label!.example münchen.example www.ads.example ` + strings.Repeat("a", 64) + ".example\n" l, err := Parse(strings.NewReader(src), Limits{}) if err != nil { t.Fatal(err) } want := map[string]uint8{ "ads.example": Exact | Below, "tracker.example": Exact | Below, "hosts.example": Exact, "other.example": Exact, "plain.example": Exact, "wild.example": Exact | Below, "xn--mnchen-3ya.example": Exact, "www.ads.example": Exact, } if len(l.Domains) != len(want) { t.Errorf("domains = %v", l.Domains) } for n, f := range want { if l.Domains[n] != f { t.Errorf("%s = %d, want %d", n, l.Domains[n], f) } } // Skipped: $third-party, @@, ##, |url, localhost (x3), 0.0.0.0, single, 1.2.3.4, bad_label!, 64-char label. if l.Skipped != 12 || l.Lines != 19 { t.Errorf("lines %d, skipped %d", l.Lines, l.Skipped) } Compact(l) if _, ok := l.Domains["www.ads.example"]; ok { t.Error("www.ads.example is covered by ||ads.example^ and should be dropped") } var sb strings.Builder n, hash, err := FeedZone(&sb, "test", 7, l) if err != nil || n != 10 { t.Fatalf("FeedZone: %d records, %v", n, err) } for _, line := range []string{"ads.example\tCNAME\t.\n", "*.ads.example\tCNAME\t.\n", "plain.example\tCNAME\t.\n", "@\tSOA\tlocalhost. hostmaster.localhost. 7 "} { if !strings.Contains(sb.String(), line) { t.Errorf("zone lacks %q:\n%s", line, sb.String()) } } if strings.Contains(sb.String(), "*.plain.example") || strings.Contains(sb.String(), "$ORIGIN") { t.Errorf("zone:\n%s", sb.String()) } // The hash ignores the serial. var other strings.Builder if _, h2, _ := FeedZone(&other, "test", 8, l); h2 != hash { t.Error("hash depends on the serial") } } func TestParseLimits(t *testing.T) { _, err := Parse(strings.NewReader("a.example\nb.example\na.example\nc.example\n"), Limits{MaxEntries: 2}) if !errors.Is(err, ErrTooManyEntries) { t.Errorf("err = %v", err) } if _, err := Parse(strings.NewReader("a.example\nb.example\na.example\n"), Limits{MaxEntries: 2}); err != nil { t.Errorf("a repeated name counted against the limit: %v", err) } l, _ := Parse(strings.NewReader("long-name.example\nshort.example\n"), Limits{MaxNameLen: 15}) if len(l.Domains) != 1 || l.Skipped != 1 { t.Errorf("max name length: %v", l.Domains) } } func TestRuleEntries(t *testing.T) { rules := []Rule{ {Name: "ads.example", Action: ActionBlock}, // and everything below {Name: "cdn.ads.example", Action: ActionAllow, Exact: true}, // just this name {Name: "wide.example", Action: ActionBlock}, // and below {Name: "ok.wide.example", Action: ActionAllow}, // and below {Name: "deep.x.y.wide.example", Action: ActionAllow, Exact: true}, // two empty names in between {Name: "solo.example", Action: ActionBlock, Exact: true}, } e := RuleEntries(rules) for name, want := range map[string]string{ "ads.example": ActionBlock, "x.ads.example": ActionBlock, "cdn.ads.example": ActionAllow, "y.cdn.ads.example": ActionBlock, // the wildcard above still applies "ok.wide.example": ActionAllow, "a.ok.wide.example": ActionAllow, "y.wide.example": ActionBlock, // exists only because of a deeper rule "x.y.wide.example": ActionBlock, "z.x.y.wide.example": ActionBlock, "deep.x.y.wide.example": ActionAllow, "below.deep.x.y.wide.example": ActionBlock, "solo.example": ActionBlock, "other.example": "", "sub.solo.example": "", "example": "", "unrelated.org": "", } { _, act, ok := MatchRules(e, name) if !ok { act = "" } if act != want { t.Errorf("%s: %q, want %q", name, act, want) } } z := RulesZoneFile("rules", rules) if !strings.Contains(z, "cdn.ads.example\tCNAME\trpz-passthru.\n*.cdn.ads.example\tCNAME\t.\n") { t.Errorf("rules zone:\n%s", z) } if strings.Index(z, "ads.example\t") > strings.Index(z, "*.ads.example\t") { t.Errorf("a name should come before its wildcard:\n%s", z) } } func TestMatchFeed(t *testing.T) { l, _ := Parse(strings.NewReader("||tracker.example^\nexact.example\n||deep.a.example^\n"), Limits{}) Compact(l) path := filepath.Join(t.TempDir(), "list.rpz") f, _ := os.Create(path) FeedZone(f, "t", 1, l) f.Close() for name, want := range map[string]string{ "tracker.example": "tracker.example", "a.b.tracker.example": "*.tracker.example", "exact.example": "exact.example", "sub.exact.example": "", "x.deep.a.example": "*.deep.a.example", "a.example": "", "example": "", } { owner, ok, err := MatchFeed(path, name) if err != nil { t.Fatal(err) } if !ok { owner = "" } if owner != want { t.Errorf("%s: %q, want %q", name, owner, want) } } } func TestNames(t *testing.T) { if ListZone("hagezi") != "hagezi.blocklist.jarl.invalid" || ListName("hagezi.blocklist.jarl.invalid.") != "hagezi" || ListName(RulesZone) != "" || ListName("x.y.blocklist.jarl.invalid") != "" { t.Error("zone names") } if n, err := NormalizeName("Ads.Example.COM."); err != nil || n != "ads.example.com" { t.Errorf("NormalizeName = %q, %v", n, err) } for _, bad := range []string{"com", "1.2.3.4", "a b.example", "*.example.com", ""} { if _, err := NormalizeName(bad); err == nil { t.Errorf("%q accepted", bad) } } }