Introduce Bun.Cookie & Bun.CookieMap & request.cookies (in BunRequest) (#18073)

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: pfg <pfg@pfg.pw>
This commit is contained in:
Jarred Sumner
2025-03-20 21:29:00 -07:00
committed by GitHub
parent a1690cd708
commit 9888570456
24 changed files with 4071 additions and 28 deletions

View File

@@ -3760,6 +3760,7 @@ declare module "bun" {
interface BunRequest<T extends string = string> extends Request {
params: RouterTypes.ExtractRouteParams<T>;
readonly cookies: CookieMap;
}
interface GenericServeOptions {
@@ -7528,4 +7529,86 @@ declare module "bun" {
| [pkg: string, info: BunLockFilePackageInfo, bunTag: string]
/** root */
| [pkg: string, info: Pick<BunLockFileBasePackageInfo, "bin" | "binDir">];
interface CookieInit {
name?: string;
value?: string;
domain?: string;
path?: string;
expires?: number | Date;
secure?: boolean;
sameSite?: CookieSameSite;
httpOnly?: boolean;
partitioned?: boolean;
maxAge?: number;
}
interface CookieStoreDeleteOptions {
name: string;
domain?: string | null;
path?: string;
}
interface CookieStoreGetOptions {
name?: string;
url?: string;
}
type CookieSameSite = "strict" | "lax" | "none";
class Cookie {
constructor(name: string, value: string, options?: CookieInit);
constructor(cookieString: string);
constructor(cookieObject?: CookieInit);
name: string;
value: string;
domain?: string;
path: string;
expires?: number;
secure: boolean;
sameSite: CookieSameSite;
partitioned: boolean;
maxAge?: number;
httpOnly: boolean;
isExpired(): boolean;
toString(): string;
toJSON(): CookieInit;
static parse(cookieString: string): Cookie;
static from(name: string, value: string, options?: CookieInit): Cookie;
static serialize(...cookies: Cookie[]): string;
}
class CookieMap implements Iterable<[string, Cookie]> {
constructor(init?: string[][] | Record<string, string> | string);
get(name: string): Cookie | null;
get(options?: CookieStoreGetOptions): Cookie | null;
getAll(name: string): Cookie[];
getAll(options?: CookieStoreGetOptions): Cookie[];
has(name: string, value?: string): boolean;
set(name: string, value: string): void;
set(options: CookieInit): void;
delete(name: string): void;
delete(options: CookieStoreDeleteOptions): void;
toString(): string;
toJSON(): Record<string, ReturnType<Cookie["toJSON"]>>;
readonly size: number;
entries(): IterableIterator<[string, Cookie]>;
keys(): IterableIterator<string>;
values(): IterableIterator<Cookie>;
forEach(callback: (value: Cookie, key: string, map: CookieMap) => void, thisArg?: any): void;
[Symbol.iterator](): IterableIterator<[string, Cookie]>;
}
}

View File

@@ -34,7 +34,10 @@
macro(OperationWasAborted, "The operation was aborted.") \
macro(OperationTimedOut, "The operation timed out.") \
macro(ConnectionWasClosed, "The connection was closed.") \
macro(OperationFailed, "The operation failed.")
macro(OperationFailed, "The operation failed.") \
macro(strict, "strict") \
macro(lax, "lax") \
macro(none, "none")
// clang-format on

View File

@@ -14,6 +14,9 @@
#include "JSURLSearchParams.h"
#include "JSDOMFormData.h"
#include <JavaScriptCore/JSCallbackObject.h>
#include "JSCookie.h"
#include "JSCookieMap.h"
namespace Bun {
using namespace JSC;
@@ -150,6 +153,7 @@ JSValue BunInjectedScriptHost::getInternalProperties(VM& vm, JSGlobalObject* exe
RETURN_IF_EXCEPTION(scope, {});
return array;
}
} else if (type == JSAsJSONType) {
if (auto* params = jsDynamicCast<JSURLSearchParams*>(value)) {
auto* array = constructEmptyArray(exec, nullptr);
@@ -157,6 +161,20 @@ JSValue BunInjectedScriptHost::getInternalProperties(VM& vm, JSGlobalObject* exe
RETURN_IF_EXCEPTION(scope, {});
return array;
}
if (auto* cookie = jsDynamicCast<JSCookie*>(value)) {
auto* array = constructEmptyArray(exec, nullptr);
constructDataProperties(vm, exec, array, WebCore::getInternalProperties(vm, exec, cookie));
RETURN_IF_EXCEPTION(scope, {});
return array;
}
if (auto* cookieMap = jsDynamicCast<JSCookieMap*>(value)) {
auto* array = constructEmptyArray(exec, nullptr);
constructDataProperties(vm, exec, array, WebCore::getInternalProperties(vm, exec, cookieMap));
RETURN_IF_EXCEPTION(scope, {});
return array;
}
}
}

View File

@@ -39,6 +39,8 @@
#include "GeneratedBunObject.h"
#include "JavaScriptCore/BunV8HeapSnapshotBuilder.h"
#include "BunObjectModule.h"
#include "JSCookie.h"
#include "JSCookieMap.h"
#ifdef WIN32
#include <ws2def.h>
@@ -82,6 +84,9 @@ static JSValue BunObject_getter_wrap_ArrayBufferSink(VM& vm, JSObject* bunObject
return jsCast<Zig::GlobalObject*>(bunObject->globalObject())->ArrayBufferSink();
}
static JSValue constructCookieObject(VM& vm, JSObject* bunObject);
static JSValue constructCookieMapObject(VM& vm, JSObject* bunObject);
static JSValue constructEnvObject(VM& vm, JSObject* object)
{
return jsCast<Zig::GlobalObject*>(object->globalObject())->processEnvObject();
@@ -694,6 +699,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
@begin bunObjectTable
$ constructBunShell DontDelete|PropertyCallback
ArrayBufferSink BunObject_getter_wrap_ArrayBufferSink DontDelete|PropertyCallback
Cookie constructCookieObject DontDelete|ReadOnly|PropertyCallback
CookieMap constructCookieMapObject DontDelete|ReadOnly|PropertyCallback
CryptoHasher BunObject_getter_wrap_CryptoHasher DontDelete|PropertyCallback
FFI BunObject_getter_wrap_FFI DontDelete|PropertyCallback
FileSystemRouter BunObject_getter_wrap_FileSystemRouter DontDelete|PropertyCallback
@@ -845,6 +852,18 @@ public:
const JSC::ClassInfo JSBunObject::s_info = { "Bun"_s, &Base::s_info, &bunObjectTable, nullptr, CREATE_METHOD_TABLE(JSBunObject) };
static JSValue constructCookieObject(VM& vm, JSObject* bunObject)
{
auto* zigGlobalObject = jsCast<Zig::GlobalObject*>(bunObject->globalObject());
return WebCore::JSCookie::getConstructor(vm, zigGlobalObject);
}
static JSValue constructCookieMapObject(VM& vm, JSObject* bunObject)
{
auto* zigGlobalObject = jsCast<Zig::GlobalObject*>(bunObject->globalObject());
return WebCore::JSCookieMap::getConstructor(vm, zigGlobalObject);
}
JSC::JSObject* createBunObject(VM& vm, JSObject* globalObject)
{
return JSBunObject::create(vm, jsCast<Zig::GlobalObject*>(globalObject));

View File

@@ -0,0 +1,310 @@
#include "Cookie.h"
#include "JSCookie.h"
#include "helpers.h"
#include <JavaScriptCore/ObjectConstructor.h>
#include <wtf/WallTime.h>
#include <wtf/text/StringToIntegerConversion.h>
namespace WebCore {
extern "C" JSC::EncodedJSValue Cookie__create(JSDOMGlobalObject* globalObject, const ZigString* name, const ZigString* value, const ZigString* domain, const ZigString* path, double expires, bool secure, int32_t sameSite, bool httpOnly, double maxAge, bool partitioned)
{
String nameStr = Zig::toString(*name);
String valueStr = Zig::toString(*value);
String domainStr = Zig::toString(*domain);
String pathStr = Zig::toString(*path);
CookieSameSite sameSiteEnum;
switch (sameSite) {
case 0:
sameSiteEnum = CookieSameSite::Strict;
break;
case 1:
sameSiteEnum = CookieSameSite::Lax;
break;
case 2:
sameSiteEnum = CookieSameSite::None;
break;
default:
sameSiteEnum = CookieSameSite::Strict;
}
auto result = Cookie::create(nameStr, valueStr, domainStr, pathStr, expires, secure, sameSiteEnum, httpOnly, maxAge, partitioned);
return JSC::JSValue::encode(WebCore::toJSNewlyCreated(globalObject, globalObject, WTFMove(result)));
}
extern "C" WebCore::Cookie* Cookie__fromJS(JSC::EncodedJSValue value)
{
return WebCoreCast<WebCore::JSCookie, WebCore::Cookie>(value);
}
Cookie::~Cookie() = default;
Cookie::Cookie(const String& name, const String& value,
const String& domain, const String& path,
double expires, bool secure, CookieSameSite sameSite,
bool httpOnly, double maxAge, bool partitioned)
: m_name(name)
, m_value(value)
, m_domain(domain)
, m_path(path.isEmpty() ? "/"_s : path)
, m_expires(expires)
, m_secure(secure)
, m_sameSite(sameSite)
, m_httpOnly(httpOnly)
, m_maxAge(maxAge)
, m_partitioned(partitioned)
{
}
Ref<Cookie> Cookie::create(const String& name, const String& value,
const String& domain, const String& path,
double expires, bool secure, CookieSameSite sameSite,
bool httpOnly, double maxAge, bool partitioned)
{
return adoptRef(*new Cookie(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned));
}
Ref<Cookie> Cookie::from(const String& name, const String& value,
const String& domain, const String& path,
double expires, bool secure, CookieSameSite sameSite,
bool httpOnly, double maxAge, bool partitioned)
{
return create(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned);
}
String Cookie::serialize(JSC::VM& vm, const Vector<Ref<Cookie>>& cookies)
{
if (cookies.isEmpty())
return emptyString();
StringBuilder builder;
bool first = true;
for (const auto& cookie : cookies) {
if (!first)
builder.append("; "_s);
cookie->appendTo(vm, builder);
first = false;
}
return builder.toString();
}
ExceptionOr<Ref<Cookie>> Cookie::parse(const String& cookieString)
{
// Split the cookieString by semicolons
Vector<String> parts = cookieString.split(';');
if (parts.isEmpty())
return Exception { TypeError, "Invalid cookie string: empty string"_s };
// First part is the name-value pair
String nameValueStr = parts[0].trim(isASCIIWhitespace<UChar>);
size_t equalsPos = nameValueStr.find('=');
if (equalsPos == notFound)
return Exception { TypeError, "Invalid cookie string: missing '=' in name-value pair"_s };
String name = nameValueStr.substring(0, equalsPos).trim(isASCIIWhitespace<UChar>);
String value = nameValueStr.substring(equalsPos + 1).trim(isASCIIWhitespace<UChar>);
if (name.isEmpty())
return Exception { TypeError, "Invalid cookie string: name cannot be empty"_s };
// Default values
String domain;
String path = "/"_s;
double expires = 0;
double maxAge = 0;
bool secure = false;
bool httpOnly = false;
bool partitioned = false;
CookieSameSite sameSite = CookieSameSite::Lax;
// Parse attributes
for (size_t i = 1; i < parts.size(); i++) {
String part = parts[i].trim(isASCIIWhitespace<UChar>);
size_t attrEqualsPos = part.find('=');
String attrName;
String attrValue;
if (attrEqualsPos == notFound) {
// Flag attribute like "Secure"
attrName = part.convertToASCIILowercase();
attrValue = emptyString();
} else {
attrName = part.substring(0, attrEqualsPos).trim(isASCIIWhitespace<UChar>).convertToASCIILowercase();
attrValue = part.substring(attrEqualsPos + 1).trim(isASCIIWhitespace<UChar>);
}
if (attrName == "domain"_s)
domain = attrValue;
else if (attrName == "path"_s)
path = attrValue;
else if (attrName == "expires"_s) {
if (!attrValue.containsOnlyLatin1())
return Exception { TypeError, "Invalid cookie string: expires is not a valid date"_s };
if (UNLIKELY(!attrValue.is8Bit())) {
auto asLatin1 = attrValue.latin1();
if (auto parsed = WTF::parseDate({ reinterpret_cast<const LChar*>(asLatin1.data()), asLatin1.length() })) {
expires = parsed;
} else {
return Exception { TypeError, "Invalid cookie string: expires is not a valid date"_s };
}
} else {
if (auto parsed = WTF::parseDate(attrValue.span<LChar>())) {
expires = parsed;
} else {
return Exception { TypeError, "Invalid cookie string: expires is not a valid date"_s };
}
}
} else if (attrName == "max-age"_s) {
if (auto parsed = WTF::parseIntegerAllowingTrailingJunk<int64_t>(attrValue); parsed.has_value()) {
maxAge = static_cast<double>(parsed.value());
} else {
return Exception { TypeError, "Invalid cookie string: max-age is not a number"_s };
}
} else if (attrName == "secure"_s)
secure
= true;
else if (attrName == "httponly"_s)
httpOnly
= true;
else if (attrName == "partitioned"_s)
partitioned
= true;
else if (attrName == "samesite"_s) {
if (WTF::equalIgnoringASCIICase(attrValue, "strict"_s))
sameSite = CookieSameSite::Strict;
else if (WTF::equalIgnoringASCIICase(attrValue, "lax"_s))
sameSite = CookieSameSite::Lax;
else if (WTF::equalIgnoringASCIICase(attrValue, "none"_s))
sameSite = CookieSameSite::None;
}
}
return adoptRef(*new Cookie(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned));
}
bool Cookie::isExpired() const
{
if (m_expires == 0)
return false; // Session cookie
auto currentTime = WTF::WallTime::now().secondsSinceEpoch().seconds() * 1000.0;
return currentTime > m_expires;
}
String Cookie::toString(JSC::VM& vm) const
{
StringBuilder builder;
appendTo(vm, builder);
return builder.toString();
}
void Cookie::appendTo(JSC::VM& vm, StringBuilder& builder) const
{
// Name=Value is the basic format
builder.append(m_name);
builder.append('=');
builder.append(m_value);
// Add domain if present
if (!m_domain.isEmpty()) {
builder.append("; Domain="_s);
builder.append(m_domain);
}
if (!m_path.isEmpty() && m_path != "/"_s) {
builder.append("; Path="_s);
builder.append(m_path);
}
// Add expires if present (not 0)
if (m_expires != 0) {
builder.append("; Expires="_s);
// In a real implementation, this would convert the timestamp to a proper date string
// For now, just use a numeric timestamp
WTF::GregorianDateTime dateTime;
vm.dateCache.msToGregorianDateTime(m_expires * 1000, WTF::TimeType::UTCTime, dateTime);
builder.append(WTF::makeRFC2822DateString(dateTime.weekDay(), dateTime.monthDay(), dateTime.month(), dateTime.year(), dateTime.hour(), dateTime.minute(), dateTime.second(), dateTime.utcOffsetInMinute()));
}
// Add Max-Age if present
if (m_maxAge != 0) {
builder.append("; Max-Age="_s);
builder.append(String::number(m_maxAge));
}
// Add secure flag if true
if (m_secure)
builder.append("; Secure"_s);
// Add HttpOnly flag if true
if (m_httpOnly)
builder.append("; HttpOnly"_s);
// Add Partitioned flag if true
if (m_partitioned)
builder.append("; Partitioned"_s);
// Add SameSite directive
switch (m_sameSite) {
case CookieSameSite::Strict:
builder.append("; SameSite=strict"_s);
break;
case CookieSameSite::Lax:
// lax is the default.
break;
case CookieSameSite::None:
builder.append("; SameSite=none"_s);
break;
}
}
JSC::JSValue Cookie::toJSON(JSC::VM& vm, JSC::JSGlobalObject* globalObject) const
{
auto scope = DECLARE_THROW_SCOPE(vm);
auto* object = JSC::constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure());
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
auto& builtinNames = Bun::builtinNames(vm);
object->putDirect(vm, vm.propertyNames->name, JSC::jsString(vm, m_name));
object->putDirect(vm, vm.propertyNames->value, JSC::jsString(vm, m_value));
if (!m_domain.isEmpty())
object->putDirect(vm, builtinNames.domainPublicName(), JSC::jsString(vm, m_domain));
object->putDirect(vm, builtinNames.pathPublicName(), JSC::jsString(vm, m_path));
if (m_expires != 0)
object->putDirect(vm, builtinNames.expiresPublicName(), JSC::jsNumber(m_expires));
if (m_maxAge != 0)
object->putDirect(vm, builtinNames.maxAgePublicName(), JSC::jsNumber(m_maxAge));
object->putDirect(vm, builtinNames.securePublicName(), JSC::jsBoolean(m_secure));
object->putDirect(vm, builtinNames.sameSitePublicName(), toJS(globalObject, m_sameSite));
object->putDirect(vm, builtinNames.httpOnlyPublicName(), JSC::jsBoolean(m_httpOnly));
object->putDirect(vm, builtinNames.partitionedPublicName(), JSC::jsBoolean(m_partitioned));
return object;
}
size_t Cookie::memoryCost() const
{
size_t cost = sizeof(Cookie);
cost += m_name.sizeInBytes();
cost += m_value.sizeInBytes();
cost += m_domain.sizeInBytes();
cost += m_path.sizeInBytes();
return cost;
}
} // namespace WebCore

View File

@@ -0,0 +1,90 @@
#pragma once
#include "root.h"
#include "ExceptionOr.h"
#include <wtf/RefCounted.h>
#include <wtf/text/WTFString.h>
namespace WebCore {
enum class CookieSameSite : uint8_t {
Strict,
Lax,
None
};
JSC::JSValue toJS(JSC::JSGlobalObject*, CookieSameSite);
class Cookie : public RefCounted<Cookie> {
public:
~Cookie();
static Ref<Cookie> create(const String& name, const String& value,
const String& domain, const String& path,
double expires, bool secure, CookieSameSite sameSite,
bool httpOnly, double maxAge, bool partitioned);
static ExceptionOr<Ref<Cookie>> parse(const String& cookieString);
static Ref<Cookie> from(const String& name, const String& value,
const String& domain, const String& path,
double expires, bool secure, CookieSameSite sameSite,
bool httpOnly, double maxAge, bool partitioned);
static String serialize(JSC::VM& vm, const Vector<Ref<Cookie>>& cookies);
const String& name() const { return m_name; }
void setName(const String& name) { m_name = name; }
const String& value() const { return m_value; }
void setValue(const String& value) { m_value = value; }
const String& domain() const { return m_domain; }
void setDomain(const String& domain) { m_domain = domain; }
const String& path() const { return m_path; }
void setPath(const String& path) { m_path = path; }
double expires() const { return m_expires; }
void setExpires(double expires) { m_expires = expires; }
bool secure() const { return m_secure; }
void setSecure(bool secure) { m_secure = secure; }
CookieSameSite sameSite() const { return m_sameSite; }
void setSameSite(CookieSameSite sameSite) { m_sameSite = sameSite; }
bool httpOnly() const { return m_httpOnly; }
void setHttpOnly(bool httpOnly) { m_httpOnly = httpOnly; }
double maxAge() const { return m_maxAge; }
void setMaxAge(double maxAge) { m_maxAge = maxAge; }
bool partitioned() const { return m_partitioned; }
void setPartitioned(bool partitioned) { m_partitioned = partitioned; }
bool isExpired() const;
void appendTo(JSC::VM& vm, StringBuilder& builder) const;
String toString(JSC::VM& vm) const;
JSC::JSValue toJSON(JSC::VM& vm, JSC::JSGlobalObject*) const;
size_t memoryCost() const;
private:
Cookie(const String& name, const String& value,
const String& domain, const String& path,
double expires, bool secure, CookieSameSite sameSite,
bool httpOnly, double maxAge, bool partitioned);
String m_name;
String m_value;
String m_domain;
String m_path;
double m_expires;
bool m_secure;
CookieSameSite m_sameSite;
bool m_httpOnly;
double m_maxAge;
bool m_partitioned;
};
} // namespace WebCore

View File

@@ -0,0 +1,303 @@
#include "CookieMap.h"
#include "JSCookieMap.h"
#include "helpers.h"
#include <wtf/text/ParsingUtilities.h>
#include <JavaScriptCore/ObjectConstructor.h>
namespace WebCore {
extern "C" JSC::EncodedJSValue CookieMap__create(JSDOMGlobalObject* globalObject, const ZigString* initStr)
{
String str = Zig::toString(*initStr);
auto result = CookieMap::create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>(str));
return JSC::JSValue::encode(WebCore::toJSNewlyCreated(globalObject, globalObject, result.releaseReturnValue()));
}
extern "C" WebCore::CookieMap* CookieMap__fromJS(JSC::EncodedJSValue value)
{
return WebCoreCast<WebCore::JSCookieMap, WebCore::CookieMap>(value);
}
CookieMap::~CookieMap() = default;
CookieMap::CookieMap()
{
}
CookieMap::CookieMap(const String& cookieString)
{
if (cookieString.isEmpty())
return;
Vector<String> pairs = cookieString.split(';');
for (auto& pair : pairs) {
pair = pair.trim(isASCIIWhitespace<UChar>);
if (pair.isEmpty())
continue;
size_t equalsPos = pair.find('=');
if (equalsPos == notFound)
continue;
String name = pair.substring(0, equalsPos).trim(isASCIIWhitespace<UChar>);
String value = pair.substring(equalsPos + 1).trim(isASCIIWhitespace<UChar>);
auto cookie = Cookie::create(name, value, String(), "/"_s, 0, false, CookieSameSite::Lax, false, 0, false);
m_cookies.append(WTFMove(cookie));
}
}
CookieMap::CookieMap(const HashMap<String, String>& pairs)
{
for (auto& entry : pairs) {
auto cookie = Cookie::create(entry.key, entry.value, String(), "/"_s, 0, false, CookieSameSite::Lax,
false, 0, false);
m_cookies.append(WTFMove(cookie));
}
}
CookieMap::CookieMap(const Vector<Vector<String>>& pairs)
{
for (const auto& pair : pairs) {
if (pair.size() == 2) {
auto cookie = Cookie::create(pair[0], pair[1], String(), "/"_s, 0, false, CookieSameSite::Lax, false, 0, false);
m_cookies.append(WTFMove(cookie));
}
}
}
ExceptionOr<Ref<CookieMap>> CookieMap::create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>&& variant)
{
auto visitor = WTF::makeVisitor(
[&](const Vector<Vector<String>>& pairs) -> ExceptionOr<Ref<CookieMap>> {
return adoptRef(*new CookieMap(pairs));
},
[&](const HashMap<String, String>& pairs) -> ExceptionOr<Ref<CookieMap>> {
return adoptRef(*new CookieMap(pairs));
},
[&](const String& cookieString) -> ExceptionOr<Ref<CookieMap>> {
return adoptRef(*new CookieMap(cookieString));
});
return std::visit(visitor, variant);
}
RefPtr<Cookie> CookieMap::get(const String& name) const
{
// Return the first cookie with the matching name
for (auto& cookie : m_cookies) {
if (cookie->name() == name)
return RefPtr<Cookie>(cookie.ptr());
}
return nullptr;
}
RefPtr<Cookie> CookieMap::get(const CookieStoreGetOptions& options) const
{
// If name is provided, use that for lookup
if (!options.name.isEmpty())
return get(options.name);
// If url is provided, use that for lookup
if (!options.url.isEmpty()) {
// TODO: Implement URL-based cookie lookup
// This would involve parsing the URL, extracting the domain, and
// finding the first cookie that matches that domain
}
return nullptr;
}
Vector<Ref<Cookie>> CookieMap::getAll(const String& name) const
{
// Return all cookies with the matching name
Vector<Ref<Cookie>> result;
for (auto& cookie : m_cookies) {
if (cookie->name() == name)
result.append(cookie);
}
return result;
}
Vector<Ref<Cookie>> CookieMap::getAll(const CookieStoreGetOptions& options) const
{
// If name is provided, use that for lookup
if (!options.name.isEmpty())
return getAll(options.name);
// If url is provided, use that for lookup
if (!options.url.isEmpty()) {
// TODO: Implement URL-based cookie lookup
// This would involve parsing the URL, extracting the domain, and
// finding all cookies that match that domain
}
return Vector<Ref<Cookie>>();
}
bool CookieMap::has(const String& name, const String& value) const
{
for (auto& cookie : m_cookies) {
if (cookie->name() == name && (value.isEmpty() || cookie->value() == value))
return true;
}
return false;
}
void CookieMap::set(const String& name, const String& value, bool httpOnly, bool partitioned, double maxAge)
{
// Remove any existing cookies with the same name
remove(name);
// Add the new cookie with proper settings
auto cookie = Cookie::create(name, value, String(), "/"_s, 0, false, CookieSameSite::Strict,
httpOnly, maxAge, partitioned);
m_cookies.append(WTFMove(cookie));
}
// Maintain backward compatibility with code that uses the old signature
void CookieMap::set(const String& name, const String& value)
{
// Remove any existing cookies with the same name
remove(name);
// Add the new cookie
auto cookie = Cookie::create(name, value, String(), "/"_s, 0, false, CookieSameSite::Strict, false, 0, false);
m_cookies.append(WTFMove(cookie));
}
void CookieMap::set(Ref<Cookie> cookie)
{
// Remove any existing cookies with the same name
remove(cookie->name());
// Add the new cookie
m_cookies.append(WTFMove(cookie));
}
void CookieMap::remove(const String& name)
{
m_cookies.removeAllMatching([&name](const auto& cookie) {
return cookie->name() == name;
});
}
void CookieMap::remove(const CookieStoreDeleteOptions& options)
{
String name = options.name;
String domain = options.domain;
String path = options.path;
m_cookies.removeAllMatching([&](const auto& cookie) {
if (cookie->name() != name)
return false;
// If domain is specified, it must match
if (!domain.isNull() && cookie->domain() != domain)
return false;
// If path is specified, it must match
if (!path.isNull() && cookie->path() != path)
return false;
return true;
});
}
Vector<Ref<Cookie>> CookieMap::getCookiesMatchingDomain(const String& domain) const
{
Vector<Ref<Cookie>> result;
for (auto& cookie : m_cookies) {
const auto& cookieDomain = cookie->domain();
if (cookieDomain.isEmpty() || cookieDomain == domain) {
result.append(cookie);
}
}
return result;
}
Vector<Ref<Cookie>> CookieMap::getCookiesMatchingPath(const String& path) const
{
Vector<Ref<Cookie>> result;
for (auto& cookie : m_cookies) {
// Simple path matching logic - a cookie matches if its path is a prefix of the requested path
if (path.startsWith(cookie->path())) {
result.append(cookie);
}
}
return result;
}
String CookieMap::toString(JSC::VM& vm) const
{
if (m_cookies.isEmpty())
return emptyString();
StringBuilder builder;
bool first = true;
for (auto& cookie : m_cookies) {
if (!first)
builder.append("; "_s);
cookie->appendTo(vm, builder);
first = false;
}
return builder.toString();
}
JSC::JSValue CookieMap::toJSON(JSC::JSGlobalObject* globalObject) const
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
// Create an array of cookie entries
auto* array = JSC::constructEmptyArray(globalObject, nullptr, m_cookies.size());
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
unsigned index = 0;
for (const auto& cookie : m_cookies) {
// For each cookie, create a [name, cookie JSON] entry
auto* entryArray = JSC::constructEmptyArray(globalObject, nullptr, 2);
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
entryArray->putDirectIndex(globalObject, 0, JSC::jsString(vm, cookie->name()));
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
entryArray->putDirectIndex(globalObject, 1, cookie->toJSON(vm, globalObject));
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
array->putDirectIndex(globalObject, index++, entryArray);
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
}
return array;
}
size_t CookieMap::memoryCost() const
{
size_t cost = sizeof(CookieMap);
for (auto& cookie : m_cookies) {
cost += cookie->memoryCost();
}
return cost;
}
std::optional<CookieMap::KeyValuePair> CookieMap::Iterator::next()
{
auto& cookies = m_target->m_cookies;
if (m_index >= cookies.size())
return std::nullopt;
auto& cookie = cookies[m_index++];
return KeyValuePair(cookie->name(), cookie.ptr());
}
CookieMap::Iterator::Iterator(CookieMap& cookieMap)
: m_target(cookieMap)
{
}
} // namespace WebCore

View File

@@ -0,0 +1,89 @@
#pragma once
#include "root.h"
#include "Cookie.h"
#include "ExceptionOr.h"
#include <wtf/HashMap.h>
#include <wtf/Vector.h>
#include <wtf/RefCounted.h>
#include <wtf/text/WTFString.h>
#include <variant>
namespace WebCore {
struct CookieStoreGetOptions {
String name;
String url;
};
struct CookieStoreDeleteOptions {
String name;
String domain;
String path;
};
class CookieMap : public RefCounted<CookieMap> {
public:
~CookieMap();
static ExceptionOr<Ref<CookieMap>> create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>&& init);
RefPtr<Cookie> get(const String& name) const;
RefPtr<Cookie> get(const CookieStoreGetOptions& options) const;
Vector<Ref<Cookie>> getAll(const String& name) const;
Vector<Ref<Cookie>> getAll(const CookieStoreGetOptions& options) const;
bool has(const String& name, const String& value = String()) const;
void set(const String& name, const String& value, bool httpOnly, bool partitioned, double maxAge);
void set(const String& name, const String& value);
void set(Ref<Cookie>);
void remove(const String& name);
void remove(const CookieStoreDeleteOptions& options);
String toString(JSC::VM& vm) const;
JSC::JSValue toJSON(JSC::JSGlobalObject*) const;
size_t size() const { return m_cookies.size(); }
size_t memoryCost() const;
// Define a simple struct to hold the key-value pair
struct KeyValuePair {
KeyValuePair(const String& k, RefPtr<Cookie> c)
: key(k)
, value(c)
{
}
String key;
RefPtr<Cookie> value;
};
class Iterator {
public:
explicit Iterator(CookieMap&);
std::optional<KeyValuePair> next();
private:
Ref<CookieMap> m_target;
size_t m_index { 0 };
};
Iterator createIterator() { return Iterator { *this }; }
Iterator createIterator(const void*) { return Iterator { *this }; }
private:
CookieMap();
CookieMap(const String& cookieString);
CookieMap(const HashMap<String, String>& pairs);
CookieMap(const Vector<Vector<String>>& pairs);
Vector<Ref<Cookie>> getCookiesMatchingDomain(const String& domain) const;
Vector<Ref<Cookie>> getCookiesMatchingPath(const String& path) const;
Vector<Ref<Cookie>> m_cookies;
};
} // namespace WebCore

