milestone 21: abp list exceptions and a regex rule kind
This commit is contained in:
@@ -279,6 +279,14 @@ Consumed by the blocklist manager (`src/filter/manager.zig`): downloaded by the
|
||||
fetcher and compiled into domain sets. A disabled source is neither downloaded
|
||||
nor loaded.
|
||||
|
||||
A list in Adblock Plus syntax may also carry exception lines, `@@||name^` and
|
||||
`@@||name`, either of which may end in `$important`. Those become allow entries
|
||||
that cancel what any attached list blocks, for the name and its subdomains. They
|
||||
cancel nothing an operator decided: every rule of the table above is checked
|
||||
first, so a downloaded list can reopen only a hole another downloaded list dug.
|
||||
Each source reports how many it carried as `exceptions`; there is no way to write
|
||||
one by hand, and no reason to want one — write an allow rule instead.
|
||||
|
||||
### group_sources
|
||||
|
||||
Which groups consult which blocklist sources.
|
||||
@@ -299,15 +307,48 @@ Per-group allow and block overrides, checked before the blocklists.
|
||||
|---|---|---|---|
|
||||
| `group` | string | required | must name a declared group |
|
||||
| `pattern` | string | required | see below |
|
||||
| `kind` | enum `.exact` \| `.wildcard` | required | — |
|
||||
| `kind` | enum `.exact` \| `.wildcard` \| `.regex` | required | — |
|
||||
| `action` | enum `.allow` \| `.block` | required | — |
|
||||
|
||||
Pattern rules: an `.exact` pattern is a plain domain name and may not contain
|
||||
`*`. A `.wildcard` pattern must contain at least one label that is exactly `*`
|
||||
(`*.tracker.example`, or `*` alone), and every other label must be a legal DNS
|
||||
label. `ads*.example` is not a valid wildcard.
|
||||
label. `ads*.example` is not a valid wildcard; a partial label is what the
|
||||
`.regex` kind is for.
|
||||
|
||||
Consumed by the filter engine's rule sets (`src/filter/rules.zig`).
|
||||
A `.regex` pattern is a regular expression matched against the whole normalized
|
||||
lowercase name, unanchored unless you write `^` or `$` — the POSIX-grep
|
||||
convention. It is stored exactly as you typed it, which the other two kinds are
|
||||
not: lowercasing would turn `\D` into `\d`, and trimming a trailing `.` would
|
||||
delete an any-byte atom. The engine (`src/filter/regex.zig`) accepts literal
|
||||
bytes, `.` for any byte, character classes `[a-z0-9]` with a leading `^` for
|
||||
negation, the escapes `\d` and `\w` plus `\` before any other ASCII punctuation
|
||||
to make it a literal, the repetitions `*` `+` `?` `{n}` `{n,m}` `{n,}`,
|
||||
alternation `|`, grouping `(...)`, and the anchors `^` and `$`.
|
||||
|
||||
Everything else is refused at the edge rather than approximated, so a pattern
|
||||
written for another engine fails where you can read the diagnostic instead of
|
||||
silently matching names you did not mean:
|
||||
|
||||
- backreferences, lookaround, captures, named groups, Unicode classes and the
|
||||
`(?…)` prefix they share;
|
||||
- any alphanumeric escape the list above omits — `\s`, `\b`, `\1`, `\D`;
|
||||
- a `]` inside a class, unless written `\]`;
|
||||
- an empty pattern, and an empty branch: `ads|` is refused rather than read as a
|
||||
pattern that matches every name;
|
||||
- a quantifier applied straight to another quantifier: `a+?` is refused rather
|
||||
than read as `(a+)?`, which matches every name. Write `(a+)?` to mean that.
|
||||
|
||||
A pattern is at most 256 bytes and compiles to at most 1024 instructions, each
|
||||
limit with its own diagnostic, and one group holds at most 256 regex rules.
|
||||
Groups do not capture, and the engine simulates every alternative in lockstep,
|
||||
so a pattern costs at most its compiled length times the length of the name —
|
||||
`(a+)+b` is as cheap here as it is expensive in a backtracking engine.
|
||||
|
||||
Consumed by the filter engine's rule sets (`src/filter/rules.zig`), which checks
|
||||
the three kinds in the order they are listed above, allow before block within
|
||||
each. Regex is checked last of the three because it is the only kind that costs
|
||||
more than a hash lookup or a label walk.
|
||||
|
||||
### local_records
|
||||
|
||||
@@ -548,10 +589,12 @@ upstream. Everything else keeps its default.
|
||||
.{ .group = "kids", .source_url = "https://lists.example/ads.txt" },
|
||||
},
|
||||
|
||||
// Overrides beat blocklists. Wildcards need a label that is exactly "*".
|
||||
// Overrides beat blocklists. Wildcards need a label that is exactly "*";
|
||||
// a partial label takes a regex, which is unanchored unless you say "^".
|
||||
.rules = .{
|
||||
.{ .group = "default", .pattern = "allowed.example", .kind = .exact, .action = .allow },
|
||||
.{ .group = "kids", .pattern = "*.tracker.example", .kind = .wildcard, .action = .block },
|
||||
.{ .group = "kids", .pattern = "^ad[0-9]+-", .kind = .regex, .action = .block },
|
||||
},
|
||||
|
||||
// Local names, answered without any upstream.
|
||||
|
||||
Reference in New Issue
Block a user