From f6397e2731be15c2c6d34bf156f080c237881652 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 22 Jan 2026 14:15:00 +0800 Subject: [PATCH] Handle scripts that don't return a 200 status code This was already being handled for async scripts, but for sync scripts, we'd log the error then proceed to try and execute the body (which would be some error message). This allows the header_callback to return a boolean to indicate whether or not the http client should continue to process the request or abort it. --- src/browser/Page.zig | 4 ++- src/browser/ScriptManager.zig | 17 +++++++----- src/browser/webapi/net/Fetch.zig | 4 ++- src/browser/webapi/net/XMLHttpRequest.zig | 7 +++-- src/http/Client.zig | 33 ++++++++++++++++------- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index b5bc29af..253642fd 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -590,7 +590,7 @@ fn _documentIsComplete(self: *Page) !void { ); } -fn pageHeaderDoneCallback(transfer: *Http.Transfer) !void { +fn pageHeaderDoneCallback(transfer: *Http.Transfer) !bool { var self: *Page = @ptrCast(@alignCast(transfer.ctx)); // would be different than self.url in the case of a redirect @@ -607,6 +607,8 @@ fn pageHeaderDoneCallback(transfer: *Http.Transfer) !void { .content_type = header.contentType(), }); } + + return true; } fn pageDataCallback(transfer: *Http.Transfer, data: []const u8) !void { diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index aa0b7dc6..b2b64b86 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -663,7 +663,7 @@ pub const Script = struct { log.debug(.http, "script fetch start", .{ .req = transfer }); } - fn headerCallback(transfer: *Http.Transfer) !void { + fn headerCallback(transfer: *Http.Transfer) !bool { const self: *Script = @ptrCast(@alignCast(transfer.ctx)); const header = &transfer.response_header.?; self.status = header.status; @@ -673,7 +673,7 @@ pub const Script = struct { .status = header.status, .content_type = header.contentType(), }); - return; + return false; } if (comptime IS_DEBUG) { @@ -694,6 +694,7 @@ pub const Script = struct { try buffer.ensureTotalCapacity(self.manager.allocator, cl); } self.source = .{ .remote = buffer }; + return true; } fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void { @@ -733,7 +734,7 @@ pub const Script = struct { log.warn(.http, "script fetch error", .{ .err = err, .req = self.url, - .mode = self.mode, + .mode = std.meta.activeTag(self.mode), .kind = self.kind, .status = self.status, }); @@ -753,9 +754,13 @@ pub const Script = struct { return; } - if (self.mode == .import) { - const entry = self.manager.imported_modules.getPtr(self.url).?; - entry.state = .err; + switch (self.mode) { + .import_async => |ia| ia.callback(ia.data, error.FailedToLoad), + .import => { + const entry = manager.imported_modules.getPtr(self.url).?; + entry.state = .err; + }, + else => {}, } self.deinit(true); manager.evaluate(); diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index b5e3b384..98aaf1a4 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -89,7 +89,7 @@ pub fn deinit(self: *Fetch) void { } } -fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void { +fn httpHeaderDoneCallback(transfer: *Http.Transfer) !bool { const self: *Fetch = @ptrCast(@alignCast(transfer.ctx)); if (transfer.getContentLength()) |cl| { @@ -133,6 +133,8 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void { while (it.next()) |hdr| { try res._headers.append(hdr.name, hdr.value, self._page); } + + return true; } fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void { diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index a2dec743..c61a2760 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -290,7 +290,7 @@ fn httpHeaderCallback(transfer: *Http.Transfer, header: Http.Header) !void { try self._response_headers.append(self._arena, joined); } -fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void { +fn httpHeaderDoneCallback(transfer: *Http.Transfer) !bool { const self: *XMLHttpRequest = @ptrCast(@alignCast(transfer.ctx)); const header = &transfer.response_header.?; @@ -305,7 +305,8 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void { if (header.contentType()) |ct| { self._response_mime = Mime.parse(ct) catch |e| { - return self.handleError(e); + log.info(.http, "invalid content type", .{.content_Type = ct, .err = e, .url = self._url,}); + return false; }; } @@ -332,6 +333,8 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void { try self.stateChanged(.headers_received, local, page); try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, local, page); try self.stateChanged(.loading, local, page); + + return true; } fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void { diff --git a/src/http/Client.zig b/src/http/Client.zig index c76a355e..a0f5ee81 100644 --- a/src/http/Client.zig +++ b/src/http/Client.zig @@ -594,15 +594,19 @@ fn processMessages(self: *Client) !bool { defer transfer.deinit(); - if (errorCheck(msg.data.result)) { + if (errorCheck(msg.data.result)) blk: { // In case of request w/o data, we need to call the header done // callback now. if (!transfer._header_done_called) { - transfer.headerDoneCallback(easy) catch |err| { + const proceed = transfer.headerDoneCallback(easy) catch |err| { log.err(.http, "header_done_callback", .{ .err = err }); self.requestFailed(transfer, err); continue; }; + if (!proceed) { + self.requestFailed(transfer, error.Abort); + break :blk; + } } transfer.req.done_callback(transfer.ctx) catch |err| { // transfer isn't valid at this point, don't use it. @@ -771,7 +775,7 @@ pub const Request = struct { ctx: *anyopaque = undefined, start_callback: ?*const fn (transfer: *Transfer) anyerror!void = null, - header_callback: *const fn (transfer: *Transfer) anyerror!void, + header_callback: *const fn (transfer: *Transfer) anyerror!bool, data_callback: *const fn (transfer: *Transfer, data: []const u8) anyerror!void, done_callback: *const fn (ctx: *anyopaque) anyerror!void, error_callback: *const fn (ctx: *anyopaque, err: anyerror) void, @@ -1042,7 +1046,7 @@ pub const Transfer = struct { // headerDoneCallback is called once the headers have been read. // It can be called either on dataCallback or once the request for those // w/o body. - fn headerDoneCallback(transfer: *Transfer, easy: *c.CURL) !void { + fn headerDoneCallback(transfer: *Transfer, easy: *c.CURL) !bool { if (comptime IS_DEBUG) { std.debug.assert(transfer._header_done_called == false); } @@ -1070,7 +1074,7 @@ pub const Transfer = struct { if (i >= ct.?.amount) break; } - transfer.req.header_callback(transfer) catch |err| { + const proceed = transfer.req.header_callback(transfer) catch |err| { log.err(.http, "header_callback", .{ .err = err, .req = transfer }); return err; }; @@ -1080,6 +1084,8 @@ pub const Transfer = struct { .transfer = transfer, }); } + + return proceed; } // headerCallback is called by curl on each request's header line read. @@ -1193,7 +1199,7 @@ pub const Transfer = struct { return buf_len; } - fn dataCallback(buffer: [*]const u8, chunk_count: usize, chunk_len: usize, data: *anyopaque) callconv(.c) usize { + fn dataCallback(buffer: [*]const u8, chunk_count: usize, chunk_len: usize, data: *anyopaque) callconv(.c) isize { // libcurl should only ever emit 1 chunk at a time if (comptime IS_DEBUG) { std.debug.assert(chunk_count == 1); @@ -1206,14 +1212,18 @@ pub const Transfer = struct { }; if (transfer._redirecting or transfer._auth_challenge != null) { - return chunk_len; + return @intCast(chunk_len); } if (!transfer._header_done_called) { - transfer.headerDoneCallback(easy) catch |err| { + const proceed = transfer.headerDoneCallback(easy) catch |err| { log.err(.http, "header_done_callback", .{ .err = err, .req = transfer }); return c.CURL_WRITEFUNC_ERROR; }; + if (!proceed) { + // signal abort to libcurl + return -1; + } } transfer.bytes_received += chunk_len; @@ -1230,7 +1240,7 @@ pub const Transfer = struct { }); } - return chunk_len; + return @intCast(chunk_len); } pub fn responseHeaderIterator(self: *Transfer) HeaderIterator { @@ -1288,7 +1298,10 @@ pub const Transfer = struct { } } - try req.header_callback(transfer); + if (try req.header_callback(transfer) == false) { + transfer.abort(error.Abort); + return; + } if (body) |b| { try req.data_callback(transfer, b);