View File

@@ -7,13 +7,20 @@
#include "ZigGlobalObject.h"
#include "AsyncContextFrame.h"
#include <JavaScriptCore/ObjectConstructor.h>
#include "JSFetchHeaders.h"
#include "JSCookieMap.h"
#include "Cookie.h"
#include "CookieMap.h"
#include "JSDOMExceptionHandling.h"
namespace Bun {
static JSC_DECLARE_CUSTOM_GETTER(jsJSBunRequestGetParams);
static JSC_DECLARE_CUSTOM_GETTER(jsJSBunRequestGetCookies);
static const HashTableValue JSBunRequestPrototypeValues[] = {
{ "params"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsJSBunRequestGetParams, nullptr } },
{ "cookies"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsJSBunRequestGetCookies, nullptr } },
};
JSBunRequest* JSBunRequest::create(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr, JSObject* params)
@@ -51,6 +58,19 @@ void JSBunRequest::setParams(JSObject* params)
m_params.set(Base::vm(), this, params);
}
JSObject* JSBunRequest::cookies() const
{
if (m_cookies) {
return m_cookies.get();
}
return nullptr;
}
void JSBunRequest::setCookies(JSObject* cookies)
{
m_cookies.set(Base::vm(), this, cookies);
}
JSBunRequest::JSBunRequest(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
: Base(vm, structure, sinkPtr)
{
@@ -61,6 +81,7 @@ void JSBunRequest::finishCreation(JSC::VM& vm, JSObject* params)
{
Base::finishCreation(vm);
m_params.setMayBeNull(vm, this, params);
m_cookies.clear();
Bun__JSRequest__calculateEstimatedByteSize(this->wrapped());
auto size = Request__estimatedSize(this->wrapped());
@@ -73,6 +94,7 @@ void JSBunRequest::visitChildrenImpl(JSCell* cell, Visitor& visitor)
JSBunRequest* thisCallSite = jsCast<JSBunRequest*>(cell);
Base::visitChildren(thisCallSite, visitor);
visitor.append(thisCallSite->m_params);
visitor.append(thisCallSite->m_cookies);
}
DEFINE_VISIT_CHILDREN(JSBunRequest);
@@ -137,6 +159,47 @@ JSC_DEFINE_CUSTOM_GETTER(jsJSBunRequestGetParams, (JSC::JSGlobalObject * globalO
return JSValue::encode(params);
}
JSC_DEFINE_CUSTOM_GETTER(jsJSBunRequestGetCookies, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
{
JSBunRequest* request = jsDynamicCast<JSBunRequest*>(JSValue::decode(thisValue));
if (!request)
return JSValue::encode(jsUndefined());
auto* cookies = request->cookies();
if (!cookies) {
auto& vm = globalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& names = builtinNames(vm);
JSC::JSValue headersValue = request->get(globalObject, names.headersPublicName());
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
auto* headers = jsDynamicCast<WebCore::JSFetchHeaders*>(headersValue);
if (!headers)
return JSValue::encode(jsUndefined());
auto& fetchHeaders = headers->wrapped();
auto cookieHeader = fetchHeaders.internalHeaders().get(HTTPHeaderName::Cookie);
// Create a CookieMap from the cookie header
auto cookieMapResult = WebCore::CookieMap::create(cookieHeader);
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
if (cookieMapResult.hasException()) {
WebCore::propagateException(*globalObject, throwScope, cookieMapResult.releaseException());
return JSValue::encode(jsUndefined());
}
auto cookieMap = cookieMapResult.releaseReturnValue();
// Convert to JS
auto cookies = WebCore::toJSNewlyCreated(globalObject, jsCast<JSDOMGlobalObject*>(globalObject), WTFMove(cookieMap));
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
request->setCookies(cookies.getObject());
return JSValue::encode(cookies);
}
return JSValue::encode(cookies);
}
Structure* createJSBunRequestStructure(JSC::VM& vm, Zig::GlobalObject* globalObject)
{
auto prototypeStructure = JSBunRequestPrototype::createStructure(vm, globalObject, globalObject->JSRequestPrototype());

View File

@@ -32,11 +32,15 @@ public:
JSObject* params() const;
void setParams(JSObject* params);
JSObject* cookies() const;
void setCookies(JSObject* cookies);
private:
JSBunRequest(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr);
void finishCreation(JSC::VM& vm, JSObject* params);
mutable JSC::WriteBarrier<JSC::JSObject> m_params;
mutable JSC::WriteBarrier<JSC::JSObject> m_cookies;
};
JSC::Structure* createJSBunRequestStructure(JSC::VM&, Zig::GlobalObject*);

View File

@@ -130,6 +130,7 @@
#include "ObjectBindings.h"
#include <JavaScriptCore/VMInlines.h>
#include "wtf-bindings.h"
#if OS(DARWIN)
#if BUN_DEBUG
@@ -6063,7 +6064,7 @@ extern "C" EncodedJSValue JSC__JSValue__dateInstanceFromNullTerminatedString(JSC
// this is largely copied from dateProtoFuncToISOString
extern "C" int JSC__JSValue__toISOString(JSC::JSGlobalObject* globalObject, EncodedJSValue dateValue, char* buf)
{
char buffer[29];
char buffer[64];
JSC::DateInstance* thisDateObj = JSC::jsDynamicCast<JSC::DateInstance*>(JSC::JSValue::decode(dateValue));
if (!thisDateObj)
return -1;
@@ -6073,28 +6074,7 @@ extern "C" int JSC__JSValue__toISOString(JSC::JSGlobalObject* globalObject, Enco
auto& vm = JSC::getVM(globalObject);
const GregorianDateTime* gregorianDateTime = thisDateObj->gregorianDateTimeUTC(vm.dateCache);
if (!gregorianDateTime)
return -1;
// If the year is outside the bounds of 0 and 9999 inclusive we want to use the extended year format (ES 15.9.1.15.1).
int ms = static_cast<int>(fmod(thisDateObj->internalNumber(), msPerSecond));
if (ms < 0)
ms += msPerSecond;
int charactersWritten;
if (gregorianDateTime->year() > 9999 || gregorianDateTime->year() < 0)
charactersWritten = snprintf(buffer, sizeof(buffer), "%+07d-%02d-%02dT%02d:%02d:%02d.%03dZ", gregorianDateTime->year(), gregorianDateTime->month() + 1, gregorianDateTime->monthDay(), gregorianDateTime->hour(), gregorianDateTime->minute(), gregorianDateTime->second(), ms);
else
charactersWritten = snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", gregorianDateTime->year(), gregorianDateTime->month() + 1, gregorianDateTime->monthDay(), gregorianDateTime->hour(), gregorianDateTime->minute(), gregorianDateTime->second(), ms);
memcpy(buf, buffer, charactersWritten);
ASSERT(charactersWritten > 0 && static_cast<unsigned>(charactersWritten) < sizeof(buffer));
if (static_cast<unsigned>(charactersWritten) >= sizeof(buffer))
return -1;
return charactersWritten;
return static_cast<int>(Bun::toISOString(vm, thisDateObj->internalNumber(), buffer));
}
extern "C" int JSC__JSValue__DateNowISOString(JSC::JSGlobalObject* globalObject, char* buf)

View File

@@ -73,8 +73,11 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormDataIterator;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMURL;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParams;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParamsIterator;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCookie;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCookieMap;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCookieMapIterator;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForExposedToWorkerAndWindow;

View File

@@ -541,6 +541,8 @@ enum class DOMConstructorID : uint16_t {
TextMetrics,
TimeRanges,
URLSearchParams,
Cookie,
CookieMap,
ValidityState,
WebKitMediaKeyError,
ANGLEInstancedArrays,
@@ -858,7 +860,7 @@ enum class DOMConstructorID : uint16_t {
EventEmitter,
};
static constexpr unsigned numberOfDOMConstructorsBase = 846;
static constexpr unsigned numberOfDOMConstructorsBase = 848;
static constexpr unsigned bunExtraConstructors = 1;

View File

@@ -919,6 +919,10 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForExposedToWorkerAndWindow;
std::unique_ptr<IsoSubspace> m_subspaceForURLSearchParams;
std::unique_ptr<IsoSubspace> m_subspaceForURLSearchParamsIterator;
std::unique_ptr<IsoSubspace> m_subspaceForCookie;
std::unique_ptr<IsoSubspace> m_subspaceForCookieMap;
std::unique_ptr<IsoSubspace> m_subspaceForCookieMapIterator;
std::unique_ptr<IsoSubspace> m_subspaceForDOMException;
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormData;

View File

@@ -0,0 +1,893 @@
#include "config.h"
#include "JSCookie.h"
#include "DOMClientIsoSubspaces.h"
#include "DOMIsoSubspaces.h"
#include "IDLTypes.h"
#include "JSDOMBinding.h"
#include "JSDOMConstructor.h"
#include "JSDOMConvertBase.h"
#include "JSDOMConvertBoolean.h"
#include "JSDOMConvertInterface.h"
#include "JSDOMConvertNullable.h"
#include "JSDOMConvertNumbers.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMGlobalObject.h"
#include "JSDOMGlobalObjectInlines.h"
#include "JSDOMOperation.h"
#include "JSDOMWrapperCache.h"
#include <JavaScriptCore/HeapAnalyzer.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/SlotVisitorMacros.h>
#include <JavaScriptCore/SubspaceInlines.h>
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <JavaScriptCore/DateInstance.h>
namespace WebCore {
using namespace JSC;
// Helper for getting wrapped Cookie from JS value
static Cookie* toCookieWrapped(JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& throwScope, JSValue value)
{
auto& vm = getVM(lexicalGlobalObject);
auto* impl = JSCookie::toWrapped(vm, value);
if (UNLIKELY(!impl))
throwVMTypeError(lexicalGlobalObject, throwScope);
return impl;
}
static JSC_DECLARE_HOST_FUNCTION(jsCookiePrototypeFunction_toString);
static JSC_DECLARE_HOST_FUNCTION(jsCookiePrototypeFunction_toJSON);
static JSC_DECLARE_HOST_FUNCTION(jsCookieStaticFunctionParse);
static JSC_DECLARE_HOST_FUNCTION(jsCookieStaticFunctionFrom);
static JSC_DECLARE_HOST_FUNCTION(jsCookieStaticFunctionSerialize);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_name);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_name);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_value);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_value);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_domain);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_domain);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_path);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_path);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_expires);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_expires);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_secure);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_secure);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_sameSite);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_sameSite);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_httpOnly);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_httpOnly);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_maxAge);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_maxAge);
static JSC_DECLARE_CUSTOM_GETTER(jsCookiePrototypeGetter_partitioned);
static JSC_DECLARE_CUSTOM_SETTER(jsCookiePrototypeSetter_partitioned);
static JSC_DECLARE_HOST_FUNCTION(jsCookiePrototypeFunction_isExpired);
static JSC_DECLARE_CUSTOM_GETTER(jsCookieConstructor);
class JSCookiePrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSCookiePrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
{
JSCookiePrototype* ptr = new (NotNull, JSC::allocateCell<JSCookiePrototype>(vm)) JSCookiePrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCookiePrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
private:
JSCookiePrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
: JSC::JSNonFinalObject(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCookiePrototype, JSCookiePrototype::Base);
JSValue getInternalProperties(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSCookie* castedThis)
{
return castedThis->wrapped().toJSON(vm, lexicalGlobalObject);
}
using JSCookieDOMConstructor = JSDOMConstructor<JSCookie>;
template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSCookieDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* castedThis = jsCast<JSCookieDOMConstructor*>(callFrame->jsCallee());
// Check if this was called with 'new'
if (UNLIKELY(!callFrame->thisValue().isObject()))
return throwVMError(lexicalGlobalObject, throwScope, createNotAConstructorError(lexicalGlobalObject, callFrame->jsCallee()));
// Static method: parse(cookieString)
if (callFrame->argumentCount() == 1) {
auto cookieString = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->argument(0));
RETURN_IF_EXCEPTION(throwScope, {});
auto result = Cookie::parse(cookieString);
RETURN_IF_EXCEPTION(throwScope, {});
auto* globalObject = castedThis->globalObject();
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS(lexicalGlobalObject, globalObject, result.releaseReturnValue())));
}
// Constructor: Cookie.from(name, value, options)
if (callFrame->argumentCount() >= 2) {
auto name = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->argument(0));
RETURN_IF_EXCEPTION(throwScope, {});
auto value = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->argument(1));
RETURN_IF_EXCEPTION(throwScope, {});
// Default values
String domain;
String path = "/"_s;
double expires = 0;
double maxAge = 0;
bool secure = false;
bool httpOnly = false;
bool partitioned = false;
CookieSameSite sameSite = CookieSameSite::Lax;
auto& names = builtinNames(vm);
// Optional options parameter (third argument)
if (callFrame->argumentCount() > 2 && !callFrame->argument(2).isUndefinedOrNull()) {
auto options = callFrame->argument(2);
if (!options.isObject())
return throwVMTypeError(lexicalGlobalObject, throwScope, "Options must be an object"_s);
auto* optionsObj = options.getObject();
// domain
if (auto domainValue = optionsObj->get(lexicalGlobalObject, names.domainPublicName());
!domainValue.isUndefined() && !domainValue.isNull()) {
domain = convert<IDLUSVString>(*lexicalGlobalObject, domainValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
// path
if (auto pathValue = optionsObj->get(lexicalGlobalObject, names.pathPublicName());
!pathValue.isUndefined() && !pathValue.isNull()) {
path = convert<IDLUSVString>(*lexicalGlobalObject, pathValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
// expires
if (auto expiresValue = optionsObj->get(lexicalGlobalObject, names.expiresPublicName());
!expiresValue.isUndefined() && !expiresValue.isNull()) {
// Handle both Date objects and numeric timestamps
if (expiresValue.inherits<JSC::DateInstance>()) {
JSC::DateInstance* dateInstance = JSC::jsCast<JSC::DateInstance*>(expiresValue.asCell());
expires = dateInstance->internalNumber();
} else if (expiresValue.isNumber()) {
expires = expiresValue.asNumber();
}
RETURN_IF_EXCEPTION(throwScope, {});
}
// maxAge
if (auto maxAgeValue = optionsObj->get(lexicalGlobalObject, names.maxAgePublicName());
!maxAgeValue.isUndefined() && !maxAgeValue.isNull() && maxAgeValue.isNumber()) {
maxAge = maxAgeValue.asNumber();
RETURN_IF_EXCEPTION(throwScope, {});
}
// secure
if (auto secureValue = optionsObj->get(lexicalGlobalObject, names.securePublicName());
!secureValue.isUndefined()) {
secure = secureValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
// httpOnly
if (auto httpOnlyValue = optionsObj->get(lexicalGlobalObject, names.httpOnlyPublicName());
!httpOnlyValue.isUndefined()) {
httpOnly = httpOnlyValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
// partitioned
if (auto partitionedValue = optionsObj->get(lexicalGlobalObject, names.partitionedPublicName());
!partitionedValue.isUndefined()) {
partitioned = partitionedValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
// sameSite
if (auto sameSiteValue = optionsObj->get(lexicalGlobalObject, names.sameSitePublicName());
!sameSiteValue.isUndefined() && !sameSiteValue.isNull()) {
String sameSiteStr = convert<IDLUSVString>(*lexicalGlobalObject, sameSiteValue);
RETURN_IF_EXCEPTION(throwScope, {});
if (sameSiteStr == "strict"_s)
sameSite = CookieSameSite::Strict;
else if (sameSiteStr == "lax"_s)
sameSite = CookieSameSite::Lax;
else if (sameSiteStr == "none"_s)
sameSite = CookieSameSite::None;
else
return throwVMTypeError(lexicalGlobalObject, throwScope, "Invalid sameSite value. Must be 'strict', 'lax', or 'none'"_s);
}
}
auto cookie = Cookie::create(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned);
auto* globalObject = castedThis->globalObject();
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS(lexicalGlobalObject, globalObject, WTFMove(cookie))));
}
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
}
JSC_ANNOTATE_HOST_FUNCTION(JSCookieDOMConstructorConstruct, JSCookieDOMConstructor::construct);
// Setup for JSCookieDOMConstructor
template<> const ClassInfo JSCookieDOMConstructor::s_info = { "Cookie"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCookieDOMConstructor) };
template<> JSValue JSCookieDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
{
return globalObject.objectPrototype();
}
template<> void JSCookieDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
{
putDirect(vm, vm.propertyNames->length, jsNumber(2), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
JSString* nameString = jsNontrivialString(vm, "Cookie"_s);
m_originalName.set(vm, this, nameString);
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->prototype, JSCookie::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
// Add static methods
JSC::JSFunction* parseFunction = JSC::JSFunction::create(vm, &globalObject, 1, "parse"_s, jsCookieStaticFunctionParse, JSC::ImplementationVisibility::Public, JSC::NoIntrinsic);
putDirect(vm, Identifier::fromString(vm, "parse"_s), parseFunction, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete));
JSC::JSFunction* fromFunction = JSC::JSFunction::create(vm, &globalObject, 3, "from"_s, jsCookieStaticFunctionFrom, JSC::ImplementationVisibility::Public, JSC::NoIntrinsic);
putDirect(vm, Identifier::fromString(vm, "from"_s), fromFunction, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete));
JSC::JSFunction* serializeFunction = JSC::JSFunction::create(vm, &globalObject, 1, "serialize"_s, jsCookieStaticFunctionSerialize, JSC::ImplementationVisibility::Public, JSC::NoIntrinsic);
putDirect(vm, Identifier::fromString(vm, "serialize"_s), serializeFunction, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete));
}
static const HashTableValue JSCookiePrototypeTableValues[] = {
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookieConstructor, 0 } },
{ "name"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_name, jsCookiePrototypeSetter_name } },
{ "value"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_value, jsCookiePrototypeSetter_value } },
{ "domain"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_domain, jsCookiePrototypeSetter_domain } },
{ "path"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_path, jsCookiePrototypeSetter_path } },
{ "expires"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_expires, jsCookiePrototypeSetter_expires } },
{ "maxAge"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_maxAge, jsCookiePrototypeSetter_maxAge } },
{ "secure"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_secure, jsCookiePrototypeSetter_secure } },
{ "httpOnly"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_httpOnly, jsCookiePrototypeSetter_httpOnly } },
{ "sameSite"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_sameSite, jsCookiePrototypeSetter_sameSite } },
{ "partitioned"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookiePrototypeGetter_partitioned, jsCookiePrototypeSetter_partitioned } },
{ "isExpired"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookiePrototypeFunction_isExpired, 0 } },
{ "toString"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookiePrototypeFunction_toString, 0 } },
{ "toJSON"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookiePrototypeFunction_toJSON, 0 } },
};
const ClassInfo JSCookiePrototype::s_info = { "Cookie"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCookiePrototype) };
void JSCookiePrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSCookie::info(), JSCookiePrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
const ClassInfo JSCookie::s_info = { "Cookie"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCookie) };
JSCookie::JSCookie(Structure* structure, JSDOMGlobalObject& globalObject, Ref<Cookie>&& impl)
: JSDOMWrapper<Cookie>(structure, globalObject, WTFMove(impl))
{
}
void JSCookie::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
JSObject* JSCookie::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
auto* structure = JSCookiePrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
structure->setMayBePrototype(true);
return JSCookiePrototype::create(vm, &globalObject, structure);
}
JSObject* JSCookie::prototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return getDOMPrototype<JSCookie>(vm, globalObject);
}
JSValue JSCookie::getConstructor(VM& vm, const JSGlobalObject* globalObject)
{
return getDOMConstructor<JSCookieDOMConstructor, DOMConstructorID::Cookie>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
}
void JSCookie::destroy(JSC::JSCell* cell)
{
JSCookie* thisObject = static_cast<JSCookie*>(cell);
thisObject->JSCookie::~JSCookie();
}
JSC_DEFINE_CUSTOM_GETTER(jsCookieConstructor, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* prototype = jsDynamicCast<JSCookiePrototype*>(JSValue::decode(thisValue));
if (UNLIKELY(!prototype))
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(JSCookie::getConstructor(vm, prototype->globalObject()));
}
// Instance methods
static inline JSC::EncodedJSValue jsCookiePrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookie>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLDOMString>(*lexicalGlobalObject, throwScope, impl.toString(vm))));
}
JSC_DEFINE_HOST_FUNCTION(jsCookiePrototypeFunction_toString, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookie>::call<jsCookiePrototypeFunction_toStringBody>(*lexicalGlobalObject, *callFrame, "toString");
}
// Implementation of the toJSON method
static inline JSC::EncodedJSValue jsCookiePrototypeFunction_toJSONBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookie>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
// Delegate to the C++ toJSON method
JSC::JSValue result = impl.toJSON(vm, lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
return JSValue::encode(result);
}
JSC_DEFINE_HOST_FUNCTION(jsCookiePrototypeFunction_toJSON, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookie>::call<jsCookiePrototypeFunction_toJSONBody>(*lexicalGlobalObject, *callFrame, "toJSON");
}
// Static function implementations
JSC_DEFINE_HOST_FUNCTION(jsCookieStaticFunctionParse, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 1)
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
auto cookieString = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(throwScope, {});
auto result = Cookie::parse(cookieString);
RETURN_IF_EXCEPTION(throwScope, {});
auto* globalObject = jsCast<JSDOMGlobalObject*>(lexicalGlobalObject);
return JSValue::encode(toJS(lexicalGlobalObject, globalObject, result.releaseReturnValue()));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieStaticFunctionFrom, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 2)
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
auto name = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(throwScope, {});
auto value = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(1));
RETURN_IF_EXCEPTION(throwScope, {});
// Optional parameters
String domain;
String path = "/"_s;
double expires = 0;
double maxAge = 0;
bool secure = false;
bool httpOnly = false;
bool partitioned = false;
auto& builtinNames = Bun::builtinNames(vm);
CookieSameSite sameSite = CookieSameSite::Lax;
// Check for options object
if (callFrame->argumentCount() > 2 && !callFrame->uncheckedArgument(2).isUndefinedOrNull() && callFrame->uncheckedArgument(2).isObject()) {
auto* options = callFrame->uncheckedArgument(2).getObject();
// domain
auto domainValue = options->get(lexicalGlobalObject, builtinNames.domainPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!domainValue.isUndefined() && !domainValue.isNull()) {
domain = convert<IDLUSVString>(*lexicalGlobalObject, domainValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
// path
auto pathValue = options->get(lexicalGlobalObject, builtinNames.pathPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!pathValue.isUndefined() && !pathValue.isNull()) {
path = convert<IDLUSVString>(*lexicalGlobalObject, pathValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
// expires
auto expiresValue = options->get(lexicalGlobalObject, builtinNames.expiresPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!expiresValue.isUndefined() && !expiresValue.isNull()) {
if (auto* dateInstance = jsDynamicCast<JSC::DateInstance*>(expiresValue)) {
expires = dateInstance->internalNumber();
} else if (expiresValue.isNumber()) {
expires = expiresValue.asNumber();
}
RETURN_IF_EXCEPTION(throwScope, {});
}
// maxAge
auto maxAgeValue = options->get(lexicalGlobalObject, builtinNames.maxAgePublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!maxAgeValue.isUndefined() && !maxAgeValue.isNull() && maxAgeValue.isNumber()) {
maxAge = maxAgeValue.asNumber();
RETURN_IF_EXCEPTION(throwScope, {});
}
// secure
auto secureValue = options->get(lexicalGlobalObject, builtinNames.securePublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!secureValue.isUndefined()) {
secure = secureValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
// httpOnly
auto httpOnlyValue = options->get(lexicalGlobalObject, builtinNames.httpOnlyPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!httpOnlyValue.isUndefined()) {
httpOnly = httpOnlyValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
// partitioned
auto partitionedValue = options->get(lexicalGlobalObject, builtinNames.partitionedPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!partitionedValue.isUndefined()) {
partitioned = partitionedValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
}
// sameSite
auto sameSiteValue = options->get(lexicalGlobalObject, builtinNames.sameSitePublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!sameSiteValue.isUndefined() && !sameSiteValue.isNull()) {
String sameSiteStr = convert<IDLUSVString>(*lexicalGlobalObject, sameSiteValue);
RETURN_IF_EXCEPTION(throwScope, {});
if (WTF::equalIgnoringASCIICase(sameSiteStr, "strict"_s))
sameSite = CookieSameSite::Strict;
else if (WTF::equalIgnoringASCIICase(sameSiteStr, "lax"_s))
sameSite = CookieSameSite::Lax;
else if (WTF::equalIgnoringASCIICase(sameSiteStr, "none"_s))
sameSite = CookieSameSite::None;
else
return throwVMTypeError(lexicalGlobalObject, throwScope, "Invalid sameSite value. Must be 'strict', 'lax', or 'none'"_s);
}
}
// Create the cookie
auto cookie = Cookie::from(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned);
auto* globalObject = jsCast<JSDOMGlobalObject*>(lexicalGlobalObject);
return JSValue::encode(toJSNewlyCreated(lexicalGlobalObject, globalObject, WTFMove(cookie)));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieStaticFunctionSerialize, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 1)
return JSValue::encode(jsEmptyString(vm));
Vector<Ref<Cookie>> cookies;
// Process each cookie argument
for (unsigned i = 0; i < callFrame->argumentCount(); i++) {
auto* cookieImpl = toCookieWrapped(lexicalGlobalObject, throwScope, callFrame->uncheckedArgument(i));
RETURN_IF_EXCEPTION(throwScope, {});
if (cookieImpl)
cookies.append(*cookieImpl);
}
// Let the C++ Cookie::serialize handle the work
String result = Cookie::serialize(vm, cookies);
return JSValue::encode(jsString(vm, result));
}
// Property getters/setters
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_name, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "name"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLUSVString>(*lexicalGlobalObject, throwScope, impl.name()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_name, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "name"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLUSVString>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setName(value);
return true;
}
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_value, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "value"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLUSVString>(*lexicalGlobalObject, throwScope, impl.value()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_value, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "value"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLUSVString>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setValue(value);
return true;
}
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_domain, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "domain"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLNullable<IDLUSVString>>(*lexicalGlobalObject, throwScope, impl.domain()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_domain, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "domain"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLUSVString>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setDomain(value);
return true;
}
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_path, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "path"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLUSVString>(*lexicalGlobalObject, throwScope, impl.path()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_path, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "path"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLUSVString>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setPath(value);
return true;
}
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_expires, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "expires"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLNullable<IDLDouble>>(*lexicalGlobalObject, throwScope, impl.expires()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_expires, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "expires"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLDouble>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setExpires(value);
return true;
}
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_secure, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "secure"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLBoolean>(*lexicalGlobalObject, throwScope, impl.secure()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_secure, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "secure"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLBoolean>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setSecure(value);
return true;
}
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_sameSite, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "sameSite"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS(lexicalGlobalObject, impl.sameSite()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_sameSite, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "sameSite"_s);
auto& impl = thisObject->wrapped();
auto sameSiteStr = convert<IDLUSVString>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
CookieSameSite sameSite;
if (WTF::equalIgnoringASCIICase(sameSiteStr, "strict"_s))
sameSite = CookieSameSite::Strict;
else if (WTF::equalIgnoringASCIICase(sameSiteStr, "lax"_s))
sameSite = CookieSameSite::Lax;
else if (WTF::equalIgnoringASCIICase(sameSiteStr, "none"_s))
sameSite = CookieSameSite::None;
else {
throwTypeError(lexicalGlobalObject, throwScope, "Invalid sameSite value. Must be 'strict', 'lax', or 'none'"_s);
return false;
}
impl.setSameSite(sameSite);
return true;
}
// HttpOnly property
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_httpOnly, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "httpOnly"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLBoolean>(*lexicalGlobalObject, throwScope, impl.httpOnly()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_httpOnly, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "httpOnly"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLBoolean>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setHttpOnly(value);
return true;
}
// MaxAge property
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_maxAge, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "maxAge"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLNullable<IDLDouble>>(*lexicalGlobalObject, throwScope, impl.maxAge()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_maxAge, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "maxAge"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLDouble>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setMaxAge(value);
return true;
}
// Partitioned property
JSC_DEFINE_CUSTOM_GETTER(jsCookiePrototypeGetter_partitioned, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "partitioned"_s);
auto& impl = thisObject->wrapped();
return JSValue::encode(toJS<IDLBoolean>(*lexicalGlobalObject, throwScope, impl.partitioned()));
}
JSC_DEFINE_CUSTOM_SETTER(jsCookiePrototypeSetter_partitioned, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookie*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwThisTypeError(*lexicalGlobalObject, throwScope, "Cookie"_s, "partitioned"_s);
auto& impl = thisObject->wrapped();
auto value = convert<IDLBoolean>(*lexicalGlobalObject, JSValue::decode(encodedValue));
RETURN_IF_EXCEPTION(throwScope, false);
impl.setPartitioned(value);
return true;
}
// isExpired method
static inline JSC::EncodedJSValue jsCookiePrototypeFunction_isExpiredBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookie>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
bool expired = impl.isExpired();
return JSValue::encode(JSC::jsBoolean(expired));
}
JSC_DEFINE_HOST_FUNCTION(jsCookiePrototypeFunction_isExpired, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookie>::call<jsCookiePrototypeFunction_isExpiredBody>(*lexicalGlobalObject, *callFrame, "isExpired");
}
GCClient::IsoSubspace* JSCookie::subspaceForImpl(VM& vm)
{
return WebCore::subspaceForImpl<JSCookie, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForCookie.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCookie = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForCookie.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForCookie = std::forward<decltype(space)>(space); });
}
void JSCookie::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
{
auto* thisObject = jsCast<JSCookie*>(cell);
analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());
Base::analyzeHeap(cell, analyzer);
}
bool JSCookieOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, ASCIILiteral* reason)
{
UNUSED_PARAM(handle);
UNUSED_PARAM(visitor);
UNUSED_PARAM(reason);
return false;
}
void JSCookieOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
{
auto* jsCookie = static_cast<JSCookie*>(handle.slot()->asCell());
auto& world = *static_cast<DOMWrapperWorld*>(context);
uncacheWrapper(world, &jsCookie->wrapped(), jsCookie);
}
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<Cookie>&& impl)
{
return createWrapper<Cookie>(globalObject, WTFMove(impl));
}
JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Cookie& impl)
{
return wrap(lexicalGlobalObject, globalObject, impl);
}
Cookie* JSCookie::toWrapped(JSC::VM& vm, JSC::JSValue value)
{
if (auto* wrapper = jsDynamicCast<JSCookie*>(value))
return &wrapper->wrapped();
return nullptr;
}
size_t JSCookie::estimatedSize(JSC::JSCell* cell, JSC::VM& vm)
{
auto* thisObject = jsCast<JSCookie*>(cell);
auto& wrapped = thisObject->wrapped();
return Base::estimatedSize(cell, vm) + wrapped.memoryCost();
}
JSC::JSValue toJS(JSC::JSGlobalObject* globalObject, CookieSameSite sameSite)
{
auto& commonStrings = defaultGlobalObject(globalObject)->commonStrings();
switch (sameSite) {
case CookieSameSite::Strict:
return commonStrings.strictString(globalObject);
case CookieSameSite::Lax:
return commonStrings.laxString(globalObject);
case CookieSameSite::None:
return commonStrings.noneString(globalObject);
default: {
break;
}
}
__builtin_unreachable();
return {};
}
}

