diff options
Diffstat (limited to '')
-rw-r--r-- | src/bun.js/base.zig | 7 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 108 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 40 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 14 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 12 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/HTTPParsers.cpp | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/JSFetchHeaders.cpp | 26 | ||||
-rw-r--r-- | src/bun.js/webcore/body.zig | 8 | ||||
-rw-r--r-- | src/bun.js/webcore/request.zig | 10 | ||||
-rw-r--r-- | src/bun.js/webcore/response.zig | 31 | ||||
-rw-r--r-- | test/bun.js/fetch_headers.test.js | 58 |
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(); +} |