mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
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:
83
packages/bun-types/bun.d.ts
vendored
83
packages/bun-types/bun.d.ts
vendored
@@ -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]>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
310
src/bun.js/bindings/Cookie.cpp
Normal file
310
src/bun.js/bindings/Cookie.cpp
Normal 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
|
||||
90
src/bun.js/bindings/Cookie.h
Normal file
90
src/bun.js/bindings/Cookie.h
Normal 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
|
||||
303
src/bun.js/bindings/CookieMap.cpp
Normal file
303
src/bun.js/bindings/CookieMap.cpp
Normal 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
|
||||
89
src/bun.js/bindings/CookieMap.h
Normal file
89
src/bun.js/bindings/CookieMap.h
Normal 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
|
||||
@@ -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());
|
||||
|
||||
@@ -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*);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
893
src/bun.js/bindings/webcore/JSCookie.cpp
Normal file
893
src/bun.js/bindings/webcore/JSCookie.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
||||
75
src/bun.js/bindings/webcore/JSCookie.h
Normal file
75
src/bun.js/bindings/webcore/JSCookie.h
Normal 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
|
||||
893
src/bun.js/bindings/webcore/JSCookieMap.cpp
Normal file
893
src/bun.js/bindings/webcore/JSCookieMap.cpp
Normal 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
|
||||
75
src/bun.js/bindings/webcore/JSCookieMap.h
Normal file
75
src/bun.js/bindings/webcore/JSCookieMap.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) \
|
||||
|
||||
248
test/js/bun/cookie/cookie-map.test.ts
Normal file
248
test/js/bun/cookie/cookie-map.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
653
test/js/bun/http/bun-serve-cookies.test.ts
Normal file
653
test/js/bun/http/bun-serve-cookies.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
228
test/js/bun/util/cookie.test.js
Normal file
228
test/js/bun/util/cookie.test.js
Normal 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
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user