View File

@@ -0,0 +1,75 @@
#pragma once
#include "JSDOMWrapper.h"
#include "Cookie.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
class JSCookie : public JSDOMWrapper<Cookie> {
public:
using Base = JSDOMWrapper<Cookie>;
static JSCookie* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<Cookie>&& impl)
{
JSCookie* ptr = new (NotNull, JSC::allocateCell<JSCookie>(globalObject->vm())) JSCookie(structure, *globalObject, WTFMove(impl));
ptr->finishCreation(globalObject->vm());
return ptr;
}
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
static Cookie* toWrapped(JSC::VM&, JSC::JSValue);
static void destroy(JSC::JSCell*);
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(WebCore::JSAsJSONType), StructureFlags), info());
}
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
static size_t estimatedSize(JSC::JSCell* cell, JSC::VM& vm);
protected:
JSCookie(JSC::Structure*, JSDOMGlobalObject&, Ref<Cookie>&&);
void finishCreation(JSC::VM&);
};
class JSCookieOwner final : public JSC::WeakHandleOwner {
public:
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
};
inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, Cookie*)
{
static NeverDestroyed<JSCookieOwner> owner;
return &owner.get();
}
inline void* wrapperKey(Cookie* wrappableObject)
{
return wrappableObject;
}
JSC::JSValue getInternalProperties(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSCookie* castedThis);
JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, Cookie&);
inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Cookie* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); }
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<Cookie>&&);
inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<Cookie>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }
template<> struct JSDOMWrapperConverterTraits<Cookie> {
using WrapperClass = JSCookie;
using ToWrappedReturnType = Cookie*;
};
} // namespace WebCore

