mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-08 20:32:00 -04:00
webapi: xhr/fetch report status reason from server
If the server gives a status reason, report it as-is. Only default to the zig code->reason map when one isn't given. Also, don't force a 407 status code when auth_challenge is present.
This commit is contained in:
6 files changed
+133
-6
No files matched your search
@@ -194,6 +194,46 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_reason_phrase type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
req.onload = () => { state.resolve() };
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/reason');
|
||||
req.send();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(503, req.status);
|
||||
testing.expectEqual('HOUSTON WE HAVE A', req.statusText);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_reason_phrase_sync>
|
||||
{
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/reason', false);
|
||||
req.send();
|
||||
testing.expectEqual(503, req.status);
|
||||
testing.expectEqual('HOUSTON WE HAVE A', req.statusText);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_401_challenge type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
const req = new XMLHttpRequest();
|
||||
req.onload = () => { state.resolve() };
|
||||
req.open('GET', 'http://127.0.0.1:9582/xhr/401');
|
||||
req.send();
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(401, req.status);
|
||||
testing.expectEqual('Unauthorized', req.statusText);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=xhr_abort type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
@@ -234,7 +234,10 @@ fn httpHeaderDoneCallback(transfer: *Transfer) !Transfer.HeaderResult {
|
||||
}
|
||||
|
||||
res._status = status;
|
||||
res._status_text = std.http.Status.phrase(@enumFromInt(status)) orelse "";
|
||||
res._status_text = if (transfer.statusText()) |st|
|
||||
try arena.allocator().dupe(u8, st)
|
||||
else
|
||||
std.http.Status.phrase(@enumFromInt(status)) orelse "";
|
||||
res._url = try arena.dupeZ(u8, transfer.req.url);
|
||||
res._is_redirected = transfer.redirectCount().? > 0;
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ _async: bool = true,
|
||||
_response: ?Response = null,
|
||||
_response_data: std.ArrayList(u8) = .empty,
|
||||
_response_status: u16 = 0,
|
||||
_response_status_text: ?[]const u8 = null,
|
||||
_response_len: ?usize = 0,
|
||||
_response_url: [:0]const u8 = "",
|
||||
_override_mime: ?Mime = null,
|
||||
@@ -230,6 +231,7 @@ pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8, async
|
||||
self._response_xml = null;
|
||||
self._response_data.clearRetainingCapacity();
|
||||
self._response_status = 0;
|
||||
self._response_status_text = null;
|
||||
self._response_len = 0;
|
||||
self._response_url = "";
|
||||
self._response_mime = null;
|
||||
@@ -397,6 +399,9 @@ pub fn send(self: *XMLHttpRequest, body_: ?BodyInit, exec_: *const Execution) !v
|
||||
defer self.releaseSelfRef();
|
||||
|
||||
self._response_status = resp.status;
|
||||
if (resp.status_text.get()) |st| {
|
||||
self._response_status_text = try self._arena.dupe(u8, st);
|
||||
}
|
||||
self._response_url = self._url;
|
||||
self._response_len = resp.body.items.len;
|
||||
|
||||
@@ -510,6 +515,9 @@ pub fn getStatus(self: *const XMLHttpRequest) u16 {
|
||||
}
|
||||
|
||||
fn getStatusText(self: *const XMLHttpRequest) []const u8 {
|
||||
if (self._response_status_text) |st| {
|
||||
return st;
|
||||
}
|
||||
return std.http.Status.phrase(@enumFromInt(self._response_status)) orelse "";
|
||||
}
|
||||
|
||||
@@ -660,6 +668,9 @@ fn httpHeaderDoneCallback(transfer: *Transfer) !Transfer.HeaderResult {
|
||||
}
|
||||
|
||||
self._response_status = transfer.responseStatus().?;
|
||||
if (transfer.statusText()) |st| {
|
||||
self._response_status_text = try self._arena.dupe(u8, st);
|
||||
}
|
||||
if (transfer.getContentLength()) |cl| {
|
||||
self._response_len = cl;
|
||||
}
|
||||
|
||||
@@ -1434,6 +1434,7 @@ const SyncContext = struct {
|
||||
} = .in_progress,
|
||||
|
||||
status: u16 = 0,
|
||||
status_text: http.StatusText = .{},
|
||||
body: std.ArrayList(u8),
|
||||
|
||||
// Acquired on the first byte we have to buffer, so a bodyless response
|
||||
@@ -1444,6 +1445,7 @@ const SyncContext = struct {
|
||||
const self: *SyncContext = @ptrCast(@alignCast(transfer.req.ctx));
|
||||
lp.assert(transfer.responseStatus() != null, "HttpClient.SyncRequest.headerCallback", .{ .value = transfer.responseStatus() });
|
||||
self.status = transfer.responseStatus().?;
|
||||
self.status_text = transfer.res.status_text;
|
||||
const body_len = transfer.bodyLen();
|
||||
if (body_len > 0) {
|
||||
try self.body.ensureTotalCapacityPrecise(try self.bodyAllocator(body_len), body_len);
|
||||
@@ -2112,6 +2114,7 @@ pub const Request = struct {
|
||||
const SyncResponse = struct {
|
||||
status: u16,
|
||||
body: std.ArrayList(u8),
|
||||
status_text: http.StatusText,
|
||||
|
||||
// Owns `body`. Null when the response had nothing to buffer. Callers that
|
||||
// keep `body` past this call take the arena instead of releasing it.
|
||||
@@ -2671,6 +2674,7 @@ pub const Transfer = struct {
|
||||
.in_progress => @panic("Impossible to be in progress here."),
|
||||
.done, .shutdown => return .{
|
||||
.status = sync_ctx.status,
|
||||
.status_text = sync_ctx.status_text,
|
||||
.body = sync_ctx.body,
|
||||
.arena = sync_ctx.arena,
|
||||
},
|
||||
@@ -3478,10 +3482,11 @@ pub const Transfer = struct {
|
||||
|
||||
const url = try conn.getEffectiveUrl();
|
||||
|
||||
const status: u16 = if (self._auth_challenge != null)
|
||||
407
|
||||
else
|
||||
try conn.getResponseCode();
|
||||
var status = try conn.getResponseCode();
|
||||
if (status == 0 and self._auth_challenge != null) {
|
||||
// A proxy that refuses the CONNECT gives us no status code
|
||||
status = try conn.getConnectCode();
|
||||
}
|
||||
|
||||
self.res.header = .{
|
||||
.url = url,
|
||||
@@ -3831,7 +3836,14 @@ pub const Transfer = struct {
|
||||
std.debug.assert(chunk_count == 1);
|
||||
}
|
||||
|
||||
if (announcesBody(buffer[0..chunk_len]) == false) {
|
||||
const line = buffer[0..chunk_len];
|
||||
if (std.mem.startsWith(u8, line, "HTTP/")) {
|
||||
const conn: *http.Connection = @ptrCast(@alignCast(data));
|
||||
conn.transport.http.res.status_text = .fromStatusLine(line);
|
||||
return chunk_len;
|
||||
}
|
||||
|
||||
if (announcesBody(line) == false) {
|
||||
return chunk_len;
|
||||
}
|
||||
|
||||
@@ -4010,6 +4022,10 @@ pub const Transfer = struct {
|
||||
return rh.status;
|
||||
}
|
||||
|
||||
pub fn statusText(self: *const Transfer) ?[]const u8 {
|
||||
return self.res.status_text.get();
|
||||
}
|
||||
|
||||
pub fn contentType(self: *Transfer) ?[]const u8 {
|
||||
if (self.res.header) |*rh| {
|
||||
return rh.contentType();
|
||||
@@ -4251,6 +4267,10 @@ const ResourceTiming = struct {
|
||||
const Response = struct {
|
||||
header: ?http.ResponseHead = null,
|
||||
|
||||
// From the most recent status line curl reported (a CONNECT, a 1xx or
|
||||
// an auth challenge can precede the final one).
|
||||
status_text: http.StatusText = .{},
|
||||
|
||||
// Full response headers, materialized into the transfer arena at
|
||||
// completion (or set directly by cache / synthetic / fulfill). Names are
|
||||
// lowercased.
|
||||
|
||||
@@ -228,6 +228,32 @@ pub const AuthChallenge = struct {
|
||||
}
|
||||
};
|
||||
|
||||
// The actual reason phrase from the server, verbatim. HTTP/2 has none, so "".
|
||||
pub const StatusText = struct {
|
||||
pub const MAX_LEN = 128;
|
||||
|
||||
_len: ?u8 = null,
|
||||
_buf: [MAX_LEN]u8 = undefined,
|
||||
|
||||
pub fn fromStatusLine(line: []const u8) StatusText {
|
||||
const trimmed = std.mem.trimEnd(u8, line, "\r\n");
|
||||
// HTTP-version SP status-code SP [ reason-phrase ]
|
||||
const sp1 = std.mem.indexOfScalar(u8, trimmed, ' ') orelse return .{ ._len = 0 };
|
||||
const sp2 = std.mem.indexOfScalarPos(u8, trimmed, sp1 + 1, ' ') orelse return .{ ._len = 0 };
|
||||
const phrase = trimmed[sp2 + 1 ..];
|
||||
const len = @min(phrase.len, MAX_LEN);
|
||||
|
||||
var st: StatusText = .{ ._len = @intCast(len) };
|
||||
@memcpy(st._buf[0..len], phrase[0..len]);
|
||||
return st;
|
||||
}
|
||||
|
||||
pub fn get(self: *const StatusText) ?[]const u8 {
|
||||
const len = self._len orelse return null;
|
||||
return self._buf[0..len];
|
||||
}
|
||||
};
|
||||
|
||||
pub const ResponseHead = struct {
|
||||
// Matches Mime.parse's 255-byte cap
|
||||
pub const MAX_CONTENT_TYPE_LEN = 255;
|
||||
@@ -1041,6 +1067,17 @@ test "Header.param" {
|
||||
try testing.expect((Header{ .name = "Content-Disposition", .value = "attachment; filename=\"\"" }).param("filename") == null);
|
||||
}
|
||||
|
||||
test "StatusText.fromStatusLine" {
|
||||
try testing.expect((StatusText{}).get() == null);
|
||||
try testing.expectEqualSlices(u8, "OK", StatusText.fromStatusLine("HTTP/1.1 200 OK\r\n").get().?);
|
||||
try testing.expectEqualSlices(u8, "HOUSTON WE HAVE A", StatusText.fromStatusLine("HTTP/1.1 503 HOUSTON WE HAVE A\r\n").get().?);
|
||||
try testing.expectEqualSlices(u8, "lowercase", StatusText.fromStatusLine("HTTP/1.0 502 lowercase\r\n").get().?);
|
||||
// curl's synthesized HTTP/2 status line has no phrase
|
||||
try testing.expectEqualSlices(u8, "", StatusText.fromStatusLine("HTTP/2 200 \r\n").get().?);
|
||||
try testing.expectEqualSlices(u8, "", StatusText.fromStatusLine("HTTP/1.1 200\r\n").get().?);
|
||||
try testing.expectEqual(StatusText.MAX_LEN, StatusText.fromStatusLine("HTTP/1.1 200 " ++ "x" ** 200).get().?.len);
|
||||
}
|
||||
|
||||
test "opensocketCallback: private IPv4 returns CURL_SOCKET_BAD" {
|
||||
testing.silenceLog(&.{.http});
|
||||
|
||||
|
||||
@@ -884,6 +884,22 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
|
||||
return req.respond("", .{ .status = @enumFromInt(code) });
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/xhr/reason")) {
|
||||
return req.respond("", .{
|
||||
.status = .service_unavailable,
|
||||
.reason = "HOUSTON WE HAVE A",
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/xhr/401")) {
|
||||
return req.respond("No", .{
|
||||
.status = .unauthorized,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "WWW-Authenticate", .value = "Basic realm=\"test\"" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/xhr/500")) {
|
||||
return req.respond("Internal Server Error", .{
|
||||
.status = .internal_server_error,
|
||||
|
||||
Reference in new issue
Block a user