mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
http: inherit request URL fragment across fragment-less redirect
Per RFC 7231 §7.1.2, when a 3xx response carries a Location header without a fragment, the user agent must process the redirect as if the value inherited the fragment of the request URL. URL.resolve follows RFC 3986 §5.3 which drops the base fragment, so handleRedirect now reattaches the original fragment when the resolved target has none. Closes #2263
This commit is contained in:
@@ -1572,7 +1572,19 @@ pub const Transfer = struct {
|
||||
}
|
||||
|
||||
const base_url = try conn.getEffectiveUrl();
|
||||
break :blk try URL.resolve(arena, std.mem.span(base_url), location.value, .{});
|
||||
const resolved = try URL.resolve(arena, std.mem.span(base_url), location.value, .{});
|
||||
|
||||
// RFC 7231 §7.1.2: if the Location value has no fragment, the redirect
|
||||
// inherits the fragment from the URI used to generate the request.
|
||||
// URL.resolve follows RFC 3986 §5.3, which drops the base fragment when
|
||||
// the relative ref has none, so we re-attach it here.
|
||||
if (URL.getHash(resolved).len == 0) {
|
||||
const original_hash = URL.getHash(transfer.url);
|
||||
if (original_hash.len != 0) {
|
||||
break :blk try std.mem.joinZ(arena, "", &.{ resolved, original_hash });
|
||||
}
|
||||
}
|
||||
break :blk resolved;
|
||||
};
|
||||
|
||||
try transfer.updateURL(url);
|
||||
|
||||
@@ -1003,6 +1003,60 @@ test "cdp.frame: reload" {
|
||||
}
|
||||
}
|
||||
|
||||
test "cdp.frame: navigate inherits original fragment across redirect" {
|
||||
// RFC 7231 §7.1.2: when a 3xx Location header has no fragment, the redirect
|
||||
// inherits the fragment of the request URL.
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
var bc = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
||||
|
||||
{
|
||||
// Location: /redirect-target (no fragment) — must inherit #myfrag.
|
||||
try ctx.processMessage(.{
|
||||
.id = 40,
|
||||
.method = "Page.navigate",
|
||||
.params = .{ .url = "http://127.0.0.1:9582/redirect-no-fragment#myfrag" },
|
||||
});
|
||||
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
|
||||
const frame = bc.session.currentFrame() orelse unreachable;
|
||||
try testing.expectEqualSlices(u8, "http://127.0.0.1:9582/redirect-target#myfrag", frame.url);
|
||||
}
|
||||
|
||||
{
|
||||
// Location: /redirect-target#target_fragment — target's fragment wins.
|
||||
try ctx.processMessage(.{
|
||||
.id = 41,
|
||||
.method = "Page.navigate",
|
||||
.params = .{ .url = "http://127.0.0.1:9582/redirect-with-fragment#requested" },
|
||||
});
|
||||
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
|
||||
const frame = bc.session.currentFrame() orelse unreachable;
|
||||
try testing.expectEqualSlices(u8, "http://127.0.0.1:9582/redirect-target#target_fragment", frame.url);
|
||||
}
|
||||
|
||||
{
|
||||
// No fragment on either side — final URL has no fragment.
|
||||
try ctx.processMessage(.{
|
||||
.id = 42,
|
||||
.method = "Page.navigate",
|
||||
.params = .{ .url = "http://127.0.0.1:9582/redirect-no-fragment" },
|
||||
});
|
||||
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
|
||||
const frame = bc.session.currentFrame() orelse unreachable;
|
||||
try testing.expectEqualSlices(u8, "http://127.0.0.1:9582/redirect-target", frame.url);
|
||||
}
|
||||
}
|
||||
|
||||
test "cdp.frame: addScriptToEvaluateOnNewDocument" {
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
@@ -610,6 +610,32 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/redirect-no-fragment")) {
|
||||
return req.respond("", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "Location", .value = "/redirect-target" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/redirect-target")) {
|
||||
return req.respond("<!DOCTYPE html><title>landed</title>", .{
|
||||
.extra_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/redirect-with-fragment")) {
|
||||
return req.respond("", .{
|
||||
.status = .found,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "Location", .value = "/redirect-target#target_fragment" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/xhr/404")) {
|
||||
return req.respond("Not Found", .{
|
||||
.status = .not_found,
|
||||
|
||||
Reference in New Issue
Block a user