View File

@@ -0,0 +1,893 @@
#include "config.h"
#include "JSCookieMap.h"
#include "Cookie.h"
#include "DOMClientIsoSubspaces.h"
#include "DOMIsoSubspaces.h"
#include "IDLTypes.h"
#include "JSCookie.h"
#include "JSDOMBinding.h"
#include "JSDOMConstructor.h"
#include "JSDOMConvertBase.h"
#include "JSDOMConvertBoolean.h"
#include "JSDOMConvertDate.h"
#include "JSDOMConvertInterface.h"
#include "JSDOMConvertNullable.h"
#include "JSDOMConvertRecord.h"
#include "JSDOMConvertSequences.h"
#include "JSDOMConvertStrings.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMGlobalObject.h"
#include "JSDOMGlobalObjectInlines.h"
#include "JSDOMIterator.h"
#include "JSDOMOperation.h"
#include "JSDOMWrapperCache.h"
#include <JavaScriptCore/DateInstance.h>
#include <JavaScriptCore/HeapAnalyzer.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/SlotVisitorMacros.h>
#include <JavaScriptCore/SubspaceInlines.h>
#include <wtf/GetPtr.h>
#include <wtf/PointerPreparations.h>
#include <variant>
namespace WebCore {
using namespace JSC;
// Define the toWrapped template function for CookieMap
template<typename ExceptionThrower>
CookieMap* toWrapped(JSGlobalObject& lexicalGlobalObject, ExceptionThrower&& exceptionThrower, JSValue value)
{
auto& vm = getVM(&lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto* impl = JSCookieMap::toWrapped(vm, value);
if (UNLIKELY(!impl))
exceptionThrower(lexicalGlobalObject, scope);
return impl;
}
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_get);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_getAll);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_has);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_set);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_delete);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_toString);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_toJSON);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_entries);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_keys);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_values);
static JSC_DECLARE_HOST_FUNCTION(jsCookieMapPrototypeFunction_forEach);
static JSC_DECLARE_CUSTOM_GETTER(jsCookieMapPrototypeGetter_size);
static JSC_DECLARE_CUSTOM_GETTER(jsCookieMapConstructor);
class JSCookieMapPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSCookieMapPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
{
JSCookieMapPrototype* ptr = new (NotNull, JSC::allocateCell<JSCookieMapPrototype>(vm)) JSCookieMapPrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCookieMapPrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
private:
JSCookieMapPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
: JSC::JSNonFinalObject(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCookieMapPrototype, JSCookieMapPrototype::Base);
using JSCookieMapDOMConstructor = JSDOMConstructor<JSCookieMap>;
template<> JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSCookieMapDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* castedThis = jsCast<JSCookieMapDOMConstructor*>(callFrame->jsCallee());
// Check arguments
JSValue initValue = callFrame->argument(0);
std::variant<Vector<Vector<String>>, HashMap<String, String>, String> init;
if (initValue.isUndefinedOrNull() || (initValue.isString() && initValue.getString(lexicalGlobalObject).isEmpty())) {
init = String();
} else if (initValue.isString()) {
init = initValue.getString(lexicalGlobalObject);
} else if (initValue.isObject()) {
auto* object = initValue.getObject();
if (isArray(lexicalGlobalObject, object)) {
auto* array = jsCast<JSArray*>(object);
Vector<Vector<String>> seqSeq;
uint32_t length = array->length();
for (uint32_t i = 0; i < length; ++i) {
auto element = array->getIndex(lexicalGlobalObject, i);
RETURN_IF_EXCEPTION(throwScope, {});
if (!element.isObject() || !jsDynamicCast<JSArray*>(element)) {
throwTypeError(lexicalGlobalObject, throwScope, "Expected each element to be an array of two strings"_s);
return {};
}
auto* subArray = jsCast<JSArray*>(element);
if (subArray->length() != 2) {
throwTypeError(lexicalGlobalObject, throwScope, "Expected arrays of exactly two strings"_s);
return {};
}
auto first = subArray->getIndex(lexicalGlobalObject, 0);
RETURN_IF_EXCEPTION(throwScope, {});
auto second = subArray->getIndex(lexicalGlobalObject, 1);
RETURN_IF_EXCEPTION(throwScope, {});
auto firstStr = first.toString(lexicalGlobalObject)->value(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
auto secondStr = second.toString(lexicalGlobalObject)->value(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
Vector<String> pair;
pair.append(firstStr);
pair.append(secondStr);
seqSeq.append(WTFMove(pair));
}
init = WTFMove(seqSeq);
} else {
// Handle as record<USVString, USVString>
HashMap<String, String> record;
PropertyNameArray propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
JSObject::getOwnPropertyNames(object, lexicalGlobalObject, propertyNames, DontEnumPropertiesMode::Include);
RETURN_IF_EXCEPTION(throwScope, {});
for (const auto& propertyName : propertyNames) {
JSValue value = object->get(lexicalGlobalObject, propertyName);
RETURN_IF_EXCEPTION(throwScope, {});
auto valueStr = value.toString(lexicalGlobalObject)->value(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
record.set(propertyName.string(), valueStr);
}
init = WTFMove(record);
}
} else {
throwTypeError(lexicalGlobalObject, throwScope, "Invalid initializer type"_s);
return {};
}
auto result = CookieMap::create(WTFMove(init));
RETURN_IF_EXCEPTION(throwScope, {});
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJSNewlyCreated(lexicalGlobalObject, castedThis->globalObject(), result.releaseReturnValue())));
}
JSC_ANNOTATE_HOST_FUNCTION(JSCookieMapDOMConstructorConstruct, JSCookieMapDOMConstructor::construct);
template<> const ClassInfo JSCookieMapDOMConstructor::s_info = { "CookieMap"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCookieMapDOMConstructor) };
template<> JSValue JSCookieMapDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
{
return globalObject.objectPrototype();
}
template<> void JSCookieMapDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
{
putDirect(vm, vm.propertyNames->length, jsNumber(1), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
JSString* nameString = jsNontrivialString(vm, "CookieMap"_s);
m_originalName.set(vm, this, nameString);
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->prototype, JSCookieMap::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
}
static const HashTableValue JSCookieMapPrototypeTableValues[] = {
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookieMapConstructor, 0 } },
{ "get"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_get, 1 } },
{ "getAll"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_getAll, 1 } },
{ "has"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_has, 1 } },
{ "set"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_set, 1 } },
{ "delete"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_delete, 1 } },
{ "entries"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_entries, 0 } },
{ "keys"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_keys, 0 } },
{ "values"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_values, 0 } },
{ "forEach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_forEach, 1 } },
{ "toString"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_toString, 0 } },
{ "toJSON"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsCookieMapPrototypeFunction_toJSON, 0 } },
{ "size"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, jsCookieMapPrototypeGetter_size, 0 } },
};
const ClassInfo JSCookieMapPrototype::s_info = { "CookieMap"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCookieMapPrototype) };
void JSCookieMapPrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSCookieMap::info(), JSCookieMapPrototypeTableValues, *this);
putDirect(vm, vm.propertyNames->iteratorSymbol, getDirect(vm, PropertyName(Identifier::fromString(vm, "entries"_s))), static_cast<unsigned>(JSC::PropertyAttribute::DontEnum));
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
const ClassInfo JSCookieMap::s_info = { "CookieMap"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCookieMap) };
JSCookieMap::JSCookieMap(Structure* structure, JSDOMGlobalObject& globalObject, Ref<CookieMap>&& impl)
: JSDOMWrapper<CookieMap>(structure, globalObject, WTFMove(impl))
{
}
void JSCookieMap::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
JSObject* JSCookieMap::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
auto* structure = JSCookieMapPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
structure->setMayBePrototype(true);
return JSCookieMapPrototype::create(vm, &globalObject, structure);
}
JSObject* JSCookieMap::prototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return getDOMPrototype<JSCookieMap>(vm, globalObject);
}
JSValue JSCookieMap::getConstructor(VM& vm, const JSGlobalObject* globalObject)
{
return getDOMConstructor<JSCookieMapDOMConstructor, DOMConstructorID::CookieMap>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
}
void JSCookieMap::destroy(JSC::JSCell* cell)
{
JSCookieMap* thisObject = static_cast<JSCookieMap*>(cell);
thisObject->JSCookieMap::~JSCookieMap();
}
JSC_DEFINE_CUSTOM_GETTER(jsCookieMapConstructor, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* prototype = jsDynamicCast<JSCookieMapPrototype*>(JSValue::decode(thisValue));
if (UNLIKELY(!prototype))
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(JSCookieMap::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject()));
}
JSC_DEFINE_CUSTOM_GETTER(jsCookieMapPrototypeGetter_size, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSCookieMap*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(jsNumber(thisObject->wrapped().size()));
}
// Implementation of the get method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_getBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
if (callFrame->argumentCount() < 1)
return JSValue::encode(jsNull());
JSValue arg0 = callFrame->uncheckedArgument(0);
auto& names = builtinNames(vm);
if (arg0.isObject()) {
// Handle options object
auto* options = arg0.getObject();
// Extract name
auto nameValue = options->get(lexicalGlobalObject, PropertyName(vm.propertyNames->name));
RETURN_IF_EXCEPTION(throwScope, {});
if (!nameValue.isUndefined()) {
auto name = convert<IDLUSVString>(*lexicalGlobalObject, nameValue);
RETURN_IF_EXCEPTION(throwScope, {});
// Get cookie by name
auto cookie = impl.get(name);
if (!cookie)
return JSValue::encode(jsNull());
// Return as Cookie object
return JSValue::encode(toJS(lexicalGlobalObject, castedThis->globalObject(), *cookie));
}
// Extract url
auto urlValue = options->get(lexicalGlobalObject, names.urlPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!urlValue.isUndefined()) {
auto url = convert<IDLUSVString>(*lexicalGlobalObject, urlValue);
RETURN_IF_EXCEPTION(throwScope, {});
// Create options struct and get cookie by URL
CookieStoreGetOptions options;
options.url = url;
auto cookie = impl.get(options);
if (!cookie)
return JSValue::encode(jsNull());
// Return as Cookie object
return JSValue::encode(toJS(lexicalGlobalObject, castedThis->globalObject(), *cookie));
}
// If we got here, neither name nor url was provided
return JSValue::encode(jsNull());
} else {
// Handle single string argument (name)
auto name = convert<IDLUSVString>(*lexicalGlobalObject, arg0);
RETURN_IF_EXCEPTION(throwScope, {});
auto cookie = impl.get(name);
if (!cookie)
return JSValue::encode(jsNull());
// Return as Cookie object
return JSValue::encode(toJS(lexicalGlobalObject, castedThis->globalObject(), *cookie));
}
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_get, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_getBody>(*lexicalGlobalObject, *callFrame, "get");
}
// Implementation of the getAll method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_getAllBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
if (callFrame->argumentCount() < 1)
return JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr));
JSValue arg0 = callFrame->uncheckedArgument(0);
Vector<Ref<Cookie>> cookies;
auto& names = builtinNames(vm);
if (arg0.isObject()) {
// Handle options object
auto* options = arg0.getObject();
// Extract name
auto nameValue = options->get(lexicalGlobalObject, PropertyName(vm.propertyNames->name));
RETURN_IF_EXCEPTION(throwScope, {});
if (!nameValue.isUndefined()) {
auto name = convert<IDLUSVString>(*lexicalGlobalObject, nameValue);
RETURN_IF_EXCEPTION(throwScope, {});
// Get cookies by name
cookies = impl.getAll(name);
} else {
// Extract url
auto urlValue = options->get(lexicalGlobalObject, names.urlPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!urlValue.isUndefined()) {
auto url = convert<IDLUSVString>(*lexicalGlobalObject, urlValue);
RETURN_IF_EXCEPTION(throwScope, {});
// Create options struct and get cookies by URL
CookieStoreGetOptions options;
options.url = url;
cookies = impl.getAll(options);
}
}
} else {
// Handle single string argument (name)
auto name = convert<IDLUSVString>(*lexicalGlobalObject, arg0);
RETURN_IF_EXCEPTION(throwScope, {});
cookies = impl.getAll(name);
}
// Create array of Cookie objects
JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, cookies.size());
RETURN_IF_EXCEPTION(throwScope, {});
for (size_t i = 0; i < cookies.size(); ++i) {
resultArray->putDirectIndex(lexicalGlobalObject, i, toJS(lexicalGlobalObject, castedThis->globalObject(), cookies[i]));
RETURN_IF_EXCEPTION(throwScope, {});
}
return JSValue::encode(resultArray);
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_getAll, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_getAllBody>(*lexicalGlobalObject, *callFrame, "getAll");
}
// Implementation of the has method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_hasBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
if (callFrame->argumentCount() < 1)
return JSValue::encode(jsBoolean(false));
auto name = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(throwScope, {});
String value;
if (callFrame->argumentCount() > 1) {
value = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(1));
RETURN_IF_EXCEPTION(throwScope, {});
}
return JSValue::encode(jsBoolean(impl.has(name, value)));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_has, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_hasBody>(*lexicalGlobalObject, *callFrame, "has");
}
// Implementation of the set method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_setBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
if (callFrame->argumentCount() < 1)
return JSValue::encode(jsUndefined());
JSValue arg0 = callFrame->uncheckedArgument(0);
JSValue arg2 = callFrame->argument(2);
String name;
String value;
// Extract optional fields
String domain;
String path = "/"_s;
double expires = 0;
bool secure = false;
CookieSameSite sameSite = CookieSameSite::Lax;
// Create and set the cookie
// Extract httpOnly and partitioned
bool httpOnly = false;
bool partitioned = false;
double maxAge = 0;
auto& names = builtinNames(vm);
const auto fromObject = [&](JSObject* obj, bool checkNameAndValue = false) -> void {
if (checkNameAndValue) {
auto nameValue = obj->get(lexicalGlobalObject, PropertyName(vm.propertyNames->name));
RETURN_IF_EXCEPTION(throwScope, void());
if (nameValue.isUndefined() || nameValue.isNull()) {
throwVMError(lexicalGlobalObject, throwScope, createTypeError(lexicalGlobalObject, "Cookie name is required"_s));
return;
}
auto valueValue = obj->get(lexicalGlobalObject, vm.propertyNames->value);
RETURN_IF_EXCEPTION(throwScope, void());
if (valueValue.isUndefined() || valueValue.isNull()) {
throwVMError(lexicalGlobalObject, throwScope, createTypeError(lexicalGlobalObject, "Cookie value is required"_s));
return;
}
name = convert<IDLUSVString>(*lexicalGlobalObject, nameValue);
RETURN_IF_EXCEPTION(throwScope, void());
value = convert<IDLUSVString>(*lexicalGlobalObject, valueValue);
RETURN_IF_EXCEPTION(throwScope, void());
}
// domain
auto domainValue = obj->get(lexicalGlobalObject, names.domainPublicName());
RETURN_IF_EXCEPTION(throwScope, void());
if (!domainValue.isUndefined() && !domainValue.isNull()) {
domain = convert<IDLUSVString>(*lexicalGlobalObject, domainValue);
RETURN_IF_EXCEPTION(throwScope, void());
}
// path
auto pathValue = obj->get(lexicalGlobalObject, names.pathPublicName());
RETURN_IF_EXCEPTION(throwScope, void());
if (!pathValue.isUndefined() && !pathValue.isNull()) {
path = convert<IDLUSVString>(*lexicalGlobalObject, pathValue);
RETURN_IF_EXCEPTION(throwScope, void());
}
// expires
auto expiresValue = obj->get(lexicalGlobalObject, names.expiresPublicName());
RETURN_IF_EXCEPTION(throwScope, void());
if (!expiresValue.isUndefined() && !expiresValue.isNull()) {
// Handle Date object
if (expiresValue.inherits<JSC::DateInstance>()) {
auto* dateInstance = jsCast<JSC::DateInstance*>(expiresValue.asCell());
expires = dateInstance->internalNumber();
RETURN_IF_EXCEPTION(throwScope, void());
} else if (expiresValue.isNumber()) {
expires = expiresValue.asNumber();
RETURN_IF_EXCEPTION(throwScope, void());
} else if (expiresValue.isString()) {
auto expiresStr = convert<IDLUSVString>(*lexicalGlobalObject, expiresValue);
RETURN_IF_EXCEPTION(throwScope, void());
if (auto parsed = WTF::parseDate(expiresStr.span<LChar>())) {
expires = parsed;
RETURN_IF_EXCEPTION(throwScope, void());
} else {
throwVMError(lexicalGlobalObject, throwScope, createTypeError(lexicalGlobalObject, "Invalid cookie expiration date"_s));
return;
}
} else {
throwVMError(lexicalGlobalObject, throwScope, createTypeError(lexicalGlobalObject, "Invalid cookie expiration date"_s));
return;
}
}
// secure
auto secureValue = obj->get(lexicalGlobalObject, names.securePublicName());
RETURN_IF_EXCEPTION(throwScope, void());
if (!secureValue.isUndefined()) {
secure = secureValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, void());
}
// sameSite
auto sameSiteValue = obj->get(lexicalGlobalObject, names.sameSitePublicName());
RETURN_IF_EXCEPTION(throwScope, void());
if (!sameSiteValue.isUndefined() && !sameSiteValue.isNull()) {
String sameSiteStr = convert<IDLUSVString>(*lexicalGlobalObject, sameSiteValue);
RETURN_IF_EXCEPTION(throwScope, void());
if (sameSiteStr == "strict"_s)
sameSite = CookieSameSite::Strict;
else if (sameSiteStr == "lax"_s)
sameSite = CookieSameSite::Lax;
else if (sameSiteStr == "none"_s)
sameSite = CookieSameSite::None;
else
throwVMTypeError(lexicalGlobalObject, throwScope, "Invalid sameSite value. Must be 'strict', 'lax', or 'none'"_s);
return;
}
auto httpOnlyValue
= obj->get(lexicalGlobalObject, PropertyName(names.httpOnlyPublicName()));
RETURN_IF_EXCEPTION(throwScope, void());
if (!httpOnlyValue.isUndefined()) {
httpOnly = httpOnlyValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, void());
}
auto partitionedValue = obj->get(lexicalGlobalObject, PropertyName(names.partitionedPublicName()));
RETURN_IF_EXCEPTION(throwScope, void());
if (!partitionedValue.isUndefined()) {
partitioned = partitionedValue.toBoolean(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, void());
}
auto maxAgeValue = obj->get(lexicalGlobalObject, PropertyName(names.maxAgePublicName()));
RETURN_IF_EXCEPTION(throwScope, void());
if (!maxAgeValue.isUndefined() && !maxAgeValue.isNull() && maxAgeValue.isNumber()) {
maxAge = maxAgeValue.asNumber();
RETURN_IF_EXCEPTION(throwScope, void());
}
};
// Check if we're setting with a Cookie object directly
if (arg0.isObject() && JSCookie::toWrapped(vm, arg0)) {
auto* cookieImpl = JSCookie::toWrapped(vm, arg0);
if (cookieImpl)
impl.set(Ref<Cookie>(*cookieImpl));
return JSValue::encode(jsUndefined());
} else if (arg0.isObject()) {
auto* obj = arg0.getObject();
fromObject(obj, true);
} else {
// Handle name/value pair
if (callFrame->argumentCount() < 2)
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
name = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(throwScope, {});
value = convert<IDLUSVString>(*lexicalGlobalObject, callFrame->uncheckedArgument(1));
RETURN_IF_EXCEPTION(throwScope, {});
// Check for optional third parameter (options)
if (callFrame->argumentCount() >= 3) {
JSValue optionsArg = arg2;
if (!optionsArg.isObject())
return JSValue::encode(jsUndefined());
auto* options = optionsArg.getObject();
fromObject(options, false);
}
}
auto cookie = Cookie::create(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned);
impl.set(WTFMove(cookie));
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_set, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_setBody>(*lexicalGlobalObject, *callFrame, "set");
}
// Implementation of the delete method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_deleteBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
if (callFrame->argumentCount() < 1)
return JSValue::encode(jsUndefined());
JSValue arg0 = callFrame->uncheckedArgument(0);
auto& names = builtinNames(vm);
if (arg0.isObject()) {
// Handle as options object (CookieStoreDeleteOptions)
auto* options = arg0.getObject();
// Extract required name
auto nameValue = options->get(lexicalGlobalObject, PropertyName(vm.propertyNames->name));
RETURN_IF_EXCEPTION(throwScope, {});
if (nameValue.isUndefined() || nameValue.isNull())
return throwVMError(lexicalGlobalObject, throwScope, createTypeError(lexicalGlobalObject, "Cookie name is required"_s));
auto name = convert<IDLUSVString>(*lexicalGlobalObject, nameValue);
RETURN_IF_EXCEPTION(throwScope, {});
CookieStoreDeleteOptions deleteOptions;
deleteOptions.name = name;
// Extract optional domain
auto domainValue = options->get(lexicalGlobalObject, names.domainPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!domainValue.isUndefined() && !domainValue.isNull()) {
deleteOptions.domain = convert<IDLUSVString>(*lexicalGlobalObject, domainValue);
RETURN_IF_EXCEPTION(throwScope, {});
}
// Extract optional path
auto pathValue = options->get(lexicalGlobalObject, names.pathPublicName());
RETURN_IF_EXCEPTION(throwScope, {});
if (!pathValue.isUndefined() && !pathValue.isNull()) {
deleteOptions.path = convert<IDLUSVString>(*lexicalGlobalObject, pathValue);
RETURN_IF_EXCEPTION(throwScope, {});
} else {
deleteOptions.path = "/"_s;
}
impl.remove(deleteOptions);
} else {
// Handle single string argument (name)
auto name = convert<IDLUSVString>(*lexicalGlobalObject, arg0);
RETURN_IF_EXCEPTION(throwScope, {});
impl.remove(name);
}
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_delete, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_deleteBody>(*lexicalGlobalObject, *callFrame, "delete");
}
// Implementation of the toString method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLDOMString>(*lexicalGlobalObject, throwScope, impl.toString(vm))));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_toString, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_toStringBody>(*lexicalGlobalObject, *callFrame, "toString");
}
// Implementation of the toJSON method
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_toJSONBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSCookieMap>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto& impl = castedThis->wrapped();
// Delegate to the C++ toJSON method
JSC::JSValue result = impl.toJSON(lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
return JSValue::encode(result);
}
JSC::JSValue getInternalProperties(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSCookieMap* castedThis)
{
return castedThis->wrapped().toJSON(lexicalGlobalObject);
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_toJSON, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_toJSONBody>(*lexicalGlobalObject, *callFrame, "toJSON");
}
// Iterator implementation for CookieMap
struct CookieMapIteratorTraits {
static constexpr JSDOMIteratorType type = JSDOMIteratorType::Map;
using KeyType = IDLUSVString;
using ValueType = IDLInterface<Cookie>;
};
using CookieMapIteratorBase = JSDOMIteratorBase<JSCookieMap, CookieMapIteratorTraits>;
class CookieMapIterator final : public CookieMapIteratorBase {
public:
using Base = CookieMapIteratorBase;
DECLARE_INFO;
template<typename, SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<CookieMapIterator, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForCookieMapIterator.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCookieMapIterator = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForCookieMapIterator.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForCookieMapIterator = std::forward<decltype(space)>(space); });
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
static CookieMapIterator* create(JSC::VM& vm, JSC::Structure* structure, JSCookieMap& iteratedObject, IterationKind kind)
{
auto* instance = new (NotNull, JSC::allocateCell<CookieMapIterator>(vm)) CookieMapIterator(structure, iteratedObject, kind);
instance->finishCreation(vm);
return instance;
}
private:
CookieMapIterator(JSC::Structure* structure, JSCookieMap& iteratedObject, IterationKind kind)
: Base(structure, iteratedObject, kind)
{
}
};
using CookieMapIteratorPrototype = JSDOMIteratorPrototype<JSCookieMap, CookieMapIteratorTraits>;
JSC_ANNOTATE_HOST_FUNCTION(CookieMapIteratorPrototypeNext, CookieMapIteratorPrototype::next);
template<>
const JSC::ClassInfo CookieMapIteratorBase::s_info = { "CookieMap Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CookieMapIteratorBase) };
const JSC::ClassInfo CookieMapIterator::s_info = { "CookieMap Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CookieMapIterator) };
template<>
const JSC::ClassInfo CookieMapIteratorPrototype::s_info = { "CookieMap Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(CookieMapIteratorPrototype) };
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_entriesCaller(JSGlobalObject*, CallFrame*, JSCookieMap* thisObject)
{
return JSValue::encode(iteratorCreate<CookieMapIterator>(*thisObject, IterationKind::Entries));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_entries, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_entriesCaller>(*lexicalGlobalObject, *callFrame, "entries");
}
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_keysCaller(JSGlobalObject*, CallFrame*, JSCookieMap* thisObject)
{
return JSValue::encode(iteratorCreate<CookieMapIterator>(*thisObject, IterationKind::Keys));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_keys, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_keysCaller>(*lexicalGlobalObject, *callFrame, "keys");
}
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_valuesCaller(JSGlobalObject*, CallFrame*, JSCookieMap* thisObject)
{
return JSValue::encode(iteratorCreate<CookieMapIterator>(*thisObject, IterationKind::Values));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_values, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_valuesCaller>(*lexicalGlobalObject, *callFrame, "values");
}
static inline JSC::EncodedJSValue jsCookieMapPrototypeFunction_forEachCaller(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame, JSCookieMap* thisObject)
{
return JSValue::encode(iteratorForEach<CookieMapIterator>(*lexicalGlobalObject, *callFrame, *thisObject));
}
JSC_DEFINE_HOST_FUNCTION(jsCookieMapPrototypeFunction_forEach, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
return IDLOperation<JSCookieMap>::call<jsCookieMapPrototypeFunction_forEachCaller>(*lexicalGlobalObject, *callFrame, "forEach");
}
GCClient::IsoSubspace* JSCookieMap::subspaceForImpl(VM& vm)
{
return WebCore::subspaceForImpl<JSCookieMap, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForCookieMap.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCookieMap = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForCookieMap.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForCookieMap = std::forward<decltype(space)>(space); });
}
void JSCookieMap::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
{
auto* thisObject = jsCast<JSCookieMap*>(cell);
analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped());
Base::analyzeHeap(cell, analyzer);
}
bool JSCookieMapOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, ASCIILiteral* reason)
{
UNUSED_PARAM(handle);
UNUSED_PARAM(visitor);
UNUSED_PARAM(reason);
return false;
}
void JSCookieMapOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context)
{
auto* jsCookieMap = static_cast<JSCookieMap*>(handle.slot()->asCell());
auto& world = *static_cast<DOMWrapperWorld*>(context);
uncacheWrapper(world, &jsCookieMap->wrapped(), jsCookieMap);
}
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<CookieMap>&& impl)
{
return createWrapper<CookieMap>(globalObject, WTFMove(impl));
}
JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, CookieMap& impl)
{
return wrap(lexicalGlobalObject, globalObject, impl);
}
CookieMap* JSCookieMap::toWrapped(JSC::VM& vm, JSC::JSValue value)
{
if (auto* wrapper = jsDynamicCast<JSCookieMap*>(value))
return &wrapper->wrapped();
return nullptr;
}
size_t JSCookieMap::estimatedSize(JSC::JSCell* cell, JSC::VM& vm)
{
auto* thisObject = jsCast<JSCookieMap*>(cell);
auto& wrapped = thisObject->wrapped();
return Base::estimatedSize(cell, vm) + wrapped.memoryCost();
}
} // namespace WebCore

