skip empty URL block patterns consistently

Centralize empty-pattern filtering in UrlBlocklist.init/initPatterns so
the CDP setBlockedURLs path matches the CLI --block-urls behavior. Keep
blocks aligned with the filtered patterns.
This commit is contained in:
Pierre Tachoire committed 2026-07-23 12:15:49 +02:00
1 parent dee40075a0
commit 43d17a78f2
1 file changed
+52 -12
+52 -12
View File
@@ -26,22 +26,30 @@ pub const Pattern = struct {
block: bool,
};
/// Compile and own a set of URL wildcard patterns. Patterns are normalized to
/// lowercase once so request matching remains allocation-free.
/// Compile and own a set of URL wildcard patterns. Empty patterns (e.g. from
/// trailing or duplicate commas in `--block-urls`) are skipped so every entry
/// point behaves the same. Patterns are normalized to lowercase once so request
/// matching remains allocation-free.
pub fn init(allocator: std.mem.Allocator, patterns: []const []const u8) !UrlBlocklist {
const owned = try allocator.alloc([]const u8, patterns.len);
var count: usize = 0;
for (patterns) |pattern| {
if (pattern.len > 0) count += 1;
}
const owned = try allocator.alloc([]const u8, count);
var initialized: usize = 0;
errdefer {
for (owned[0..initialized]) |pattern| allocator.free(pattern);
allocator.free(owned);
}
for (patterns, owned) |pattern, *compiled| {
for (patterns) |pattern| {
if (pattern.len == 0) continue;
const normalized = try allocator.alloc(u8, pattern.len);
for (pattern, normalized) |char, *lower| {
lower.* = std.ascii.toLower(char);
}
compiled.* = normalized;
owned[initialized] = normalized;
initialized += 1;
}
@@ -52,14 +60,28 @@ pub fn init(allocator: std.mem.Allocator, patterns: []const []const u8) !UrlBloc
}
pub fn initPatterns(allocator: std.mem.Allocator, patterns: []const Pattern) !UrlBlocklist {
const urls = try allocator.alloc([]const u8, patterns.len);
var count: usize = 0;
for (patterns) |pattern| {
if (pattern.urlPattern.len > 0) count += 1;
}
// Pre-filter empty patterns here too so `blocks` stays aligned with the
// (also-filtered) patterns compiled by init().
const urls = try allocator.alloc([]const u8, count);
defer allocator.free(urls);
for (patterns, urls) |pattern, *url| url.* = pattern.urlPattern;
const blocks = try allocator.alloc(bool, count);
errdefer allocator.free(blocks);
{
var i: usize = 0;
for (patterns) |pattern| {
if (pattern.urlPattern.len == 0) continue;
urls[i] = pattern.urlPattern;
blocks[i] = pattern.block;
i += 1;
}
}
var blocklist = try init(allocator, urls);
errdefer blocklist.deinit();
const blocks = try allocator.alloc(bool, patterns.len);
for (patterns, blocks) |pattern, *block| block.* = pattern.block;
blocklist.blocks = blocks;
return blocklist;
}
@@ -131,14 +153,16 @@ test "UrlBlocklist: matches full URLs with case-insensitive wildcards" {
try std.testing.expect(!blocklist.isBlocked("https://example.com/exact/path"));
}
test "UrlBlocklist: wildcard matching backtracks and handles empty patterns" {
test "UrlBlocklist: wildcard matching backtracks and skips empty patterns" {
var blocklist = try UrlBlocklist.init(std.testing.allocator, &.{
"",
"**tracker***pixel*",
});
defer blocklist.deinit();
try std.testing.expect(blocklist.isBlocked(""));
// The empty pattern is dropped, so it never matches (not even "").
try std.testing.expectEqual(1, blocklist.patterns.len);
try std.testing.expect(!blocklist.isBlocked(""));
try std.testing.expect(blocklist.isBlocked("https://tracker.test/a/pixel.gif"));
try std.testing.expect(!blocklist.isBlocked("https://anything.test/"));
@@ -151,6 +175,22 @@ test "UrlBlocklist: wildcard matching backtracks and handles empty patterns" {
try std.testing.expect(!empty.isBlocked("https://example.com/"));
}
test "UrlBlocklist: initPatterns skips empty patterns and keeps blocks aligned" {
var blocklist = try UrlBlocklist.initPatterns(std.testing.allocator, &.{
.{ .urlPattern = "*allow*", .block = false },
.{ .urlPattern = "", .block = true },
.{ .urlPattern = "*", .block = true },
});
defer blocklist.deinit();
try std.testing.expectEqual(2, blocklist.patterns.len);
try std.testing.expectEqual(2, blocklist.blocks.?.len);
// The exemption (block = false) still wins over the trailing catch-all,
// proving the empty pattern was removed without shifting `blocks`.
try std.testing.expect(!blocklist.isBlocked("https://example.com/allow/me"));
try std.testing.expect(blocklist.isBlocked("https://example.com/other"));
}
test "UrlBlocklist: owns compiled patterns" {
var pattern = [_]u8{ '*', 'a', 'd', 's', '*' };
var blocklist = try UrlBlocklist.initPatterns(std.testing.allocator, &.{ .{ .urlPattern = &pattern, .block = false }, .{ .urlPattern = "*", .block = true } });