milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
+47
View File
@@ -134,6 +134,34 @@ pub fn fromText(text: []const u8) FromTextError!Name {
return name;
}
pub const NormalizeError = error{BadName};
/// Lowercases over ASCII into `buf`, strips one trailing dot, and checks the
/// result is a name `fromText` accepts. Returns the normalized text, borrowed
/// from `buf`.
///
/// A byte ≥ 0x80 is rejected: query names arrive ASCII-lowercased, so a high
/// byte here could never be matched, and configuration that can never match is
/// worth reporting rather than storing. The root name is rejected for the same
/// reason — a configured entry that matches everything, or nothing, is not what
/// either caller means.
///
/// `filter/rules.zig`, `filter/compiler.zig` and `cache/dns_cache.zig` keep
/// their own variants on purpose; each says why beside its copy.
pub fn normalizeText(text: []const u8, buf: *[types.max_name_len]u8) NormalizeError![]const u8 {
var rest = text;
if (rest.len > 0 and rest[rest.len - 1] == '.') rest = rest[0 .. rest.len - 1];
if (rest.len == 0 or rest.len > types.max_name_len) return error.BadName;
for (rest, 0..) |byte, i| {
if (byte >= 0x80) return error.BadName;
buf[i] = std.ascii.toLower(byte);
}
const normalized = buf[0..rest.len];
_ = fromText(normalized) catch return error.BadName;
return normalized;
}
/// Writes presentation form: labels joined by dots, no trailing dot. The root
/// name writes as ".".
pub fn formatText(name: Name, w: *Writer) Writer.Error!void {
@@ -392,3 +420,22 @@ test "labelCount" {
try testing.expectEqual(@as(usize, 1), (try fromText("com")).labelCount());
try testing.expectEqual(@as(usize, 3), (try fromText("www.example.com")).labelCount());
}
test "normalizeText lowercases and strips one trailing dot" {
var buf: [types.max_name_len]u8 = undefined;
try testing.expectEqualStrings("example.com", try normalizeText("Example.COM", &buf));
try testing.expectEqualStrings("example.com", try normalizeText("example.com.", &buf));
try testing.expectEqualStrings("a", try normalizeText("A", &buf));
}
test "normalizeText rejects the root, high bytes and names fromText refuses" {
var buf: [types.max_name_len]u8 = undefined;
try testing.expectError(error.BadName, normalizeText("", &buf));
try testing.expectError(error.BadName, normalizeText(".", &buf));
try testing.expectError(error.BadName, normalizeText("caf\xc3\xa9.example.com", &buf));
try testing.expectError(error.BadName, normalizeText("a..b", &buf));
try testing.expectError(error.BadName, normalizeText("a." ** 200 ++ "com", &buf));
// A label of 64 bytes is one over the wire limit.
try testing.expectError(error.BadName, normalizeText("a" ** 64 ++ ".com", &buf));
}