View File

@@ -0,0 +1,75 @@
#pragma once
#include "JSDOMWrapper.h"
#include "CookieMap.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
class JSCookieMap : public JSDOMWrapper<CookieMap> {
public:
using Base = JSDOMWrapper<CookieMap>;
static JSCookieMap* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<CookieMap>&& impl)
{
JSCookieMap* ptr = new (NotNull, JSC::allocateCell<JSCookieMap>(globalObject->vm())) JSCookieMap(structure, *globalObject, WTFMove(impl));
ptr->finishCreation(globalObject->vm());
return ptr;
}
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
static CookieMap* toWrapped(JSC::VM&, JSC::JSValue);
static void destroy(JSC::JSCell*);
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(WebCore::JSAsJSONType), StructureFlags), info());
}
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
static size_t estimatedSize(JSC::JSCell* cell, JSC::VM& vm);
protected:
JSCookieMap(JSC::Structure*, JSDOMGlobalObject&, Ref<CookieMap>&&);
void finishCreation(JSC::VM&);
};
JSC::JSValue getInternalProperties(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSCookieMap* castedThis);
class JSCookieMapOwner final : public JSC::WeakHandleOwner {
public:
bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, ASCIILiteral*) final;
void finalize(JSC::Handle<JSC::Unknown>, void* context) final;
};
inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, CookieMap*)
{
static NeverDestroyed<JSCookieMapOwner> owner;
return &owner.get();
}
inline void* wrapperKey(CookieMap* wrappableObject)
{
return wrappableObject;
}
JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, CookieMap&);
inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, CookieMap* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); }
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<CookieMap>&&);
inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<CookieMap>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); }
template<> struct JSDOMWrapperConverterTraits<CookieMap> {
using WrapperClass = JSCookieMap;
using ToWrappedReturnType = CookieMap*;
};
} // namespace WebCore

