aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-12-02 07:40:22 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-12-02 07:42:44 -0800
commitd84f79bcc16d4e748c8a9400ea1cdb03d7f963fb (patch)
tree876d0a65d75c0f6aec97038e0d9f89c09f915c3f /src/bun.js/bindings/webcore/JSFetchHeaders.cpp
parent917cbc8d5d0ea9446db66910be46b7fff4697304 (diff)
downloadbun-d84f79bcc16d4e748c8a9400ea1cdb03d7f963fb.tar.gz
bun-d84f79bcc16d4e748c8a9400ea1cdb03d7f963fb.tar.zst
bun-d84f79bcc16d4e748c8a9400ea1cdb03d7f963fb.zip
[fetch] Implement `Headers#getAll` and `Headers#getSetCookie()`
This matches Deno's behavior (get() combines, iterator preserves the order, set and append combine), but implements both the Cloudflare Workers `getAll()` and the potential standard `getSetCookie` function. The rationale for choosing both is to better support libraries which check for `getAll` and also because `getSetCookie` seems a little confusing (names are hard) This also makes `.toJSON` and JSON.stringify return an array for `Set-Cookie`
Diffstat (limited to 'src/bun.js/bindings/webcore/JSFetchHeaders.cpp')
-rw-r--r--src/bun.js/bindings/webcore/JSFetchHeaders.cpp140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
index 2c83778a2..94b574983 100644
--- a/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
+++ b/src/bun.js/bindings/webcore/JSFetchHeaders.cpp
@@ -56,6 +56,8 @@
#include <wtf/URL.h>
#include <wtf/Vector.h>
+#include "GCDefferalContext.h"
+
namespace WebCore {
using namespace JSC;
@@ -182,6 +184,107 @@ JSC_DEFINE_CUSTOM_GETTER(jsFetchHeadersGetterCount, (JSC::JSGlobalObject * globa
return JSValue::encode(jsNumber(count));
}
+JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_getAll, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSFetchHeaders* castedThis = jsDynamicCast<JSFetchHeaders*>(callFrame->thisValue());
+ if (UNLIKELY(!castedThis)) {
+ return JSValue::encode(jsUndefined());
+ }
+
+ if (UNLIKELY(!callFrame->argumentCount())) {
+ throwTypeError(lexicalGlobalObject, scope, "Missing argument"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto name = convert<IDLByteString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+
+ auto& impl = castedThis->wrapped();
+ if (name.length() != "set-cookie"_s.length() || name.convertToASCIILowercase() != "set-cookie"_s) {
+ return JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0));
+ }
+
+ auto values = impl.getSetCookieHeaders();
+ unsigned count = values.size();
+ if (!count) {
+ return JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0));
+ }
+
+ JSC::JSArray* array = nullptr;
+ GCDeferralContext deferralContext(lexicalGlobalObject->vm());
+ JSC::ObjectInitializationScope initializationScope(lexicalGlobalObject->vm());
+ if ((array = JSC::JSArray::tryCreateUninitializedRestricted(
+ initializationScope, &deferralContext,
+ lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ count))) {
+ for (unsigned i = 0; i < count; ++i) {
+ array->initializeIndex(initializationScope, i, jsString(vm, values[i]));
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ }
+ } else {
+ array = constructEmptyArray(lexicalGlobalObject, nullptr, count);
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ if (!array) {
+ throwOutOfMemoryError(lexicalGlobalObject, scope);
+ return JSValue::encode(jsUndefined());
+ }
+ for (unsigned i = 0; i < count; ++i) {
+ array->putDirectIndex(lexicalGlobalObject, i, jsString(vm, values[i]));
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ }
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ }
+
+ return JSValue::encode(array);
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_getSetCookie, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ JSFetchHeaders* castedThis = jsDynamicCast<JSFetchHeaders*>(callFrame->thisValue());
+ if (UNLIKELY(!castedThis)) {
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto& impl = castedThis->wrapped();
+ auto values = impl.getSetCookieHeaders();
+ unsigned count = values.size();
+
+ if (!count) {
+ return JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0));
+ }
+
+ JSC::JSArray* array = nullptr;
+ GCDeferralContext deferralContext(lexicalGlobalObject->vm());
+ JSC::ObjectInitializationScope initializationScope(lexicalGlobalObject->vm());
+ if ((array = JSC::JSArray::tryCreateUninitializedRestricted(
+ initializationScope, &deferralContext,
+ lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ count))) {
+ for (unsigned i = 0; i < count; ++i) {
+ array->initializeIndex(initializationScope, i, jsString(vm, values[i]));
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ }
+ } else {
+ array = constructEmptyArray(lexicalGlobalObject, nullptr, count);
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ if (!array) {
+ throwOutOfMemoryError(lexicalGlobalObject, scope);
+ return JSValue::encode(jsUndefined());
+ }
+ for (unsigned i = 0; i < count; ++i) {
+ array->putDirectIndex(lexicalGlobalObject, i, jsString(vm, values[i]));
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ }
+ RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined()));
+ }
+
+ return JSValue::encode(array);
+}
+
/* Hash table for prototype */
static const HashTableValue JSFetchHeadersPrototypeTableValues[] = {
@@ -189,6 +292,7 @@ static const HashTableValue JSFetchHeadersPrototypeTableValues[] = {
{ "append"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_append, 2 } },
{ "delete"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_delete, 1 } },
{ "get"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_get, 1 } },
+ { "getAll"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_getAll, 1 } },
{ "has"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_has, 1 } },
{ "set"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_set, 2 } },
{ "entries"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_entries, 0 } },
@@ -197,6 +301,7 @@ static const HashTableValue JSFetchHeadersPrototypeTableValues[] = {
{ "forEach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_forEach, 1 } },
{ "toJSON"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_toJSON, 0 } },
{ "count"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsFetchHeadersGetterCount, 0 } },
+ { "getSetCookie"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFetchHeadersPrototypeFunction_getSetCookie, 0 } },
};
const ClassInfo JSFetchHeadersPrototype::s_info = { "Headers"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFetchHeadersPrototype) };
@@ -305,6 +410,41 @@ static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_toJSONBody(JSC
}
{
+ auto& values = internal.getSetCookieHeaders();
+
+ size_t count = values.size();
+
+ if (count > 0) {
+ JSC::JSArray* array = nullptr;
+ GCDeferralContext deferralContext(lexicalGlobalObject->vm());
+ JSC::ObjectInitializationScope initializationScope(lexicalGlobalObject->vm());
+ if ((array = JSC::JSArray::tryCreateUninitializedRestricted(
+ initializationScope, &deferralContext,
+ lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ count))) {
+ for (unsigned i = 0; i < count; ++i) {
+ array->initializeIndex(initializationScope, i, jsString(vm, values[i]));
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
+ }
+ } else {
+ array = constructEmptyArray(lexicalGlobalObject, nullptr, count);
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
+ if (!array) {
+ throwOutOfMemoryError(lexicalGlobalObject, throwScope);
+ return JSValue::encode(jsUndefined());
+ }
+ for (unsigned i = 0; i < count; ++i) {
+ array->putDirectIndex(lexicalGlobalObject, i, jsString(vm, values[i]));
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
+ }
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
+ }
+
+ obj->putDirect(vm, JSC::Identifier::fromString(vm, httpHeaderNameString(HTTPHeaderName::SetCookie).toStringWithoutCopying()), array, 0);
+ }
+ }
+
+ {
auto& vec = internal.uncommonHeaders();
for (auto it = vec.begin(); it != vec.end(); ++it) {
auto& name = it->key;