aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js')
-rw-r--r--src/bun.js/api/JSBundler.zig7
-rw-r--r--src/bun.js/api/JSTranspiler.zig7
-rw-r--r--src/bun.js/javascript.zig1
-rw-r--r--src/bun.js/node/types.zig18
-rw-r--r--src/bun.js/webcore/blob.zig49
-rw-r--r--src/bun.js/webcore/response.zig323
6 files changed, 337 insertions, 68 deletions
diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig
index 741d956bf..8e85f1190 100644
--- a/src/bun.js/api/JSBundler.zig
+++ b/src/bun.js/api/JSBundler.zig
@@ -61,6 +61,7 @@ pub const JSBundler = struct {
code_splitting: bool = false,
minify: Minify = .{},
server_components: ServerComponents = ServerComponents{},
+ no_macros: bool = false,
names: Names = .{},
external: bun.StringSet = bun.StringSet.init(bun.default_allocator),
@@ -188,6 +189,12 @@ pub const JSBundler = struct {
}
}
+ if (config.getTruthy(globalThis, "macros")) |macros_flag| {
+ if (!macros_flag.coerce(bool, globalThis)) {
+ this.no_macros = true;
+ }
+ }
+
if (try config.getOptionalEnum(globalThis, "target", options.Target)) |target| {
this.target = target;
}
diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig
index 8a59f59e7..c58029c5e 100644
--- a/src/bun.js/api/JSTranspiler.zig
+++ b/src/bun.js/api/JSTranspiler.zig
@@ -75,6 +75,7 @@ const TranspilerOptions = struct {
minify_whitespace: bool = false,
minify_identifiers: bool = false,
minify_syntax: bool = false,
+ no_macros: bool = false,
};
// Mimalloc gets unstable if we try to move this to a different thread
@@ -479,6 +480,10 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
if (object.getIfPropertyExists(globalThis, "macro")) |macros| {
macros: {
if (macros.isUndefinedOrNull()) break :macros;
+ if (macros.isBoolean()) {
+ transpiler.no_macros = !macros.asBoolean();
+ break :macros;
+ }
const kind = macros.jsType();
const is_object = kind.isObject();
if (!(kind.isStringLike() or is_object)) {
@@ -775,7 +780,7 @@ pub fn constructor(
globalThis.throwError(err, "Error creating transpiler");
return null;
};
-
+ bundler.options.no_macros = transpiler_options.no_macros;
bundler.configureLinkerWithAutoJSX(false);
bundler.options.env.behavior = .disable;
bundler.configureDefines() catch |err| {
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 4a1fcbcb1..5c158a4fb 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -392,6 +392,7 @@ pub const VirtualMachine = struct {
macros: MacroMap,
macro_entry_points: std.AutoArrayHashMap(i32, *MacroEntryPoint),
macro_mode: bool = false,
+ no_macros: bool = false,
has_any_macro_remappings: bool = false,
is_from_devserver: bool = false,
diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig
index bbe2ea654..f090b3a12 100644
--- a/src/bun.js/node/types.zig
+++ b/src/bun.js/node/types.zig
@@ -1775,17 +1775,19 @@ pub const Path = struct {
var path_slice: JSC.ZigString.Slice = args_ptr[0].toSlice(globalThis, heap_allocator);
defer path_slice.deinit();
var path = path_slice.slice();
- var path_name = Fs.PathName.init(path);
- var root = JSC.ZigString.init(path_name.dir);
- const is_absolute = (isWindows and isZigStringAbsoluteWindows(root)) or (!isWindows and path_name.dir.len > 0 and path_name.dir[0] == '/');
-
+ var path_name = Fs.NodeJSPathName.init(path);
var dir = JSC.ZigString.init(path_name.dir);
+ const is_absolute = (isWindows and isZigStringAbsoluteWindows(dir)) or (!isWindows and path.len > 0 and path[0] == '/');
+
+ // if its not absolute root must be empty
+ var root = JSC.ZigString.Empty;
if (is_absolute) {
- root = JSC.ZigString.Empty;
- if (path_name.dir.len == 0)
- dir = JSC.ZigString.init(if (isWindows) std.fs.path.sep_str_windows else std.fs.path.sep_str_posix);
+ root = JSC.ZigString.init(if (isWindows) std.fs.path.sep_str_windows else std.fs.path.sep_str_posix);
+ // if is absolute and dir is empty, then dir = root
+ if (path_name.dir.len == 0) {
+ dir = root;
+ }
}
-
var base = JSC.ZigString.init(path_name.base);
var name_ = JSC.ZigString.init(path_name.filename);
var ext = JSC.ZigString.init(path_name.ext);
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index 591150e12..a5d3c968d 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -85,6 +85,7 @@ pub const Blob = struct {
store: ?*Store = null,
content_type: string = "",
content_type_allocated: bool = false,
+ content_type_was_set: bool = false,
/// JavaScriptCore strings are either latin1 or UTF-16
/// When UTF-16, they're nearly always due to non-ascii characters
@@ -111,6 +112,10 @@ pub const Blob = struct {
return bun.FormData.AsyncFormData.init(this.allocator orelse bun.default_allocator, encoding) catch unreachable;
}
+ pub fn hasContentTypeFromUser(this: *const Blob) bool {
+ return this.content_type_was_set or (this.store != null and this.store.?.data == .file);
+ }
+
const FormDataContext = struct {
allocator: std.mem.Allocator,
joiner: StringJoiner,
@@ -228,6 +233,7 @@ pub const Blob = struct {
var blob = Blob.initWithStore(store, globalThis);
blob.content_type = store.mime_type.value;
+ blob.content_type_was_set = true;
return blob;
}
@@ -268,6 +274,7 @@ pub const Blob = struct {
var blob = Blob.initWithStore(store, globalThis);
blob.content_type = std.fmt.allocPrint(allocator, "multipart/form-data; boundary=\"{s}\"", .{boundary}) catch unreachable;
blob.content_type_allocated = true;
+ blob.content_type_was_set = true;
return blob;
}
@@ -288,7 +295,7 @@ pub const Blob = struct {
export fn Blob__dupe(ptr: *anyopaque) *Blob {
var this = bun.cast(*Blob, ptr);
var new = bun.default_allocator.create(Blob) catch unreachable;
- new.* = this.dupe();
+ new.* = this.dupeWithContentType(true);
new.allocator = bun.default_allocator;
return new;
}
@@ -2527,6 +2534,7 @@ pub const Blob = struct {
blob.content_type = content_type;
blob.content_type_allocated = content_type_was_allocated;
+ blob.content_type_was_set = this.content_type_was_set or content_type_was_allocated;
var blob_ = allocator.create(Blob) catch unreachable;
blob_.* = blob;
@@ -2548,13 +2556,13 @@ pub const Blob = struct {
) callconv(.C) JSValue {
if (this.content_type.len > 0) {
if (this.content_type_allocated) {
- return ZigString.init(this.content_type).toValue(globalThis);
+ return ZigString.init(this.content_type).toValueGC(globalThis);
}
return ZigString.init(this.content_type).toValueGC(globalThis);
}
if (this.store) |store| {
- return ZigString.init(store.mime_type.value).toValue(globalThis);
+ return ZigString.init(store.mime_type.value).toValueGC(globalThis);
}
return ZigString.Empty.toValue(globalThis);
@@ -2754,6 +2762,8 @@ pub const Blob = struct {
if (!strings.isAllASCII(slice)) {
break :inner;
}
+ blob.content_type_was_set = true;
+
if (globalThis.bunVM().mimeType(slice)) |mime| {
blob.content_type = mime.value;
break :inner;
@@ -2769,6 +2779,7 @@ pub const Blob = struct {
if (blob.content_type.len == 0) {
blob.content_type = "";
+ blob.content_type_was_set = false;
}
},
}
@@ -2870,8 +2881,33 @@ pub const Blob = struct {
/// This creates a new view
/// and increment the reference count
pub fn dupe(this: *const Blob) Blob {
+ return this.dupeWithContentType(false);
+ }
+
+ pub fn dupeWithContentType(this: *const Blob, include_content_type: bool) Blob {
if (this.store != null) this.store.?.ref();
var duped = this.*;
+ if (duped.content_type_allocated and duped.allocator != null and !include_content_type) {
+
+ // for now, we just want to avoid a use-after-free here
+ if (JSC.VirtualMachine.get().mimeType(duped.content_type)) |mime| {
+ duped.content_type = mime.value;
+ } else {
+ // TODO: fix this
+ // this is a bug.
+ // it means whenever
+ duped.content_type = "";
+ }
+
+ duped.content_type_allocated = false;
+ duped.content_type_was_set = false;
+ if (this.content_type_was_set) {
+ duped.content_type_was_set = duped.content_type.len > 0;
+ }
+ } else if (duped.content_type_allocated and duped.allocator != null and include_content_type) {
+ duped.content_type = bun.default_allocator.dupe(u8, this.content_type) catch @panic("Out of memory");
+ }
+
duped.allocator = null;
return duped;
}
@@ -3477,6 +3513,13 @@ pub const AnyBlob = union(enum) {
// InlineBlob: InlineBlob,
InternalBlob: InternalBlob,
+ pub fn hasContentTypeFromUser(this: AnyBlob) bool {
+ return switch (this) {
+ .Blob => this.Blob.hasContentTypeFromUser(),
+ .InternalBlob => false,
+ };
+ }
+
pub fn toJSON(this: *AnyBlob, global: *JSGlobalObject, comptime lifetime: JSC.WebCore.Lifetime) JSValue {
switch (this.*) {
.Blob => return this.Blob.toJSON(global, lifetime),
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 8d1bfb961..ad3857685 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -629,7 +629,7 @@ pub const Fetch = struct {
result: HTTPClient.HTTPClientResult = .{},
javascript_vm: *VirtualMachine = undefined,
global_this: *JSGlobalObject = undefined,
- request_body: AnyBlob = undefined,
+ request_body: HTTPRequestBody = undefined,
response_buffer: MutableString = undefined,
request_headers: Headers = Headers{ .allocator = undefined },
promise: JSC.JSPromise.Strong,
@@ -647,6 +647,38 @@ pub const Fetch = struct {
abort_reason: JSValue = JSValue.zero,
// Custom Hostname
hostname: ?[]u8 = null,
+
+ pub const HTTPRequestBody = union(enum) {
+ AnyBlob: AnyBlob,
+ Sendfile: HTTPClient.Sendfile,
+
+ pub fn store(this: *HTTPRequestBody) ?*JSC.WebCore.Blob.Store {
+ return switch (this.*) {
+ .AnyBlob => this.AnyBlob.store(),
+ else => null,
+ };
+ }
+
+ pub fn slice(this: *const HTTPRequestBody) []const u8 {
+ return switch (this.*) {
+ .AnyBlob => this.AnyBlob.slice(),
+ else => "",
+ };
+ }
+
+ pub fn detach(this: *HTTPRequestBody) void {
+ switch (this.*) {
+ .AnyBlob => this.AnyBlob.detach(),
+ .Sendfile => {
+ if (@max(this.Sendfile.offset, this.Sendfile.remain) > 0)
+ _ = JSC.Node.Syscall.close(this.Sendfile.fd);
+ this.Sendfile.offset = 0;
+ this.Sendfile.remain = 0;
+ },
+ }
+ }
+ };
+
pub fn init(_: std.mem.Allocator) anyerror!FetchTasklet {
return FetchTasklet{};
}
@@ -850,12 +882,26 @@ pub const Fetch = struct {
proxy = jsc_vm.bundler.env.getHttpProxy(fetch_options.url);
}
- fetch_tasklet.http.?.* = HTTPClient.AsyncHTTP.init(allocator, fetch_options.method, fetch_options.url, fetch_options.headers.entries, fetch_options.headers.buf.items, &fetch_tasklet.response_buffer, fetch_tasklet.request_body.slice(), fetch_options.timeout, HTTPClient.HTTPClientResult.Callback.New(
- *FetchTasklet,
- FetchTasklet.callback,
- ).init(
- fetch_tasklet,
- ), proxy, if (fetch_tasklet.signal != null) &fetch_tasklet.aborted else null, fetch_options.hostname, fetch_options.redirect_type);
+ fetch_tasklet.http.?.* = HTTPClient.AsyncHTTP.init(
+ allocator,
+ fetch_options.method,
+ fetch_options.url,
+ fetch_options.headers.entries,
+ fetch_options.headers.buf.items,
+ &fetch_tasklet.response_buffer,
+ fetch_tasklet.request_body.slice(),
+ fetch_options.timeout,
+ HTTPClient.HTTPClientResult.Callback.New(
+ *FetchTasklet,
+ FetchTasklet.callback,
+ ).init(
+ fetch_tasklet,
+ ),
+ proxy,
+ if (fetch_tasklet.signal != null) &fetch_tasklet.aborted else null,
+ fetch_options.hostname,
+ fetch_options.redirect_type,
+ );
if (fetch_options.redirect_type != FetchRedirect.follow) {
fetch_tasklet.http.?.client.remaining_redirect_count = 0;
@@ -865,6 +911,12 @@ pub const Fetch = struct {
fetch_tasklet.http.?.client.verbose = fetch_options.verbose;
fetch_tasklet.http.?.client.disable_keepalive = fetch_options.disable_keepalive;
+ if (fetch_tasklet.request_body == .Sendfile) {
+ std.debug.assert(fetch_options.url.isHTTP());
+ std.debug.assert(fetch_options.proxy == null);
+ fetch_tasklet.http.?.request_body = .{ .sendfile = fetch_tasklet.request_body.Sendfile };
+ }
+
if (fetch_tasklet.signal) |signal| {
fetch_tasklet.signal = signal.listen(FetchTasklet, fetch_tasklet, FetchTasklet.abortListener);
}
@@ -886,7 +938,7 @@ pub const Fetch = struct {
const FetchOptions = struct {
method: Method,
headers: Headers,
- body: AnyBlob,
+ body: HTTPRequestBody,
timeout: usize,
disable_timeout: bool,
disable_keepalive: bool,
@@ -961,6 +1013,14 @@ pub const Fetch = struct {
var url = ZigURL{};
var first_arg = args.nextEat().?;
+
+ // We must always get the Body before the Headers That way, we can set
+ // the Content-Type header from the Blob if no Content-Type header is
+ // set in the Headers
+ //
+ // which is important for FormData.
+ // https://github.com/oven-sh/bun/issues/2264
+ //
var body: AnyBlob = AnyBlob{
.Blob = .{},
};
@@ -988,46 +1048,45 @@ pub const Fetch = struct {
method = request.method;
}
+ if (options.fastGet(ctx.ptr(), .body)) |body__| {
+ if (Body.Value.fromJS(ctx.ptr(), body__)) |body_const| {
+ var body_value = body_const;
+ // TODO: buffer ReadableStream?
+ // we have to explicitly check for InternalBlob
+ body = body_value.useAsAnyBlob();
+ } else {
+ // clean hostname if any
+ if (hostname) |host| {
+ bun.default_allocator.free(host);
+ }
+ // an error was thrown
+ return JSC.JSValue.jsUndefined();
+ }
+ } else {
+ body = request.body.value.useAsAnyBlob();
+ }
+
if (options.fastGet(ctx.ptr(), .headers)) |headers_| {
if (headers_.as(FetchHeaders)) |headers__| {
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator) catch unreachable;
+ headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
// TODO: make this one pass
} else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| {
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator) catch unreachable;
+ headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
headers__.deref();
} else if (request.headers) |head| {
if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
}
- headers = Headers.from(head, bun.default_allocator) catch unreachable;
+ headers = Headers.from(head, bun.default_allocator, .{ .body = &body }) catch unreachable;
}
} else if (request.headers) |head| {
- headers = Headers.from(head, bun.default_allocator) catch unreachable;
- }
-
- if (options.fastGet(ctx.ptr(), .body)) |body__| {
- if (Body.Value.fromJS(ctx.ptr(), body__)) |body_const| {
- var body_value = body_const;
- // TODO: buffer ReadableStream?
- // we have to explicitly check for InternalBlob
-
- body = body_value.useAsAnyBlob();
- } else {
- // clean hostname if any
- if (hostname) |host| {
- bun.default_allocator.free(host);
- }
- // an error was thrown
- return JSC.JSValue.jsUndefined();
- }
- } else {
- body = request.body.value.useAsAnyBlob();
+ headers = Headers.from(head, bun.default_allocator, .{ .body = &body }) catch unreachable;
}
if (options.get(ctx, "timeout")) |timeout_value| {
@@ -1100,13 +1159,13 @@ pub const Fetch = struct {
}
} else {
method = request.method;
+ body = request.body.value.useAsAnyBlob();
if (request.headers) |head| {
if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
}
- headers = Headers.from(head, bun.default_allocator) catch unreachable;
+ headers = Headers.from(head, bun.default_allocator, .{ .body = &body }) catch unreachable;
}
- body = request.body.value.useAsAnyBlob();
// no proxy only url
url = ZigURL.parse(getAllocator(ctx).dupe(u8, request.url) catch unreachable);
url_proxy_buffer = url.href;
@@ -1124,19 +1183,35 @@ pub const Fetch = struct {
method = Method.which(slice_.slice()) orelse .GET;
}
+ if (options.fastGet(ctx.ptr(), .body)) |body__| {
+ if (Body.Value.fromJS(ctx.ptr(), body__)) |body_const| {
+ var body_value = body_const;
+ // TODO: buffer ReadableStream?
+ // we have to explicitly check for InternalBlob
+ body = body_value.useAsAnyBlob();
+ } else {
+ // clean hostname if any
+ if (hostname) |host| {
+ bun.default_allocator.free(host);
+ }
+ // an error was thrown
+ return JSC.JSValue.jsUndefined();
+ }
+ }
+
if (options.fastGet(ctx.ptr(), .headers)) |headers_| {
if (headers_.as(FetchHeaders)) |headers__| {
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator) catch unreachable;
+ headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
// TODO: make this one pass
} else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| {
defer headers__.deref();
if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| {
hostname = _hostname.toOwnedSliceZ(bun.default_allocator) catch unreachable;
}
- headers = Headers.from(headers__, bun.default_allocator) catch unreachable;
+ headers = Headers.from(headers__, bun.default_allocator, .{ .body = &body }) catch unreachable;
} else {
// Converting the headers failed; return null and
// let the set exception get thrown
@@ -1144,22 +1219,6 @@ pub const Fetch = struct {
}
}
- if (options.fastGet(ctx.ptr(), .body)) |body__| {
- if (Body.Value.fromJS(ctx.ptr(), body__)) |body_const| {
- var body_value = body_const;
- // TODO: buffer ReadableStream?
- // we have to explicitly check for InternalBlob
- body = body_value.useAsAnyBlob();
- } else {
- // clean hostname if any
- if (hostname) |host| {
- bun.default_allocator.free(host);
- }
- // an error was thrown
- return JSC.JSValue.jsUndefined();
- }
- }
-
if (options.get(ctx, "timeout")) |timeout_value| {
if (timeout_value.isBoolean()) {
disable_timeout = !timeout_value.asBoolean();
@@ -1324,6 +1383,125 @@ pub const Fetch = struct {
return JSPromise.rejectedPromiseValue(globalThis, err);
}
+ if (headers == null and body.size() > 0 and body.hasContentTypeFromUser()) {
+ headers = Headers.from(
+ null,
+ bun.default_allocator,
+ .{ .body = &body },
+ ) catch unreachable;
+ }
+
+ var http_body = FetchTasklet.HTTPRequestBody{
+ .AnyBlob = body,
+ };
+
+ if (body.needsToReadFile()) {
+ prepare_body: {
+ const opened_fd_res: JSC.Node.Maybe(bun.FileDescriptor) = switch (body.Blob.store.?.data.file.pathlike) {
+ .fd => |fd| JSC.Node.Maybe(bun.FileDescriptor).errnoSysFd(JSC.Node.Syscall.system.dup(fd), .open, fd) orelse .{ .result = fd },
+ .path => |path| JSC.Node.Syscall.open(path.sliceZ(&globalThis.bunVM().nodeFS().sync_error_buf), std.os.O.RDONLY | std.os.O.NOCTTY, 0),
+ };
+
+ const opened_fd = switch (opened_fd_res) {
+ .err => |err| {
+ bun.default_allocator.free(url_proxy_buffer);
+
+ const rejected_value = JSPromise.rejectedPromiseValue(globalThis, err.toJSC(globalThis));
+ body.detach();
+ if (headers) |*headers_| {
+ headers_.buf.deinit(bun.default_allocator);
+ headers_.entries.deinit(bun.default_allocator);
+ }
+
+ return rejected_value;
+ },
+ .result => |fd| fd,
+ };
+
+ if (proxy == null and bun.HTTP.Sendfile.isEligible(url)) {
+ use_sendfile: {
+ const stat: std.os.Stat = switch (JSC.Node.Syscall.fstat(opened_fd)) {
+ .result => |result| result,
+ // bail out for any reason
+ .err => break :use_sendfile,
+ };
+
+ if (Environment.isMac) {
+ // macOS only supports regular files for sendfile()
+ if (!std.os.S.ISREG(stat.mode)) {
+ break :use_sendfile;
+ }
+ }
+
+ // if it's < 32 KB, it's not worth it
+ if (stat.size < 32 * 1024) {
+ break :use_sendfile;
+ }
+
+ const original_size = body.Blob.size;
+ const stat_size = @intCast(Blob.SizeType, stat.size);
+ const blob_size = if (std.os.S.ISREG(stat.mode))
+ stat_size
+ else
+ @min(original_size, stat_size);
+
+ http_body = .{
+ .Sendfile = .{
+ .fd = opened_fd,
+ .remain = body.Blob.offset + original_size,
+ .offset = body.Blob.offset,
+ .content_size = blob_size,
+ },
+ };
+
+ if (std.os.S.ISREG(stat.mode)) {
+ http_body.Sendfile.offset = @min(http_body.Sendfile.offset, stat_size);
+ http_body.Sendfile.remain = @min(@max(http_body.Sendfile.remain, http_body.Sendfile.offset), stat_size) -| http_body.Sendfile.offset;
+ }
+ body.detach();
+
+ break :prepare_body;
+ }
+ }
+
+ // TODO: make this async + lazy
+ const res = JSC.Node.NodeFS.readFile(
+ globalThis.bunVM().nodeFS(),
+ .{
+ .encoding = .buffer,
+ .path = .{ .fd = opened_fd },
+ .offset = body.Blob.offset,
+ .max_size = body.Blob.size,
+ },
+ .sync,
+ );
+
+ if (body.Blob.store.?.data.file.pathlike == .path) {
+ _ = JSC.Node.Syscall.close(opened_fd);
+ }
+
+ switch (res) {
+ .err => |err| {
+ bun.default_allocator.free(url_proxy_buffer);
+
+ const rejected_value = JSPromise.rejectedPromiseValue(globalThis, err.toJSC(globalThis));
+ body.detach();
+ if (headers) |*headers_| {
+ headers_.buf.deinit(bun.default_allocator);
+ headers_.entries.deinit(bun.default_allocator);
+ }
+
+ return rejected_value;
+ },
+ .result => |result| {
+ body.detach();
+ body.from(std.ArrayList(u8).fromOwnedSlice(bun.default_allocator, @constCast(result.slice())));
+ http_body = .{ .AnyBlob = body };
+ },
+ }
+ }
+ }
+
// Only create this after we have validated all the input.
// or else we will leak it
var promise = JSPromise.Strong.init(globalThis);
@@ -1340,7 +1518,7 @@ pub const Fetch = struct {
.headers = headers orelse Headers{
.allocator = bun.default_allocator,
},
- .body = body,
+ .body = http_body,
.timeout = std.time.ns_per_hour,
.disable_keepalive = disable_keepalive,
.disable_timeout = disable_timeout,
@@ -1376,15 +1554,31 @@ pub const Headers = struct {
"";
}
- pub fn from(headers_ref: *FetchHeaders, allocator: std.mem.Allocator) !Headers {
+ pub const Options = struct {
+ body: ?*const AnyBlob = null,
+ };
+
+ pub fn from(fetch_headers_ref: ?*FetchHeaders, allocator: std.mem.Allocator, options: Options) !Headers {
var header_count: u32 = 0;
var buf_len: u32 = 0;
- headers_ref.count(&header_count, &buf_len);
+ 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 += @truncate(u32, body.contentType().len + "Content-Type".len);
+ break :brk true;
+ }
+ }
+ break :brk false;
+ };
headers.entries.ensureTotalCapacity(allocator, header_count) catch unreachable;
headers.entries.len = header_count;
headers.buf.ensureTotalCapacityPrecise(allocator, buf_len) catch unreachable;
@@ -1392,7 +1586,24 @@ pub const Headers = struct {
var sliced = headers.entries.slice();
var names = sliced.items(.name);
var values = sliced.items(.value);
- headers_ref.copyTo(names.ptr, values.ptr, headers.buf.items.ptr);
+ 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 = @truncate(u32, options.body.?.contentType().len),
+ };
+ }
+
return headers;
}
};
@@ -1567,7 +1778,7 @@ pub const FetchEvent = struct {
var content_length: ?usize = null;
if (response.body.init.headers) |headers_ref| {
- var headers = Headers.from(headers_ref, request_context.allocator) catch unreachable;
+ var headers = Headers.from(headers_ref, request_context.allocator, .{}) catch unreachable;
var i: usize = 0;
while (i < headers.entries.len) : (i += 1) {