View File

@@ -447,7 +447,7 @@ static inline EncodedJSValue jsPerformanceResourceTimingPrototypeFunction_toJSON
auto* result = constructEmptyObject(lexicalGlobalObject);
auto nameValue = toJS<IDLDOMString>(*lexicalGlobalObject, throwScope, impl.name());
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, Identifier::fromString(vm, "name"_s), nameValue);
result->putDirect(vm, vm.propertyNames->name, nameValue);
auto entryTypeValue = toJS<IDLDOMString>(*lexicalGlobalObject, throwScope, impl.entryType());
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, Identifier::fromString(vm, "entryType"_s), entryTypeValue);

View File

@@ -220,7 +220,7 @@ static inline EncodedJSValue jsPerformanceServerTimingPrototypeFunction_toJSONBo
auto* result = constructEmptyObject(lexicalGlobalObject);
auto nameValue = toJS<IDLDOMString>(*lexicalGlobalObject, throwScope, impl.name());
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, Identifier::fromString(vm, "name"_s), nameValue);
result->putDirect(vm, vm.propertyNames->name, nameValue);
auto durationValue = toJS<IDLDouble>(*lexicalGlobalObject, throwScope, impl.duration());
RETURN_IF_EXCEPTION(throwScope, {});
result->putDirect(vm, Identifier::fromString(vm, "duration"_s), durationValue);

View File

