Files
bun.sh/src/http/Headers.zig
taylor.fish 437e15bae5 Replace catch bun.outOfMemory() with safer alternatives (#22141)
Replace `catch bun.outOfMemory()`, which can accidentally catch
non-OOM-related errors, with either `bun.handleOom` or a manual `catch
|err| switch (err)`.

(For internal tracking: fixes STAB-1070)

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-08-26 12:50:25 -07:00

186 lines
6.0 KiB
Zig

const Headers = @This();
pub const Entry = struct {
name: api.StringPointer,
value: api.StringPointer,
pub const List = bun.MultiArrayList(Entry);
};
entries: Entry.List = .{},
buf: std.ArrayListUnmanaged(u8) = .{},
allocator: std.mem.Allocator,
pub fn memoryCost(this: *const Headers) usize {
return this.buf.items.len + this.entries.memoryCost();
}
pub fn clone(this: *Headers) !Headers {
return Headers{
.entries = try this.entries.clone(this.allocator),
.buf = try this.buf.clone(this.allocator),
.allocator = this.allocator,
};
}
pub fn get(this: *const Headers, name: []const u8) ?[]const u8 {
const entries = this.entries.slice();
const names = entries.items(.name);
const values = entries.items(.value);
for (names, 0..) |name_ptr, i| {
if (bun.strings.eqlCaseInsensitiveASCII(this.asStr(name_ptr), name, true)) {
return this.asStr(values[i]);
}
}
return null;
}
pub fn append(this: *Headers, name: []const u8, value: []const u8) !void {
var offset: u32 = @truncate(this.buf.items.len);
try this.buf.ensureUnusedCapacity(this.allocator, name.len + value.len);
const name_ptr = api.StringPointer{
.offset = offset,
.length = @truncate(name.len),
};
this.buf.appendSliceAssumeCapacity(name);
offset = @truncate(this.buf.items.len);
this.buf.appendSliceAssumeCapacity(value);
const value_ptr = api.StringPointer{
.offset = offset,
.length = @truncate(value.len),
};
try this.entries.append(this.allocator, .{
.name = name_ptr,
.value = value_ptr,
});
}
pub fn deinit(this: *Headers) void {
this.entries.deinit(this.allocator);
this.buf.clearAndFree(this.allocator);
}
pub fn getContentType(this: *const Headers) ?[]const u8 {
if (this.entries.len == 0 or this.buf.items.len == 0) {
return null;
}
const header_entries = this.entries.slice();
const header_names = header_entries.items(.name);
const header_values = header_entries.items(.value);
for (header_names, 0..header_names.len) |name, i| {
if (bun.strings.eqlCaseInsensitiveASCII(this.asStr(name), "content-type", true)) {
return this.asStr(header_values[i]);
}
}
return null;
}
pub fn asStr(this: *const Headers, ptr: api.StringPointer) []const u8 {
return if (ptr.offset + ptr.length <= this.buf.items.len)
this.buf.items[ptr.offset..][0..ptr.length]
else
"";
}
pub const Options = struct {
body: ?*const Blob.Any = null,
};
pub fn fromPicoHttpHeaders(headers: []const picohttp.Header, allocator: std.mem.Allocator) !Headers {
const header_count = headers.len;
var result = Headers{
.entries = .{},
.buf = .{},
.allocator = allocator,
};
var buf_len: usize = 0;
for (headers) |header| {
buf_len += header.name.len + header.value.len;
}
bun.handleOom(result.entries.ensureTotalCapacity(allocator, header_count));
result.entries.len = headers.len;
bun.handleOom(result.buf.ensureTotalCapacityPrecise(allocator, buf_len));
result.buf.items.len = buf_len;
var offset: u32 = 0;
for (headers, 0..headers.len) |header, i| {
const name_offset = offset;
bun.copy(u8, result.buf.items[offset..][0..header.name.len], header.name);
offset += @truncate(header.name.len);
const value_offset = offset;
bun.copy(u8, result.buf.items[offset..][0..header.value.len], header.value);
offset += @truncate(header.value.len);
result.entries.set(i, .{
.name = .{
.offset = name_offset,
.length = @truncate(header.name.len),
},
.value = .{
.offset = value_offset,
.length = @truncate(header.value.len),
},
});
}
return result;
}
pub fn from(fetch_headers_ref: ?*FetchHeaders, allocator: std.mem.Allocator, options: Options) !Headers {
var header_count: u32 = 0;
var buf_len: u32 = 0;
if (fetch_headers_ref) |headers_ref|
headers_ref.count(&header_count, &buf_len);
var headers = Headers{
.entries = .{},
.buf = .{},
.allocator = allocator,
};
const buf_len_before_content_type = buf_len;
const needs_content_type = brk: {
if (options.body) |body| {
if (body.hasContentTypeFromUser() and (fetch_headers_ref == null or !fetch_headers_ref.?.fastHas(.ContentType))) {
header_count += 1;
buf_len += @as(u32, @truncate(body.contentType().len + "Content-Type".len));
break :brk true;
}
}
break :brk false;
};
bun.handleOom(headers.entries.ensureTotalCapacity(allocator, header_count));
headers.entries.len = header_count;
bun.handleOom(headers.buf.ensureTotalCapacityPrecise(allocator, buf_len));
headers.buf.items.len = buf_len;
var sliced = headers.entries.slice();
var names = sliced.items(.name);
var values = sliced.items(.value);
if (fetch_headers_ref) |headers_ref|
headers_ref.copyTo(names.ptr, values.ptr, headers.buf.items.ptr);
// TODO: maybe we should send Content-Type header first instead of last?
if (needs_content_type) {
bun.copy(u8, headers.buf.items[buf_len_before_content_type..], "Content-Type");
names[header_count - 1] = .{
.offset = buf_len_before_content_type,
.length = "Content-Type".len,
};
bun.copy(u8, headers.buf.items[buf_len_before_content_type + "Content-Type".len ..], options.body.?.contentType());
values[header_count - 1] = .{
.offset = buf_len_before_content_type + @as(u32, "Content-Type".len),
.length = @as(u32, @truncate(options.body.?.contentType().len)),
};
}
return headers;
}
const std = @import("std");
const bun = @import("bun");
const picohttp = bun.picohttp;
const api = bun.schema.api;
const Blob = bun.webcore.Blob;
const FetchHeaders = bun.webcore.FetchHeaders;