mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-30 17:25:58 -04:00
nits: just personalizing the code as a means to understand it.
This commit is contained in:
@@ -223,12 +223,6 @@ pub fn collectStructuredData(
|
||||
};
|
||||
}
|
||||
|
||||
/// Registered link relations we surface from the HTTP `Link:` response header
|
||||
/// (RFC 8288). `service-doc`/`service-desc` come from RFC 8631; `api` is in the
|
||||
/// IANA Link Relations registry. These let an agent discover a site's developer
|
||||
/// docs / API description without scraping.
|
||||
const header_link_rels = [_][]const u8{ "service-doc", "service-desc", "api" };
|
||||
|
||||
/// Parse every `Link:` response header retained on the frame, surfacing only the
|
||||
/// registered, agent-useful relations. Relative targets are resolved against the
|
||||
/// frame's base URL, mirroring `collectLink`.
|
||||
@@ -237,8 +231,14 @@ fn collectLinkHeaders(
|
||||
frame: *Frame,
|
||||
out: *std.ArrayList(LinkRel),
|
||||
) !void {
|
||||
// Registered link relations we surface from the HTTP `Link:` response header
|
||||
// (RFC 8288).
|
||||
const header_link_rels = [_][]const u8{ "service-doc", "service-desc", "api" };
|
||||
|
||||
for (frame._http_headers.items) |header| {
|
||||
if (!std.ascii.eqlIgnoreCase(header.name, "link")) continue;
|
||||
if (!std.ascii.eqlIgnoreCase(header.name, "link")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var it: LinkHeaderIterator = .{ .value = header.value };
|
||||
while (it.next()) |entry| {
|
||||
@@ -248,17 +248,21 @@ fn collectLinkHeaders(
|
||||
var rel_it = std.mem.tokenizeAny(u8, rel_value, " \t");
|
||||
while (rel_it.next()) |rel_token| {
|
||||
const known = for (header_link_rels) |candidate| {
|
||||
if (std.ascii.eqlIgnoreCase(rel_token, candidate)) break candidate;
|
||||
if (std.ascii.eqlIgnoreCase(rel_token, candidate)) {
|
||||
break candidate;
|
||||
}
|
||||
} else continue;
|
||||
|
||||
const href = URL.resolve(arena, frame.base(), entry.target, .{ .encoding = frame.charset }) catch entry.target;
|
||||
|
||||
// Drop duplicate (rel, href) pairs — a target can legitimately
|
||||
// carry the same relation across repeated headers.
|
||||
const dup = for (out.items) |existing| {
|
||||
if (std.mem.eql(u8, existing.rel, known) and std.mem.eql(u8, existing.href, href)) break true;
|
||||
} else false;
|
||||
if (!dup) try out.append(arena, .{ .rel = known, .href = href });
|
||||
// Drop duplicate (rel, href) pairs (it happens)
|
||||
for (out.items) |existing| {
|
||||
if (std.mem.eql(u8, existing.rel, known) and std.mem.eql(u8, existing.href, href)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
try out.append(arena, .{ .rel = known, .href = href });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,25 +278,30 @@ const LinkHeaderEntry = struct {
|
||||
/// skipping commas that fall inside the `<...>` target or a `"..."` quoted
|
||||
/// parameter value (RFC 8288 §3).
|
||||
const LinkHeaderIterator = struct {
|
||||
value: []const u8,
|
||||
i: usize = 0,
|
||||
value: []const u8,
|
||||
|
||||
fn next(self: *LinkHeaderIterator) ?LinkHeaderEntry {
|
||||
const v = self.value;
|
||||
|
||||
// Loop (rather than recurse) over malformed segments so stack usage is
|
||||
// independent of the attacker-controlled header length.
|
||||
while (true) {
|
||||
// Skip separators and whitespace between link-values. The comma that
|
||||
// ended the previous link-value is consumed here.
|
||||
while (self.i < v.len and (std.ascii.isWhitespace(v[self.i]) or v[self.i] == ',')) : (self.i += 1) {}
|
||||
if (self.i >= v.len) return null;
|
||||
for (v[self.i..]) |c| {
|
||||
if (std.ascii.isWhitespace(c) == false and c != ',') {
|
||||
break;
|
||||
}
|
||||
self.i += 1;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// A link-value must begin with the angle-bracketed target.
|
||||
if (v[self.i] != '<') {
|
||||
self.skipToNextComma();
|
||||
continue;
|
||||
}
|
||||
|
||||
const target_start = self.i + 1;
|
||||
const gt = std.mem.indexOfScalarPos(u8, v, target_start, '>') orelse {
|
||||
self.i = v.len;
|
||||
@@ -316,13 +325,18 @@ const LinkHeaderIterator = struct {
|
||||
var in_quotes = false;
|
||||
while (self.i < v.len) : (self.i += 1) {
|
||||
const c = v[self.i];
|
||||
|
||||
if (c == ',' and in_quotes == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\\' and in_quotes) {
|
||||
// Skip the escaped byte; guard a trailing backslash.
|
||||
if (self.i + 1 < v.len) self.i += 1;
|
||||
if (self.i + 1 < v.len) {
|
||||
self.i += 1;
|
||||
}
|
||||
} else if (c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
} else if (c == ',' and !in_quotes) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,8 +348,11 @@ fn extractRel(params: []const u8) ?[]const u8 {
|
||||
var it = std.mem.splitScalar(u8, params, ';');
|
||||
while (it.next()) |raw_param| {
|
||||
const param = std.mem.trim(u8, raw_param, &std.ascii.whitespace);
|
||||
const eq = std.mem.indexOfScalar(u8, param, '=') orelse continue;
|
||||
if (!std.ascii.eqlIgnoreCase(std.mem.trim(u8, param[0..eq], &std.ascii.whitespace), "rel")) continue;
|
||||
const eq = std.mem.indexOfScalarPos(u8, param, 0, '=') orelse continue;
|
||||
const name = std.mem.trim(u8, param[0..eq], &std.ascii.whitespace);
|
||||
if (std.ascii.eqlIgnoreCase(name, "rel") == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var val = std.mem.trim(u8, param[eq + 1 ..], &std.ascii.whitespace);
|
||||
if (val.len >= 2 and val[0] == '"' and val[val.len - 1] == '"') {
|
||||
|
||||
@@ -87,6 +87,9 @@ pub const ContentSignal = struct {
|
||||
pub const Robots = @This();
|
||||
pub const empty: Robots = .{ .rules = &.{}, .content_signals = &.{} };
|
||||
|
||||
// Think twice before deleting/freeing any entries from the map. Readers, e.g.
|
||||
// get and getContentSignals, receive values from the map, and if another thread
|
||||
// was to delete / free those values while in use, UAF.
|
||||
pub const RobotStore = struct {
|
||||
const RobotsEntry = union(enum) {
|
||||
present: Robots,
|
||||
@@ -165,10 +168,7 @@ pub const RobotStore = struct {
|
||||
try self.map.put(self.allocator, duped, .{ .present = robots });
|
||||
}
|
||||
|
||||
/// Advisory `Content-Signal` preferences declared for `url`'s host, or null
|
||||
/// when no robots.txt has been fetched/stored for it. The returned slice is
|
||||
/// owned by the store; callers must read it synchronously (and dupe to keep
|
||||
/// it) and must never free it.
|
||||
// The returned slice is owned by the store
|
||||
pub fn getContentSignals(self: *RobotStore, url: []const u8) ?[]const ContentSignal {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
@@ -237,14 +237,17 @@ fn appendContentSignals(
|
||||
var iter = std.mem.splitScalar(u8, value, ',');
|
||||
while (iter.next()) |raw_pair| {
|
||||
const pair = std.mem.trim(u8, raw_pair, &std.ascii.whitespace);
|
||||
const eq = std.mem.indexOfScalar(u8, pair, '=') orelse continue;
|
||||
const eq = std.mem.indexOfScalarPos(u8, pair, 0, '=') orelse continue;
|
||||
|
||||
const name_raw = std.mem.trim(u8, pair[0..eq], &std.ascii.whitespace);
|
||||
const value_raw = std.mem.trim(u8, pair[eq + 1 ..], &std.ascii.whitespace);
|
||||
if (name_raw.len == 0) continue;
|
||||
if (name_raw.len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = try std.ascii.allocLowerString(allocator, name_raw);
|
||||
errdefer allocator.free(name);
|
||||
|
||||
const val = try std.ascii.allocLowerString(allocator, value_raw);
|
||||
errdefer allocator.free(val);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user