@@ -97,6 +97,7 @@ using namespace JSC;
macro(dirname) \
macro(disturbed) \
macro(document) \
macro(domain) \
macro(encode) \
macro(encoding) \
macro(end) \
@@ -105,6 +106,7 @@ using namespace JSC;
macro(evaluateCommonJSModule) \
macro(evaluated) \
macro(execArgv) \
macro(expires) \
macro(exports) \
macro(extname) \
macro(failureKind) \
@@ -130,6 +132,7 @@ using namespace JSC;
macro(host) \
macro(hostname) \
macro(href) \
macro(httpOnly) \
macro(ignoreBOM) \
macro(importer) \
macro(inFlightCloseRequest) \
@@ -157,6 +160,7 @@ using namespace JSC;
macro(makeDOMException) \
macro(makeErrorWithCode) \
macro(makeGetterTypeError) \
macro(maxAge) \
macro(method) \
macro(mockedFunction) \
macro(mode) \
@@ -174,6 +178,7 @@ using namespace JSC;
macro(overridableRequire) \
macro(ownerReadableStream) \
macro(parse) \
macro(partitioned) \
macro(password) \
macro(patch) \
macro(path) \
@@ -217,6 +222,8 @@ using namespace JSC;
macro(requireNativeModule) \
macro(resolveSync) \
macro(resume) \
macro(sameSite) \
macro(secure) \
macro(self) \
macro(sep) \
macro(setBody) \

View File

@@ -0,0 +1,248 @@
import { test, expect, describe } from "bun:test";
describe("Bun.Cookie and Bun.CookieMap", () => {
// Basic Cookie tests
test("can create a basic Cookie", () => {
const cookie = new Bun.Cookie("name", "value");
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.path).toBe("/");
expect(cookie.domain).toBe(null);
expect(cookie.secure).toBe(false);
expect(cookie.httpOnly).toBe(false);
expect(cookie.partitioned).toBe(false);
expect(cookie.sameSite).toBe("lax");
});
test("can create a Cookie with options", () => {
const cookie = new Bun.Cookie("name", "value", {
domain: "example.com",
path: "/foo",
secure: true,
httpOnly: true,
partitioned: true,
sameSite: "lax",
maxAge: 3600,
});
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.domain).toBe("example.com");
expect(cookie.path).toBe("/foo");
expect(cookie.secure).toBe(true);
expect(cookie.httpOnly).toBe(true);
expect(cookie.partitioned).toBe(true);
expect(cookie.sameSite).toBe("lax");
expect(cookie.maxAge).toBe(3600);
});
test("Cookie.toString() formats properly", () => {
const cookie = new Bun.Cookie("name", "value", {
domain: "example.com",
path: "/foo",
secure: true,
httpOnly: true,
partitioned: true,
sameSite: "strict",
maxAge: 3600,
});
const str = cookie.toString();
expect(str).toInclude("name=value");
expect(str).toInclude("Domain=example.com");
expect(str).toInclude("Path=/foo");
expect(str).toInclude("Max-Age=3600");
expect(str).toInclude("Secure");
expect(str).toInclude("HttpOnly");
expect(str).toInclude("Partitioned");
expect(str).toInclude("SameSite=strict");
});
test("can set Cookie expires as Date", () => {
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 1); // tomorrow
const cookie = new Bun.Cookie("name", "value", {
expires: futureDate,
});
expect(cookie.isExpired()).toBe(false);
});
test("Cookie.isExpired() returns correct value", async () => {
// Expired cookie (max-age in the past)
const expiredCookie = new Bun.Cookie("name", "value", {
expires: new Date(Date.now() - 1000),
});
expect(expiredCookie.isExpired()).toBe(true);
// Non-expired cookie (future max-age)
const validCookie = new Bun.Cookie("name", "value", {
maxAge: 3600, // 1 hour
});
expect(validCookie.isExpired()).toBe(false);
// Session cookie (no expiration)
const sessionCookie = new Bun.Cookie("name", "value");
expect(sessionCookie.isExpired()).toBe(false);
});
test("Cookie.parse works with all attributes", () => {
const cookieStr =
"name=value; Domain=example.com; Expires=Thu, 13 Mar 2025 12:00:00 GMT; Path=/foo; Max-Age=3600; Secure; HttpOnly; Partitioned; SameSite=Strict";
const cookie = Bun.Cookie.parse(cookieStr);
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.domain).toBe("example.com");
expect(cookie.path).toBe("/foo");
expect(cookie.maxAge).toBe(3600);
expect(cookie.secure).toBe(true);
expect(cookie.httpOnly).toBe(true);
expect(cookie.expires).toEqual(Date.parse("Thu, 13 Mar 2025 12:00:00 GMT"));
expect(cookie.partitioned).toBe(true);
expect(cookie.sameSite).toBe("strict");
});
test("Cookie.serialize creates cookie string", () => {
const cookie1 = new Bun.Cookie("foo", "bar");
const cookie2 = new Bun.Cookie("baz", "qux");
const cookieStr = Bun.Cookie.serialize(cookie1, cookie2);
expect(cookieStr).toMatchInlineSnapshot(`"foo=bar; baz=qux"`);
});
// Basic CookieMap tests
test("can create an empty CookieMap", () => {
const map = new Bun.CookieMap();
expect(map.size).toBe(0);
expect(map.toString()).toBe("");
});
test("can create CookieMap from string", () => {
const map = new Bun.CookieMap("name=value; foo=bar");
expect(map.size).toBe(2);
const cookie1 = map.get("name");
expect(cookie1).toBeDefined();
expect(cookie1?.name).toBe("name");
expect(cookie1?.value).toBe("value");
const cookie2 = map.get("foo");
expect(cookie2).toBeDefined();
expect(cookie2?.name).toBe("foo");
expect(cookie2?.value).toBe("bar");
expect(map.toString()).toMatchInlineSnapshot(`"name=value; foo=bar"`);
});
test("can create CookieMap from object", () => {
const map = new Bun.CookieMap({
name: "value",
foo: "bar",
});
expect(map.size).toBe(2);
expect(map.get("name")?.value).toBe("value");
expect(map.get("foo")?.value).toBe("bar");
});
test("can create CookieMap from array pairs", () => {
const map = new Bun.CookieMap([
["name", "value"],
["foo", "bar"],
]);
expect(map.size).toBe(2);
expect(map.get("name")?.value).toBe("value");
expect(map.get("foo")?.value).toBe("bar");
});
test("CookieMap methods work", () => {
const map = new Bun.CookieMap();
// Set a cookie with name/value
map.set("name", "value");
expect(map.size).toBe(1);
expect(map.has("name")).toBe(true);
// Set with cookie object
map.set(
new Bun.Cookie("foo", "bar", {
secure: true,
httpOnly: true,
partitioned: true,
}),
);
expect(map.size).toBe(2);
expect(map.has("foo")).toBe(true);
expect(map.get("foo")?.secure).toBe(true);
expect(map.get("foo")?.httpOnly).toBe(true);
expect(map.get("foo")?.partitioned).toBe(true);
expect(map.toString()).toMatchInlineSnapshot(`"name=value; foo=bar; Secure; HttpOnly; Partitioned"`);
// Delete a cookie
map.delete("name");
expect(map.size).toBe(1);
expect(map.has("name")).toBe(false);
// Get all (only one remains)
const all = map.getAll("foo");
expect(all.length).toBe(1);
expect(all[0].value).toBe("bar");
});
test("CookieMap supports iteration", () => {
const map = new Bun.CookieMap("a=1; b=2; c=3");
// Test keys()
const keys = Array.from(map.keys());
expect(keys).toEqual(["a", "b", "c"]);
// Test entries()
let count = 0;
for (const [key, cookie] of map.entries()) {
count++;
expect(typeof key).toBe("string");
expect(typeof cookie).toBe("object");
expect(cookie instanceof Bun.Cookie).toBe(true);
expect(["1", "2", "3"]).toContain(cookie.value);
}
expect(count).toBe(3);
// Test forEach
const collected: string[] = [];
map.forEach((cookie, key) => {
collected.push(`${key}=${cookie.value}`);
});
expect(collected.sort()).toEqual(["a=1", "b=2", "c=3"]);
});
test("CookieMap.toString() formats properly", () => {
const map = new Bun.CookieMap("a=1; b=2");
const str = map.toString();
expect(str).toInclude("a=1");
expect(str).toInclude("b=2");
});
test("CookieMap works with cookies with advanced attributes", () => {
const map = new Bun.CookieMap();
// Add a cookie with httpOnly and partitioned flags
map.set("session", "abc123", {
httpOnly: true,
secure: true,
partitioned: true,
maxAge: 3600,
});
const cookie = map.get("session");
expect(cookie).toBeDefined();
expect(cookie?.httpOnly).toBe(true);
expect(cookie?.secure).toBe(true);
expect(cookie?.partitioned).toBe(true);
expect(cookie?.maxAge).toBe(3600);
expect(cookie?.value).toBe("abc123");
});
});

View File

