diff options
author | 2022-12-02 07:40:22 -0800 | |
---|---|---|
committer | 2022-12-02 07:42:44 -0800 | |
commit | d84f79bcc16d4e748c8a9400ea1cdb03d7f963fb (patch) | |
tree | 876d0a65d75c0f6aec97038e0d9f89c09f915c3f /src/bun.js/bindings/webcore/JSFetchHeaders.cpp | |
parent | 917cbc8d5d0ea9446db66910be46b7fff4697304 (diff) | |
download | bun-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.cpp | 140 |
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; |