aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Justin Whear <justin.whear@gmail.com> 2023-02-17 15:54:05 -0800
committerGravatar GitHub <noreply@github.com> 2023-02-17 15:54:05 -0800
commitfb313f210a51387fd77e0ddf4172ee3b52d0bdc9 (patch)
tree3cd3c3f53ea5be5e36f70ec77a1db75b3d171065
parentc60d7db178b66a70adadb85a5054d524169397f1 (diff)
downloadbun-fb313f210a51387fd77e0ddf4172ee3b52d0bdc9.tar.gz
bun-fb313f210a51387fd77e0ddf4172ee3b52d0bdc9.tar.zst
bun-fb313f210a51387fd77e0ddf4172ee3b52d0bdc9.zip
Fix #1602 (#2066)
* initial test case * fix segfault from JSObjectMakeDeferredPromise * pass exceptions through from FetchHeader.createFromJS * not resolved, but getting close * implement review suggestions * fix exception check, tests * Change how header filtering is accomplished Previously the FetchHeaders implementation relied on converting names and values to IDLByteString to catch non-ASCII data, though not always reliably. This resulted in message-less TypeErrors when headers contained invalid characters. This commit shifts everything to IDLDOMString for the conversion and relies on the actual error checking in FetchHeaders.canWriteHeader, resulting in nicer error messages. To ensure that all headers are written as ASCII/UTF8 rather than UTF16, the copyTo bindings function checks the encoding and converts if necessary. * wrapping up FetchHeader fixes * since utf8 allocates only do so when needed * Update src/bun.js/bindings/bindings.cpp Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> * WebCore__FetchHeaders__has should return on exception path * strip out log calls from test --------- Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Diffstat (limited to '')
-rw-r--r--src/bun.js/base.zig7
-rw-r--r--src/bun.js/bindings/bindings.cpp108
-rw-r--r--src/bun.js/bindings/bindings.zig40
-rw-r--r--src/bun.js/bindings/headers-cpp.h2
-rw-r--r--src/bun.js/bindings/headers.h14
-rw-r--r--src/bun.js/bindings/headers.zig12
-rw-r--r--src/bun.js/bindings/webcore/HTTPParsers.cpp6
-rw-r--r--src/bun.js/bindings/webcore/JSFetchHeaders.cpp26
-rw-r--r--src/bun.js/webcore/body.zig8
-rw-r--r--src/bun.js/webcore/request.zig10
-rw-r--r--src/bun.js/webcore/response.zig31
-rw-r--r--test/bun.js/fetch_headers.test.js58
12 files changed, 228 insertions, 94 deletions
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index b8b900cee..bdb516836 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -1718,6 +1718,13 @@ pub fn getAllocator(_: js.JSContextRef) std.mem.Allocator {
return default_allocator;
}
+/// Print a JSValue to stdout; this is only meant for debugging purposes
+pub fn dump(value: JSValue, globalObject: *JSC.JSGlobalObject) !void {
+ var formatter = JSC.ZigConsoleClient.Formatter{ .globalThis = globalObject };
+ try Output.errorWriter().print("{}\n", .{value.toFmt(globalObject, &formatter)});
+ Output.flush();
+}
+
pub const JSStringList = std.ArrayList(js.JSStringRef);
pub const ArrayBuffer = extern struct {
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 4b614788d..bb01e4101 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -97,19 +97,27 @@ static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res)
auto& internalHeaders = headers->internalHeaders();
for (auto& value : internalHeaders.getSetCookieHeaders()) {
- res->writeHeader(std::string_view("set-cookie", 10), std::string_view(reinterpret_cast<const char*>(value.characters8()), value.length()));
+ res->writeHeader(std::string_view("set-cookie", 10), std::string_view(
+ value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length()
+ ));
}
for (auto& header : internalHeaders.commonHeaders()) {
const auto& name = WebCore::httpHeaderNameString(header.key);
auto& value = header.value;
- res->writeHeader(std::string_view(reinterpret_cast<const char*>(name.characters8()), name.length()), std::string_view(reinterpret_cast<const char*>(value.characters8()), value.length()));
+ res->writeHeader(
+ std::string_view(name.is8Bit() ? reinterpret_cast<const char*>(name.characters8()) : name.utf8().data(), name.length()),
+ std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length())
+ );
}
for (auto& header : internalHeaders.uncommonHeaders()) {
auto& name = header.key;
auto& value = header.value;
- res->writeHeader(std::string_view(reinterpret_cast<const char*>(name.characters8()), name.length()), std::string_view(reinterpret_cast<const char*>(value.characters8()), value.length()));
+ res->writeHeader(
+ std::string_view(name.is8Bit() ? reinterpret_cast<const char*>(name.characters8()) : name.utf8().data(), name.length()),
+ std::string_view(value.is8Bit() ? reinterpret_cast<const char*>(value.characters8()) : value.utf8().data(), value.length())
+ );
}
}
@@ -709,9 +717,13 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__createEmpty()
{
return new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} });
}
-void WebCore__FetchHeaders__append(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2)
+void WebCore__FetchHeaders__append(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2,
+ JSC__JSGlobalObject* lexicalGlobalObject)
{
- headers->append(Zig::toString(*arg1), Zig::toString(*arg2));
+ auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm());
+ WebCore::propagateException(*lexicalGlobalObject, throwScope,
+ headers->append(Zig::toString(*arg1), Zig::toString(*arg2))
+ );
}
WebCore__FetchHeaders* WebCore__FetchHeaders__cast_(JSC__JSValue JSValue0, JSC__VM* vm)
{
@@ -724,12 +736,25 @@ WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject*
{
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
EnsureStillAliveScope argument0 = JSC::JSValue::decode(argument0_);
+
auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm());
- auto init = argument0.value().isUndefined() ? std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>() : std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>(convert<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>(*lexicalGlobalObject, argument0.value()));
+ // Note that we use IDLDOMString here rather than IDLByteString: while headers
+ // should be ASCII only, we want the headers->fill implementation to discover
+ // and error on invalid names and values
+ using TargetType = IDLUnion<IDLSequence<IDLSequence<IDLDOMString>>, IDLRecord<IDLDOMString, IDLDOMString>>;
+ using Converter = std::optional<Converter<TargetType>::ReturnType>;
+ auto init = argument0.value().isUndefined() ? Converter() : Converter(convert<TargetType>(*lexicalGlobalObject, argument0.value()));
RETURN_IF_EXCEPTION(throwScope, nullptr);
+
auto* headers = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} });
- if (init)
- headers->fill(WTFMove(init.value()));
+ if (init) {
+ // `fill` doesn't set an exception on the VM if it fails, it returns an
+ // ExceptionOr<void>. So we need to check for the exception and, if set,
+ // translate it to JSValue and throw it.
+ WebCore::propagateException(*lexicalGlobalObject, throwScope,
+ headers->fill(WTFMove(init.value()))
+ );
+ }
return headers;
}
@@ -741,16 +766,22 @@ JSC__JSValue WebCore__FetchHeaders__toJS(WebCore__FetchHeaders* headers, JSC__JS
}
JSC__JSValue WebCore__FetchHeaders__clone(WebCore__FetchHeaders* headers, JSC__JSGlobalObject* arg1)
{
+ auto throwScope = DECLARE_THROW_SCOPE(arg1->vm());
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg1);
auto* clone = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} });
- clone->fill(*headers);
+ WebCore::propagateException(*arg1, throwScope,
+ clone->fill(*headers)
+ );
return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg1, globalObject, WTFMove(clone)));
}
-WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* headers)
+WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* headers, JSC__JSGlobalObject* lexicalGlobalObject)
{
+ auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm());
auto* clone = new WebCore::FetchHeaders({ WebCore::FetchHeaders::Guard::None, {} });
- clone->fill(*headers);
+ WebCore::propagateException(*lexicalGlobalObject, throwScope,
+ clone->fill(*headers)
+ );
return clone;
}
@@ -764,14 +795,25 @@ void WebCore__FetchHeaders__copyTo(WebCore__FetchHeaders* headers, StringPointer
auto iter = headers->createIterator();
uint32_t i = 0;
unsigned count = 0;
+
for (auto pair = iter.next(); pair; pair = iter.next()) {
auto name = pair->key;
auto value = pair->value;
names[count] = { i, name.length() };
- memcpy(&buf[i], name.characters8(), name.length());
+
+ if (name.is8Bit())
+ memcpy(&buf[i], name.characters8(), name.length());
+ else {
+ StringImpl::copyCharacters(&buf[i], name.characters16(), name.length());
+ }
+
i += name.length();
values[count++] = { i, value.length() };
- memcpy(&buf[i], value.characters8(), value.length());
+ if (value.is8Bit())
+ memcpy(&buf[i], value.characters8(), value.length());
+ else
+ StringImpl::copyCharacters(&buf[i], value.characters16(), value.length());
+
i += value.length();
}
}
@@ -883,6 +925,7 @@ void WebCore__FetchHeaders__deref(WebCore__FetchHeaders* arg0)
JSC__JSValue WebCore__FetchHeaders__createValue(JSC__JSGlobalObject* arg0, StringPointer* arg1, StringPointer* arg2, const ZigString* arg3, uint32_t count)
{
+ auto throwScope = DECLARE_THROW_SCOPE(arg0->vm());
Vector<KeyValuePair<String, String>> pairs;
pairs.reserveCapacity(count);
ZigString buf = *arg3;
@@ -893,25 +936,44 @@ JSC__JSValue WebCore__FetchHeaders__createValue(JSC__JSGlobalObject* arg0, Strin
}
Ref<WebCore::FetchHeaders> headers = WebCore::FetchHeaders::create();
- headers->fill(WebCore::FetchHeaders::Init(WTFMove(pairs)));
+ WebCore::propagateException(*arg0, throwScope,
+ headers->fill(WebCore::FetchHeaders::Init(WTFMove(pairs)))
+ );
pairs.releaseBuffer();
return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), WTFMove(headers)));
}
-void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* headers, const ZigString* arg1, ZigString* arg2)
+void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* headers, const ZigString* arg1, ZigString* arg2, JSC__JSGlobalObject* global)
{
- *arg2 = Zig::toZigString(headers->get(Zig::toString(*arg1)).releaseReturnValue());
+ auto throwScope = DECLARE_THROW_SCOPE(global->vm());
+ auto result = headers->get(Zig::toString(*arg1));
+ if (result.hasException())
+ WebCore::propagateException(*global, throwScope, result.releaseException());
+ else
+ *arg2 = Zig::toZigString(result.releaseReturnValue());
}
-bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* headers, const ZigString* arg1)
+bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* headers, const ZigString* arg1, JSC__JSGlobalObject* global)
{
- return headers->has(Zig::toString(*arg1)).releaseReturnValue();
+ auto throwScope = DECLARE_THROW_SCOPE(global->vm());
+ auto result = headers->has(Zig::toString(*arg1));
+ if (result.hasException()) {
+ WebCore::propagateException(*global, throwScope, result.releaseException());
+ return false;
+ } else
+ return result.releaseReturnValue();
}
-void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2)
+void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2, JSC__JSGlobalObject* global)
{
- headers->set(Zig::toString(*arg1), Zig::toString(*arg2));
+ auto throwScope = DECLARE_THROW_SCOPE(global->vm());
+ WebCore::propagateException(*global, throwScope,
+ headers->set(Zig::toString(*arg1), Zig::toString(*arg2))
+ );
}
-void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* headers, const ZigString* arg1)
+void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* headers, const ZigString* arg1, JSC__JSGlobalObject* global)
{
- headers->remove(Zig::toString(*arg1));
+ auto throwScope = DECLARE_THROW_SCOPE(global->vm());
+ WebCore::propagateException(*global, throwScope,
+ headers->remove(Zig::toString(*arg1))
+ );
}
void WebCore__FetchHeaders__fastRemove_(WebCore__FetchHeaders* headers, unsigned char headerName)
@@ -3876,4 +3938,4 @@ CPP_DECL JSC__JSValue WebCore__DOMFormData__create(JSC__JSGlobalObject* arg0)
CPP_DECL WebCore__DOMFormData* WebCore__DOMFormData__fromJS(JSC__JSValue JSValue1)
{
return WebCoreCast<WebCore::JSDOMFormData, WebCore__DOMFormData>(JSValue1);
-} \ No newline at end of file
+}
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 6b5386a68..64b2b060d 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -987,12 +987,12 @@ pub const FetchHeaders = opaque {
});
}
- pub fn putDefault(this: *FetchHeaders, name_: []const u8, value: []const u8) void {
- if (this.has(&ZigString.init(name_))) {
+ pub fn putDefault(this: *FetchHeaders, name_: []const u8, value: []const u8, global: *JSGlobalObject) void {
+ if (this.has(&ZigString.init(name_), global)) {
return;
}
- this.put_(&ZigString.init(name_), &ZigString.init(value));
+ this.put_(&ZigString.init(name_), &ZigString.init(value), global);
}
pub fn from(
@@ -1064,11 +1064,13 @@ pub const FetchHeaders = opaque {
this: *FetchHeaders,
name_: *const ZigString,
value: *const ZigString,
+ global: *JSGlobalObject,
) void {
return shim.cppFn("append", .{
this,
name_,
value,
+ global,
});
}
@@ -1076,11 +1078,13 @@ pub const FetchHeaders = opaque {
this: *FetchHeaders,
name_: *const ZigString,
value: *const ZigString,
+ global: *JSGlobalObject,
) void {
return shim.cppFn("put_", .{
this,
name_,
value,
+ global,
});
}
@@ -1088,28 +1092,32 @@ pub const FetchHeaders = opaque {
this: *FetchHeaders,
name_: []const u8,
value: []const u8,
+ global: *JSGlobalObject,
) void {
- this.put_(&ZigString.init(name_), &ZigString.init(value));
+ this.put_(&ZigString.init(name_), &ZigString.init(value), global);
}
pub fn get_(
this: *FetchHeaders,
name_: *const ZigString,
out: *ZigString,
+ global: *JSGlobalObject,
) void {
shim.cppFn("get_", .{
this,
name_,
out,
+ global,
});
}
pub fn get(
this: *FetchHeaders,
name_: []const u8,
+ global: *JSGlobalObject,
) ?[]const u8 {
var out = ZigString.Empty;
- get_(this, &ZigString.init(name_), &out);
+ get_(this, &ZigString.init(name_), &out, global);
if (out.len > 0) {
return out.slice();
}
@@ -1120,10 +1128,12 @@ pub const FetchHeaders = opaque {
pub fn has(
this: *FetchHeaders,
name_: *const ZigString,
+ global: *JSGlobalObject,
) bool {
return shim.cppFn("has", .{
this,
name_,
+ global,
});
}
@@ -1285,10 +1295,12 @@ pub const FetchHeaders = opaque {
pub fn remove(
this: *FetchHeaders,
name_: *const ZigString,
+ global: *JSGlobalObject,
) void {
return shim.cppFn("remove", .{
this,
name_,
+ global,
});
}
@@ -1328,9 +1340,11 @@ pub const FetchHeaders = opaque {
pub fn cloneThis(
this: *FetchHeaders,
+ global: *JSGlobalObject,
) ?*FetchHeaders {
return shim.cppFn("cloneThis", .{
this,
+ global,
});
}
@@ -2876,22 +2890,6 @@ pub const JSValue = enum(JSValueReprInt) {
return @intToEnum(JSValue, @bitCast(i64, @ptrToInt(ptr)));
}
- pub const Formatter = struct {
- value: JSValue,
- global: *JSGlobalObject,
-
- pub fn format(formatter: Formatter, comptime fmt: []const u8, opts: fmt.FormatOptions, writer: anytype) !void {
- const self = formatter.value;
- const kind: JSType = jsType(self);
- if (kind.isStringLike()) {
- var zig_str = self.getZigString();
- return try zig_str.format(fmt, opts, writer);
- }
-
- if (kind) {}
- }
- };
-
pub fn coerceToInt32(this: JSValue, globalThis: *JSC.JSGlobalObject) i32 {
return cppFn("coerceToInt32", .{ this, globalThis });
}
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h
index d0abc7db0..c57c65e18 100644
--- a/src/bun.js/bindings/headers-cpp.h
+++ b/src/bun.js/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1676470760
+//-- AUTOGENERATED FILE -- 1676656020
// clang-format off
#pragma once
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 59450ecf1..a81695ac1 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format off
-//-- AUTOGENERATED FILE -- 1676470760
+//-- AUTOGENERATED FILE -- 1676656020
#pragma once
#include <stddef.h>
@@ -163,10 +163,10 @@ CPP_DECL WebCore__DOMFormData* WebCore__DOMFormData__fromJS(JSC__JSValue JSValue
#pragma mark - WebCore::FetchHeaders
-CPP_DECL void WebCore__FetchHeaders__append(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2);
+CPP_DECL void WebCore__FetchHeaders__append(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2, JSC__JSGlobalObject* arg3);
CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__cast_(JSC__JSValue JSValue0, JSC__VM* arg1);
CPP_DECL JSC__JSValue WebCore__FetchHeaders__clone(WebCore__FetchHeaders* arg0, JSC__JSGlobalObject* arg1);
-CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* arg0);
+CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL void WebCore__FetchHeaders__copyTo(WebCore__FetchHeaders* arg0, StringPointer* arg1, StringPointer* arg2, unsigned char* arg3);
CPP_DECL void WebCore__FetchHeaders__count(WebCore__FetchHeaders* arg0, uint32_t* arg1, uint32_t* arg2);
CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__createEmpty();
@@ -178,10 +178,10 @@ CPP_DECL void WebCore__FetchHeaders__deref(WebCore__FetchHeaders* arg0);
CPP_DECL void WebCore__FetchHeaders__fastGet_(WebCore__FetchHeaders* arg0, unsigned char arg1, ZigString* arg2);
CPP_DECL bool WebCore__FetchHeaders__fastHas_(WebCore__FetchHeaders* arg0, unsigned char arg1);
CPP_DECL void WebCore__FetchHeaders__fastRemove_(WebCore__FetchHeaders* arg0, unsigned char arg1);
-CPP_DECL void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* arg0, const ZigString* arg1, ZigString* arg2);
-CPP_DECL bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* arg0, const ZigString* arg1);
-CPP_DECL void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2);
-CPP_DECL void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* arg0, const ZigString* arg1);
+CPP_DECL void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* arg0, const ZigString* arg1, ZigString* arg2, JSC__JSGlobalObject* arg3);
+CPP_DECL bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
+CPP_DECL void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2, JSC__JSGlobalObject* arg3);
+CPP_DECL void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* arg0, const ZigString* arg1, JSC__JSGlobalObject* arg2);
CPP_DECL JSC__JSValue WebCore__FetchHeaders__toJS(WebCore__FetchHeaders* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool arg1, void* arg2);
CPP_DECL JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0, JSC__JSGlobalObject* arg1);
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index 9f2e1580d..ac936e280 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -103,10 +103,10 @@ pub extern fn WebCore__DOMFormData__count(arg0: ?*bindings.DOMFormData) usize;
pub extern fn WebCore__DOMFormData__create(arg0: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__DOMFormData__createFromURLQuery(arg0: *bindings.JSGlobalObject, arg1: [*c]ZigString) JSC__JSValue;
pub extern fn WebCore__DOMFormData__fromJS(JSValue0: JSC__JSValue) ?*bindings.DOMFormData;
-pub extern fn WebCore__FetchHeaders__append(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString) void;
+pub extern fn WebCore__FetchHeaders__append(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString, arg3: *bindings.JSGlobalObject) void;
pub extern fn WebCore__FetchHeaders__cast_(JSValue0: JSC__JSValue, arg1: *bindings.VM) ?*bindings.FetchHeaders;
pub extern fn WebCore__FetchHeaders__clone(arg0: ?*bindings.FetchHeaders, arg1: *bindings.JSGlobalObject) JSC__JSValue;
-pub extern fn WebCore__FetchHeaders__cloneThis(arg0: ?*bindings.FetchHeaders) ?*bindings.FetchHeaders;
+pub extern fn WebCore__FetchHeaders__cloneThis(arg0: ?*bindings.FetchHeaders, arg1: *bindings.JSGlobalObject) ?*bindings.FetchHeaders;
pub extern fn WebCore__FetchHeaders__copyTo(arg0: ?*bindings.FetchHeaders, arg1: [*c]StringPointer, arg2: [*c]StringPointer, arg3: [*c]u8) void;
pub extern fn WebCore__FetchHeaders__count(arg0: ?*bindings.FetchHeaders, arg1: [*c]u32, arg2: [*c]u32) void;
pub extern fn WebCore__FetchHeaders__createEmpty(...) ?*bindings.FetchHeaders;
@@ -118,10 +118,10 @@ pub extern fn WebCore__FetchHeaders__deref(arg0: ?*bindings.FetchHeaders) void;
pub extern fn WebCore__FetchHeaders__fastGet_(arg0: ?*bindings.FetchHeaders, arg1: u8, arg2: [*c]ZigString) void;
pub extern fn WebCore__FetchHeaders__fastHas_(arg0: ?*bindings.FetchHeaders, arg1: u8) bool;
pub extern fn WebCore__FetchHeaders__fastRemove_(arg0: ?*bindings.FetchHeaders, arg1: u8) void;
-pub extern fn WebCore__FetchHeaders__get_(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]ZigString) void;
-pub extern fn WebCore__FetchHeaders__has(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString) bool;
-pub extern fn WebCore__FetchHeaders__put_(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString) void;
-pub extern fn WebCore__FetchHeaders__remove(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString) void;
+pub extern fn WebCore__FetchHeaders__get_(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]ZigString, arg3: *bindings.JSGlobalObject) void;
+pub extern fn WebCore__FetchHeaders__has(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) bool;
+pub extern fn WebCore__FetchHeaders__put_(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString, arg3: *bindings.JSGlobalObject) void;
+pub extern fn WebCore__FetchHeaders__remove(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: *bindings.JSGlobalObject) void;
pub extern fn WebCore__FetchHeaders__toJS(arg0: ?*bindings.FetchHeaders, arg1: *bindings.JSGlobalObject) JSC__JSValue;
pub extern fn WebCore__FetchHeaders__toUWSResponse(arg0: ?*bindings.FetchHeaders, arg1: bool, arg2: ?*anyopaque) void;
pub extern fn SystemError__toErrorInstance(arg0: [*c]const SystemError, arg1: *bindings.JSGlobalObject) JSC__JSValue;
diff --git a/src/bun.js/bindings/webcore/HTTPParsers.cpp b/src/bun.js/bindings/webcore/HTTPParsers.cpp
index cb4985281..a696be94c 100644
--- a/src/bun.js/bindings/webcore/HTTPParsers.cpp
+++ b/src/bun.js/bindings/webcore/HTTPParsers.cpp
@@ -131,6 +131,10 @@ bool isValidHTTPHeaderValue(const String& value)
c = value[i];
if (c == 0x00 || c == 0x0A || c == 0x0D)
return false;
+
+ //NOTE: The spec doesn't require ASCII or Latin1 but common
+ // implementations, including Node, disallow codepoints > 255
+ if (c > 255) return false;
}
return true;
}
@@ -1074,4 +1078,4 @@ CrossOriginResourcePolicy parseCrossOriginResourcePolicyHeader(StringView header
return CrossOriginResourcePolicy::Invalid;
}
-} \ No newline at end of file
+}
diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
index 815989ad8..53e104152 100644
--- a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
+++ b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
@@ -123,7 +123,7 @@ template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSFetchHeadersDOMConstructor:
ASSERT(castedThis);
EnsureStillAliveScope argument0 = callFrame->argument(0);
- auto init = std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>();
+ auto init = std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLDOMString>>, IDLRecord<IDLDOMString, IDLDOMString>>>::ReturnType>();
if (argument0.value() && !argument0.value().isUndefined()) {
if (auto* existingJsFetchHeaders = jsDynamicCast<JSFetchHeaders*>(argument0.value())) {
@@ -135,7 +135,7 @@ template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSFetchHeadersDOMConstructor:
RETURN_IF_EXCEPTION(throwScope, {});
return JSValue::encode(jsValue);
}
- init = std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>(convert<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>(*lexicalGlobalObject, argument0.value()));
+ init = std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLDOMString>>, IDLRecord<IDLDOMString, IDLDOMString>>>::ReturnType>(convert<IDLUnion<IDLSequence<IDLSequence<IDLDOMString>>, IDLRecord<IDLDOMString, IDLDOMString>>>(*lexicalGlobalObject, argument0.value()));
}
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
@@ -199,7 +199,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_getAll, (JSGlobalObject
return JSValue::encode(jsUndefined());
}
- auto name = convert<IDLByteString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
+ auto name = convert<IDLDOMString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
auto& impl = castedThis->wrapped();
@@ -371,10 +371,10 @@ static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_appendBody(JSC
if (UNLIKELY(callFrame->argumentCount() < 2))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
- auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value());
+ auto name = convert<IDLDOMString>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1);
- auto value = convert<IDLByteString>(*lexicalGlobalObject, argument1.value());
+ auto value = convert<IDLDOMString>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.append(WTFMove(name), WTFMove(value)); })));
}
@@ -477,7 +477,7 @@ static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_deleteBody(JSC
if (UNLIKELY(callFrame->argumentCount() < 1))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
- auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value());
+ auto name = convert<IDLDOMString>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.remove(WTFMove(name)); })));
}
@@ -497,9 +497,9 @@ static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_getBody(JSC::J
if (UNLIKELY(callFrame->argumentCount() < 1))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
- auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value());
+ auto name = convert<IDLDOMString>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
- RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLNullable<IDLByteString>>(*lexicalGlobalObject, throwScope, impl.get(WTFMove(name)))));
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLNullable<IDLDOMString>>(*lexicalGlobalObject, throwScope, impl.get(WTFMove(name)))));
}
JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_get, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
@@ -517,7 +517,7 @@ static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_hasBody(JSC::J
if (UNLIKELY(callFrame->argumentCount() < 1))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
- auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value());
+ auto name = convert<IDLDOMString>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLBoolean>(*lexicalGlobalObject, throwScope, impl.has(WTFMove(name)))));
}
@@ -537,10 +537,10 @@ static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_setBody(JSC::J
if (UNLIKELY(callFrame->argumentCount() < 2))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
- auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value());
+ auto name = convert<IDLDOMString>(*lexicalGlobalObject, argument0.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1);
- auto value = convert<IDLByteString>(*lexicalGlobalObject, argument1.value());
+ auto value = convert<IDLDOMString>(*lexicalGlobalObject, argument1.value());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.set(WTFMove(name), WTFMove(value)); })));
}
@@ -552,8 +552,8 @@ JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_set, (JSGlobalObject *
struct FetchHeadersIteratorTraits {
static constexpr JSDOMIteratorType type = JSDOMIteratorType::Map;
- using KeyType = IDLByteString;
- using ValueType = IDLByteString;
+ using KeyType = IDLDOMString;
+ using ValueType = IDLDOMString;
};
using FetchHeadersIteratorBase = JSDOMIteratorBase<JSFetchHeaders, FetchHeadersIteratorTraits>;
diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig
index 23f00d16e..c4e2e1324 100644
--- a/src/bun.js/webcore/body.zig
+++ b/src/bun.js/webcore/body.zig
@@ -128,11 +128,11 @@ pub const Body = struct {
status_code: u16,
method: Method = Method.GET,
- pub fn clone(this: Init, _: *JSGlobalObject) Init {
+ pub fn clone(this: Init, ctx: *JSGlobalObject) Init {
var that = this;
var headers = this.headers;
if (headers) |head| {
- that.headers = head.cloneThis();
+ that.headers = head.cloneThis(ctx);
}
return that;
@@ -149,7 +149,7 @@ pub const Body = struct {
// we can skip calling JS getters
if (response_init.as(Request)) |req| {
if (req.headers) |headers| {
- result.headers = headers.cloneThis();
+ result.headers = headers.cloneThis(ctx);
}
result.method = req.method;
@@ -163,7 +163,7 @@ pub const Body = struct {
if (response_init.fastGet(ctx, .headers)) |headers| {
if (headers.as(FetchHeaders)) |orig| {
- result.headers = orig.cloneThis();
+ result.headers = orig.cloneThis(ctx);
} else {
result.headers = FetchHeaders.createFromJS(ctx.ptr(), headers);
}
diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig
index fb0e40d2e..04fb3f7e2 100644
--- a/src/bun.js/webcore/request.zig
+++ b/src/bun.js/webcore/request.zig
@@ -269,7 +269,7 @@ pub const Request = struct {
globalObject: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
if (this.headers) |headers_ref| {
- if (headers_ref.get("referrer")) |referrer| {
+ if (headers_ref.get("referrer", globalObject)) |referrer| {
return ZigString.init(referrer).toValueGC(globalObject);
}
}
@@ -427,7 +427,7 @@ pub const Request = struct {
request.body.Blob.content_type.len > 0 and
!request.headers.?.fastHas(.ContentType))
{
- request.headers.?.put("content-type", request.body.Blob.content_type);
+ request.headers.?.put("content-type", request.body.Blob.content_type, globalThis);
}
return request;
@@ -474,7 +474,7 @@ pub const Request = struct {
if (this.body == .Blob) {
const content_type = this.body.Blob.content_type;
if (content_type.len > 0) {
- this.headers.?.put("content-type", content_type);
+ this.headers.?.put("content-type", content_type, globalThis);
}
}
}
@@ -501,10 +501,10 @@ pub const Request = struct {
};
if (this.headers) |head| {
- req.headers = head.cloneThis();
+ req.headers = head.cloneThis(globalThis);
} else if (this.uws_request) |uws_req| {
req.headers = FetchHeaders.createFromUWS(globalThis, uws_req);
- this.headers = req.headers.?.cloneThis().?;
+ this.headers = req.headers.?.cloneThis(globalThis).?;
}
}
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index 725a4e64c..5c0cd9c8e 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -200,14 +200,14 @@ pub const Response = struct {
return JSValue.jsBoolean(this.isOK());
}
- fn getOrCreateHeaders(this: *Response) *FetchHeaders {
+ fn getOrCreateHeaders(this: *Response, globalThis: *JSC.JSGlobalObject) *FetchHeaders {
if (this.body.init.headers == null) {
this.body.init.headers = FetchHeaders.createEmpty();
if (this.body.value == .Blob) {
const content_type = this.body.value.Blob.content_type;
if (content_type.len > 0) {
- this.body.init.headers.?.put("content-type", content_type);
+ this.body.init.headers.?.put("content-type", content_type, globalThis);
}
}
}
@@ -219,7 +219,7 @@ pub const Response = struct {
this: *Response,
globalThis: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
- return this.getOrCreateHeaders().toJS(globalThis);
+ return this.getOrCreateHeaders(globalThis).toJS(globalThis);
}
pub fn doClone(
@@ -230,7 +230,7 @@ pub const Response = struct {
var cloned = this.clone(getAllocator(globalThis), globalThis);
const val = Response.makeMaybePooled(globalThis, cloned);
if (this.body.init.headers) |headers| {
- cloned.body.init.headers = headers.cloneThis();
+ cloned.body.init.headers = headers.cloneThis(globalThis);
}
return val;
@@ -406,8 +406,8 @@ pub const Response = struct {
}
}
- var headers_ref = response.getOrCreateHeaders();
- headers_ref.putDefault("content-type", MimeType.json.value);
+ var headers_ref = response.getOrCreateHeaders(globalThis);
+ headers_ref.putDefault("content-type", MimeType.json.value, globalThis);
var ptr = response.allocator.create(Response) catch unreachable;
ptr.* = response;
@@ -453,9 +453,9 @@ pub const Response = struct {
}
}
- response.body.init.headers = response.getOrCreateHeaders();
+ response.body.init.headers = response.getOrCreateHeaders(globalThis);
var headers_ref = response.body.init.headers.?;
- headers_ref.put("location", url_string_slice.slice());
+ headers_ref.put("location", url_string_slice.slice(), globalThis);
var ptr = response.allocator.create(Response) catch unreachable;
ptr.* = response;
@@ -521,7 +521,7 @@ pub const Response = struct {
response.body.value.Blob.content_type.len > 0 and
!response.body.init.headers.?.fastHas(.ContentType))
{
- response.body.init.headers.?.put("content-type", response.body.value.Blob.content_type);
+ response.body.init.headers.?.put("content-type", response.body.value.Blob.content_type, globalThis);
}
return response;
@@ -1031,8 +1031,12 @@ pub const Fetch = struct {
headers = Headers.from(headers__, bun.default_allocator) catch unreachable;
// TODO: make this one pass
} else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| {
+ defer headers__.deref();
headers = Headers.from(headers__, bun.default_allocator) catch unreachable;
- headers__.deref();
+ } else {
+ // Converting the headers failed; return null and
+ // let the set exception get thrown
+ return null;
}
}
@@ -1185,7 +1189,8 @@ pub const Fetch = struct {
return null;
}
- var deferred_promise = JSC.C.JSObjectMakeDeferredPromise(globalThis, null, null, null);
+ var promise = JSPromise.Strong.init(globalThis);
+ var promise_val = promise.value();
if (!method.hasRequestBody() and body.size() > 0) {
const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, fetch_error_unexpected_body, .{}, ctx);
@@ -1199,9 +1204,9 @@ pub const Fetch = struct {
.{ .method = method, .url = url, .headers = headers orelse Headers{
.allocator = bun.default_allocator,
}, .body = body, .timeout = std.time.ns_per_hour, .disable_keepalive = disable_keepalive, .disable_timeout = disable_timeout, .follow_redirects = follow_redirects, .verbose = verbose, .proxy = proxy, .url_proxy_buffer = url_proxy_buffer, .signal = signal, .globalThis = globalThis },
- JSC.JSValue.fromRef(deferred_promise),
+ promise_val,
) catch unreachable;
- return deferred_promise;
+ return promise_val.asRef();
}
};
diff --git a/test/bun.js/fetch_headers.test.js b/test/bun.js/fetch_headers.test.js
new file mode 100644
index 000000000..7f8fab188
--- /dev/null
+++ b/test/bun.js/fetch_headers.test.js
@@ -0,0 +1,58 @@
+import { describe, it, expect, beforeAll, afterAll } from "bun:test";
+const port = 3009;
+const url = `http://localhost:${port}`;
+let server;
+
+describe("Headers", async () => {
+ // Start up a single server and reuse it between tests
+ beforeAll(() => {
+ server = Bun.serve({
+ fetch(req) {
+ const hdr = req.headers.get("x-test");
+ return new Response(hdr);
+ },
+ port: port,
+ });
+ });
+ afterAll(() => {
+ server.stop();
+ });
+
+ it("Headers should work", async () => {
+ expect(await fetchContent({"x-test": "header 1"})).toBe("header 1");
+ });
+
+ it("Header names must be valid", async () => {
+ expect(() => fetch(url, {headers: {"a\tb:c": "foo" }})).toThrow("Invalid header name: 'a\tb:c'");
+ expect(() => fetch(url, {headers: {"❤️": "foo" }})).toThrow("Invalid header name: '❤️'");
+ });
+
+ it("Header values must be valid", async () => {
+ expect(() => fetch(url, {headers: {"x-test": "\0" }})).toThrow("Header 'x-test' has invalid value: '\0'");
+ expect(() => fetch(url, {headers: {"x-test": "❤️" }})).toThrow("Header 'x-test' has invalid value: '❤️'");
+ });
+
+ it("repro 1602", async () => {
+ const origString = "😂1234".slice(3);
+
+ var encoder = new TextEncoder();
+ var decoder = new TextDecoder();
+ const roundTripString = decoder.decode(encoder.encode(origString));
+
+ expect(roundTripString).toBe(origString);
+
+ // This one will pass
+ expect(await fetchContent({"x-test": roundTripString})).toBe(roundTripString);
+ // This would hang
+ expect(await fetchContent({"x-test": origString})).toBe(origString);
+ });
+});
+
+async function fetchContent(headers) {
+ const res = await fetch(
+ url,
+ { headers: headers },
+ { verbose: true }
+ );
+ return await res.text();
+}