@@ -0,0 +1,653 @@
import { afterAll, beforeAll, describe, expect, it, test } from "bun:test";
import { isBroken, isMacOS } from "harness";
import type { Server, ServeOptions, BunRequest } from "bun";
describe("request cookies", () => {
let server: Server;
beforeAll(() => {
server = Bun.serve({
port: 0,
routes: {
"/before-headers": req => {
// Access cookies before accessing headers
const cookies = req.cookies;
expect(cookies).toBeDefined();
expect(cookies.size).toBe(2);
expect(cookies.get("name")?.value).toBe("value");
expect(cookies.get("foo")?.value).toBe("bar");
// Verify headers are still accessible afterward
expect(req.headers.get("cookie")).toBe("name=value; foo=bar");
return new Response("ok");
},
"/after-headers": req => {
// Access headers first
const cookieHeader = req.headers.get("cookie");
expect(cookieHeader).toBe("name=value; foo=bar");
// Then access cookies
const cookies = req.cookies;
expect(cookies).toBeDefined();
expect(cookies.size).toBe(2);
expect(cookies.get("name")?.value).toBe("value");
expect(cookies.get("foo")?.value).toBe("bar");
return new Response("ok");
},
"/no-cookies": req => {
// Test with no cookies in request
const cookies = req.cookies;
expect(cookies).toBeDefined();
expect(cookies.size).toBe(0);
return new Response("ok");
},
"/cookies-readonly": req => {
// Verify cookies property is readonly
try {
// @ts-expect-error - This should fail at runtime
req.cookies = {};
return new Response("not ok - should have thrown");
} catch (e) {
return new Response("ok - readonly");
}
},
},
});
server.unref();
});
afterAll(() => {
server.stop(true);
});
it("parses cookies before headers are accessed", async () => {
const res = await fetch(`${server.url}before-headers`, {
headers: {
"Cookie": "name=value; foo=bar",
},
});
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});
it("parses cookies after headers are accessed", async () => {
const res = await fetch(`${server.url}after-headers`, {
headers: {
"Cookie": "name=value; foo=bar",
},
});
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});
it("handles requests with no cookies", async () => {
const res = await fetch(`${server.url}no-cookies`);
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok");
});
it("has readonly cookies property", async () => {
const res = await fetch(`${server.url}cookies-readonly`);
expect(res.status).toBe(200);
expect(await res.text()).toBe("ok - readonly");
});
});
describe("cookie attributes", () => {
let server: Server;
beforeAll(() => {
server = Bun.serve({
port: 0,
routes: {
"/cookie-attributes": req => {
const cookie = req.cookies.get("complex");
expect(cookie).toBeDefined();
if (!cookie) {
return new Response("no cookie found", { status: 500 });
}
return new Response(
JSON.stringify({
name: cookie.name,
value: cookie.value,
path: cookie.path,
secure: cookie.secure,
sameSite: cookie.sameSite,
}),
);
},
},
});
server.unref();
});
afterAll(() => {
server.stop(true);
});
it("correctly parses cookie attributes", async () => {
const res = await fetch(`${server.url}cookie-attributes`, {
headers: {
"Cookie": "complex=value; simple=123",
},
});
expect(res.status).toBe(200);
const data = await res.json();
expect(data.name).toBe("complex");
expect(data.value).toBe("value");
expect(data.path).toBe("/");
expect(data.secure).toBe(false);
expect(data.sameSite).toBe("lax");
});
});
describe("instanceof and type checks", () => {
let server: Server;
beforeAll(() => {
server = Bun.serve({
port: 0,
routes: {
"/instanceof-checks": req => {
// Check that cookies is an instance of Bun.CookieMap
expect(req.cookies instanceof Bun.CookieMap).toBe(true);
// Check that cookie is an instance of Bun.Cookie
const cookie = req.cookies.get("name");
expect(cookie instanceof Bun.Cookie).toBe(true);
return new Response("ok");
},
"/constructor-identities": req => {
// Verify that the constructors match
expect(req.cookies.constructor).toBe(Bun.CookieMap);
const cookie = req.cookies.get("name");
expect(cookie?.constructor).toBe(Bun.Cookie);
return new Response("ok");
},
},
});
server.unref();
});
afterAll(() => {
server.stop(true);
});
it("cookies is instance of Bun.CookieMap and has right prototype", async () => {
const res = await fetch(`${server.url}instanceof-checks`, {
headers: {
"Cookie": "name=value",
},
});
expect(res.status).toBe(200);
});
it("constructors match expected types", async () => {
const res = await fetch(`${server.url}constructor-identities`, {
headers: {
"Cookie": "name=value",
},
});
expect(res.status).toBe(200);
});
});
describe("complex cookie parsing", () => {
let server: Server;
beforeAll(() => {
server = Bun.serve({
port: 0,
routes: {
"/special-chars": req => {
const cookie = req.cookies.get("complex");
if (!cookie) {
return new Response("no cookie found", { status: 500 });
}
expect(cookie.value).toBe("value with spaces");
return new Response("ok");
},
"/equals-in-value": req => {
const cookie = req.cookies.get("equation");
if (!cookie) {
return new Response("no cookie found", { status: 500 });
}
expect(cookie.value).toBe("x=y+z");
return new Response("ok");
},
"/multiple-cookies": req => {
// Cookie with same name multiple times should be parsed correctly
const cookies = req.cookies;
expect(cookies.size).toBeGreaterThanOrEqual(2);
// Get first occurrence of duplicate cookie
const duplicateCookie = cookies.get("duplicate");
expect(duplicateCookie).toBeDefined();
// In most implementations, the first value should be preserved
expect(duplicateCookie?.value).toBe("first");
return new Response("ok");
},
"/cookie-map-methods": req => {
const cookies = req.cookies;
// Test has() method
expect(cookies.has("name")).toBe(true);
expect(cookies.has("nonexistent")).toBe(false);
// Test size
expect(cookies.size).toBe(2);
// Test toString() returns a valid cookie string
const cookieStr = cookies.toString();
expect(cookieStr).toInclude("name=value");
expect(cookieStr).toInclude("foo=bar");
return new Response("ok");
},
},
});
server.unref();
});
afterAll(() => {
server.stop(true);
});
it("handles cookie values with spaces", async () => {
const res = await fetch(`${server.url}special-chars`, {
headers: {
"Cookie": "complex=value with spaces",
},
});
expect(res.status).toBe(200);
});
it("handles cookie values with equals signs", async () => {
const res = await fetch(`${server.url}equals-in-value`, {
headers: {
"Cookie": "equation=x=y+z",
},
});
expect(res.status).toBe(200);
});
it("handles duplicate cookie names", async () => {
const res = await fetch(`${server.url}multiple-cookies`, {
headers: {
"Cookie": "duplicate=first; duplicate=second; other=value",
},
});
expect(res.status).toBe(200);
});
it("CookieMap methods work correctly", async () => {
const res = await fetch(`${server.url}cookie-map-methods`, {
headers: {
"Cookie": "name=value; foo=bar",
},
});
expect(res.status).toBe(200);
});
});
describe("CookieMap iterator", () => {
let server: Server;
beforeAll(() => {
server = Bun.serve({
port: 0,
routes: {
"/iterator-entries": req => {
const cookies = req.cookies;
// Test entries() iterator
const entries = Array.from(cookies.entries());
expect(entries.length).toBe(3);
// Entries should be [name, Cookie] pairs
expect(entries[0][0]).toBeTypeOf("string");
expect(entries[0][1]).toBeTypeOf("object");
expect(entries[0][1].constructor).toBe(Bun.Cookie);
// Check that we can get cookies values
const cookieNames = entries.map(([name, _]) => name);
expect(cookieNames).toContain("a");
expect(cookieNames).toContain("b");
expect(cookieNames).toContain("c");
const cookieValues = entries.map(([_, cookie]) => cookie.value);
expect(cookieValues).toContain("1");
expect(cookieValues).toContain("2");
expect(cookieValues).toContain("3");
return new Response("ok");
},
"/iterator-for-of": req => {
const cookies = req.cookies;
// Test for...of iteration (should iterate over entries)
const collected: { name: string; value: string }[] = [];
for (const entry of cookies) {
// Check that we get [name, cookie] entries
expect(entry.length).toBe(2);
expect(entry[0]).toBeTypeOf("string");
expect(entry[1]).toBeTypeOf("object");
expect(entry[1].constructor).toBe(Bun.Cookie);
const [name, cookie] = entry;
collected.push({ name, value: cookie.value });
}
expect(collected.length).toBe(3);
expect(collected.some(c => c.name === "a" && c.value === "1")).toBe(true);
expect(collected.some(c => c.name === "b" && c.value === "2")).toBe(true);
expect(collected.some(c => c.name === "c" && c.value === "3")).toBe(true);
return new Response("ok");
},
"/iterator-keys-values": req => {
const cookies = req.cookies;
// Test keys() iterator
const keys = Array.from(cookies.keys());
expect(keys.length).toBe(3);
expect(keys).toContain("a");
expect(keys).toContain("b");
expect(keys).toContain("c");
// Test values() iterator - returns Cookie objects
const values = Array.from(cookies.values());
expect(values.length).toBe(3);
// Values should be Cookie objects
for (const cookie of values) {
expect(cookie).toBeTypeOf("object");
expect(cookie.constructor).toBe(Bun.Cookie);
expect(cookie.name).toBeTypeOf("string");
expect(cookie.value).toBeTypeOf("string");
}
// Values should include the expected cookies
const cookieValues = values.map(c => c.value);
expect(cookieValues).toContain("1");
expect(cookieValues).toContain("2");
expect(cookieValues).toContain("3");
return new Response("ok");
},
"/iterator-forEach": req => {
const cookies = req.cookies;
// Test forEach method
const collected: { key: string; value: string }[] = [];
cookies.forEach((cookie, key) => {
expect(cookie).toBeTypeOf("object");
expect(cookie.constructor).toBe(Bun.Cookie);
expect(key).toBeTypeOf("string");
collected.push({ key, value: cookie.value });
});
expect(collected.length).toBe(3);
expect(collected.some(c => c.key === "a" && c.value === "1")).toBe(true);
expect(collected.some(c => c.key === "b" && c.value === "2")).toBe(true);
expect(collected.some(c => c.key === "c" && c.value === "3")).toBe(true);
return new Response("ok");
},
},
});
server.unref();
});
afterAll(() => {
server.stop(true);
});
it("implements entries() iterator", async () => {
const res = await fetch(`${server.url}iterator-entries`, {
headers: {
"Cookie": "a=1; b=2; c=3",
},
});
expect(res.status).toBe(200);
});
it("implements for...of iteration", async () => {
const res = await fetch(`${server.url}iterator-for-of`, {
headers: {
"Cookie": "a=1; b=2; c=3",
},
});
expect(res.status).toBe(200);
});
it("implements keys() and values() iterators", async () => {
const res = await fetch(`${server.url}iterator-keys-values`, {
headers: {
"Cookie": "a=1; b=2; c=3",
},
});
expect(res.status).toBe(200);
});
it("implements forEach method", async () => {
const res = await fetch(`${server.url}iterator-forEach`, {
headers: {
"Cookie": "a=1; b=2; c=3",
},
});
expect(res.status).toBe(200);
});
});
describe("Direct usage of Bun.Cookie and Bun.CookieMap", () => {
it("can create a Cookie directly", () => {
const cookie = new Bun.Cookie("name", "value");
expect(cookie.constructor).toBe(Bun.Cookie);
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.path).toBe("/");
// Domain may be null in the implementation
expect(cookie.domain == null || cookie.domain === "").toBe(true);
expect(cookie.secure).toBe(false);
expect(cookie.sameSite).toBe("lax");
});
it("can create a Cookie with options", () => {
const cookie = new Bun.Cookie("name", "value", {
path: "/path",
domain: "example.com",
secure: true,
sameSite: "lax",
});
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.path).toBe("/path");
expect(cookie.domain).toBe("example.com");
expect(cookie.secure).toBe(true);
expect(cookie.sameSite).toBe("lax");
});
it("can create a CookieMap directly", () => {
const cookieMap = new Bun.CookieMap();
expect(cookieMap.constructor).toBe(Bun.CookieMap);
expect(cookieMap.size).toBe(0);
});
it("can create a CookieMap with a cookie string", () => {
const cookieMap = new Bun.CookieMap("name=value; foo=bar");
expect(cookieMap.size).toBe(2);
const nameCookie = cookieMap.get("name");
expect(nameCookie).toBeDefined();
expect(nameCookie?.value).toBe("value");
const fooCookie = cookieMap.get("foo");
expect(fooCookie).toBeDefined();
expect(fooCookie?.value).toBe("bar");
});
it("can create a CookieMap with an object", () => {
const cookieMap = new Bun.CookieMap({
name: "value",
foo: "bar",
});
expect(cookieMap.size).toBe(2);
const nameCookie = cookieMap.get("name");
expect(nameCookie).toBeDefined();
expect(nameCookie?.value).toBe("value");
const fooCookie = cookieMap.get("foo");
expect(fooCookie).toBeDefined();
expect(fooCookie?.value).toBe("bar");
});
it("can create a CookieMap with an array of pairs", () => {
const cookieMap = new Bun.CookieMap([
["name", "value"],
["foo", "bar"],
]);
expect(cookieMap.size).toBe(2);
const nameCookie = cookieMap.get("name");
expect(nameCookie).toBeDefined();
expect(nameCookie?.value).toBe("value");
const fooCookie = cookieMap.get("foo");
expect(fooCookie).toBeDefined();
expect(fooCookie?.value).toBe("bar");
});
it("can set and get cookies in a CookieMap", () => {
const cookieMap = new Bun.CookieMap();
// Set with name/value
cookieMap.set("name", "value");
// Set with options
cookieMap.set({
name: "foo",
value: "bar",
secure: true,
path: "/path",
});
expect(cookieMap.size).toBe(2);
const nameCookie = cookieMap.get("name");
console.log(nameCookie);
expect(nameCookie).toBeDefined();
expect(nameCookie?.value).toBe("value");
const fooCookie = cookieMap.get("foo");
expect(fooCookie).toBeDefined();
expect(fooCookie?.value).toBe("bar");
expect(fooCookie?.secure).toBe(true);
expect(fooCookie?.path).toBe("/path");
});
it("can use Cookie.parse to parse cookie strings", () => {
const cookie = Bun.Cookie.parse("name=value; Path=/; Secure; SameSite=Lax");
expect(cookie.constructor).toBe(Bun.Cookie);
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.path).toBe("/");
expect(cookie.secure).toBe(true);
expect(cookie.sameSite.toLowerCase()).toBe("lax");
});
it("can use Cookie.from to create cookies", () => {
const cookie = Bun.Cookie.from("name", "value", {
path: "/path",
domain: "example.com",
secure: true,
sameSite: "none",
});
expect(cookie.constructor).toBe(Bun.Cookie);
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.path).toBe("/path");
expect(cookie.domain).toBe("example.com");
expect(cookie.secure).toBe(true);
expect(cookie.sameSite.toLowerCase()).toBe("none");
});
it("can convert cookies to string", () => {
const cookie = new Bun.Cookie("name", "value", {
path: "/path",
domain: "example.com",
secure: true,
sameSite: "lax",
});
const cookieStr = cookie.toString();
expect(cookieStr).toInclude("name=value");
expect(cookieStr).toInclude("Domain=example.com");
expect(cookieStr).toInclude("Path=/path");
expect(cookieStr).toInclude("Secure");
expect(cookieStr).not.toInclude("SameSite");
});
it("correctly handles toJSON methods", () => {
// Create a Cookie and test toJSON
const cookie = new Bun.Cookie("name", "value", {
path: "/test",
domain: "example.org",
secure: true,
sameSite: "lax",
expires: Date.now() + 3600000, // 1 hour in the future
});
const cookieJSON = cookie.toJSON();
expect(cookieJSON).toBeTypeOf("object");
expect(cookieJSON.name).toBe("name");
expect(cookieJSON.value).toBe("value");
expect(cookieJSON.path).toBe("/test");
expect(cookieJSON.domain).toBe("example.org");
expect(cookieJSON.secure).toBe(true);
// Create a CookieMap and test toJSON
const cookieMap = new Bun.CookieMap("a=1; b=2; c=3");
const mapJSON = cookieMap.toJSON();
expect(mapJSON).toBeInstanceOf(Array);
expect(mapJSON.length).toBe(3);
// Each entry should be [name, value]
for (const entry of mapJSON) {
expect(entry.length).toBe(2);
expect(entry[0]).toBeTypeOf("string");
expect(entry[1]).toBeTypeOf("object");
}
// Verify JSON.stringify works as expected
const jsonString = JSON.stringify(cookie);
expect(jsonString).toBeTypeOf("string");
const parsed = JSON.parse(jsonString);
expect(parsed.name).toBe("name");
expect(parsed.value).toBe("value");
});
});

View File

@@ -0,0 +1,228 @@
import { test, expect, describe } from "bun:test";
describe("Bun.Cookie", () => {
test("can create a cookie", () => {
const cookie = new Bun.Cookie("name", "value");
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.path).toBe("/");
expect(cookie.domain).toBe(null);
expect(cookie.expires).toBe(0);
expect(cookie.secure).toBe(false);
expect(cookie.sameSite).toBe("lax");
});
test("can create a cookie with options", () => {
const cookie = new Bun.Cookie("name", "value", {
domain: "example.com",
path: "/foo",
expires: 123456789,
secure: true,
sameSite: "strict",
});
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.domain).toBe("example.com");
expect(cookie.path).toBe("/foo");
expect(cookie.expires).toBe(123456789);
expect(cookie.secure).toBe(true);
expect(cookie.sameSite).toBe("strict");
});
test("stringify a cookie", () => {
const cookie = new Bun.Cookie("name", "value", {
domain: "example.com",
path: "/foo",
secure: true,
sameSite: "lax",
});
expect(cookie.toString()).toBe("name=value; Domain=example.com; Path=/foo; Secure");
});
test("parse a cookie string", () => {
const cookie = Bun.Cookie.parse("name=value; Domain=example.com; Path=/foo; Secure; SameSite=lax");
expect(cookie.name).toBe("name");
expect(cookie.value).toBe("value");
expect(cookie.domain).toBe("example.com");
expect(cookie.path).toBe("/foo");
expect(cookie.secure).toBe(true);
expect(cookie.sameSite).toBe("lax");
});
});
describe("Bun.CookieMap", () => {
test("can create an empty cookie map", () => {
const cookieMap = new Bun.CookieMap();
expect(cookieMap.size).toBe(0);
expect(cookieMap.toString()).toBe("");
});
test("can create a cookie map from a string", () => {
const cookieMap = new Bun.CookieMap("name=value; foo=bar");
expect(cookieMap.size).toBe(2);
expect(cookieMap.has("name")).toBe(true);
expect(cookieMap.has("foo")).toBe(true);
expect(cookieMap.get("name").value).toBe("value");
expect(cookieMap.get("foo").value).toBe("bar");
});
test("can create a cookie map from an object", () => {
const cookieMap = new Bun.CookieMap({
name: "value",
foo: "bar",
});
expect(cookieMap.size).toBe(2);
expect(cookieMap.has("name")).toBe(true);
expect(cookieMap.has("foo")).toBe(true);
expect(cookieMap.get("name").value).toBe("value");
expect(cookieMap.get("foo").value).toBe("bar");
});
test("can create a cookie map from pairs", () => {
const cookieMap = new Bun.CookieMap([
["name", "value"],
["foo", "bar"],
]);
expect(cookieMap.size).toBe(2);
expect(cookieMap.has("name")).toBe(true);
expect(cookieMap.has("foo")).toBe(true);
expect(cookieMap.get("name").value).toBe("value");
expect(cookieMap.get("foo").value).toBe("bar");
});
test("can set and get cookies", () => {
const cookieMap = new Bun.CookieMap();
cookieMap.set("name", "value");
expect(cookieMap.size).toBe(1);
expect(cookieMap.has("name")).toBe(true);
expect(cookieMap.get("name").value).toBe("value");
cookieMap.set("foo", "bar");
expect(cookieMap.size).toBe(2);
expect(cookieMap.has("foo")).toBe(true);
expect(cookieMap.get("foo").value).toBe("bar");
});
test("can set cookies with a Cookie object", () => {
const cookieMap = new Bun.CookieMap();
const cookie = new Bun.Cookie("name", "value", {
domain: "example.com",
path: "/foo",
secure: true,
sameSite: "lax",
});
cookieMap.set(cookie);
expect(cookieMap.size).toBe(1);
expect(cookieMap.has("name")).toBe(true);
const retrievedCookie = cookieMap.get("name");
expect(retrievedCookie.name).toBe("name");
expect(retrievedCookie.value).toBe("value");
expect(retrievedCookie.domain).toBe("example.com");
expect(retrievedCookie.path).toBe("/foo");
expect(retrievedCookie.secure).toBe(true);
expect(retrievedCookie.sameSite).toBe("lax");
});
test("can delete cookies", () => {
const cookieMap = new Bun.CookieMap("name=value; foo=bar");
expect(cookieMap.size).toBe(2);
cookieMap.delete("name");
expect(cookieMap.size).toBe(1);
expect(cookieMap.has("name")).toBe(false);
expect(cookieMap.has("foo")).toBe(true);
cookieMap.delete("foo");
expect(cookieMap.size).toBe(0);
expect(cookieMap.has("foo")).toBe(false);
});
test("can delete cookies with options", () => {
const cookieMap = new Bun.CookieMap();
cookieMap.set(
new Bun.Cookie("name", "value", {
domain: "example.com",
path: "/foo",
}),
);
cookieMap.delete({
name: "name",
domain: "example.com",
path: "/foo",
});
expect(cookieMap.size).toBe(0);
});
test("can get all cookies with the same name", () => {
const cookieMap = new Bun.CookieMap();
cookieMap.set(
new Bun.Cookie("name", "value1", {
domain: "example.com",
path: "/foo",
}),
);
cookieMap.set(
new Bun.Cookie("name", "value2", {
domain: "example.org",
path: "/bar",
}),
);
// Since we're overwriting cookies with the same name,
// the size should still be 1
expect(cookieMap.size).toBe(1);
// But this would work if we didn't overwrite
// const cookies = cookieMap.getAll("name");
// expect(cookies.length).toBe(2);
});
test("can stringify a cookie map", () => {
const cookieMap = new Bun.CookieMap("name=value; foo=bar");
expect(cookieMap.toString()).toBe("name=value; foo=bar");
});
test("supports iteration", () => {
const cookieMap = new Bun.CookieMap("name=value; foo=bar");
const entries = Array.from(cookieMap.entries());
expect(entries).toMatchInlineSnapshot(`
[
[
"name",
{
"name": "name",
"value": "value",
"path": "/",
"secure": false,
"sameSite": "lax",
"httpOnly": false,
"partitioned": false
},
],
[
"foo",
{
"name": "foo",
"value": "bar",
"path": "/",
"secure": false,
"sameSite": "lax",
"httpOnly": false,
"partitioned": false
},
],
]
`);
});
});