diff options
44 files changed, 5918 insertions, 843 deletions
@@ -200,9 +200,11 @@ MAC_INCLUDE_DIRS := -I$(WEBKIT_RELEASE_DIR)/JavaScriptCore/PrivateHeaders \ LINUX_INCLUDE_DIRS := -I$(JSC_INCLUDE_DIR) \ -Isrc/javascript/jsc/bindings/ -INCLUDE_DIRS := -I$(BUN_DEPS_DIR)/uws +UWS_INCLUDE_DIR := -I$(BUN_DEPS_DIR)/uws/uSockets/src -I$(BUN_DEPS_DIR)/uws/src -I$(BUN_DEPS_DIR) +INCLUDE_DIRS := $(UWS_INCLUDE_DIR) + ifeq ($(OS_NAME),linux) INCLUDE_DIRS += $(LINUX_INCLUDE_DIRS) @@ -456,7 +458,7 @@ build-obj-wasm-small: build-obj-safe: $(ZIG) build obj -Drelease-safe -UWS_CC_FLAGS = -pthread -DUWS_HTTPRESPONSE_NO_WRITEMARK=1 -DLIBUS_USE_OPENSSL=1 -DLIBUS_USE_BORINGSSL=1 -DWITH_BORINGSSL=1 -Wpedantic -Wall -Wextra -Wsign-conversion -Wconversion -Isrc -IuSockets/src -DUWS_WITH_PROXY +UWS_CC_FLAGS = -pthread -DLIBUS_USE_OPENSSL=1 -DUWS_HTTPRESPONSE_NO_WRITEMARK=1 -DLIBUS_USE_BORINGSSL=1 -DWITH_BORINGSSL=1 -Wpedantic -Wall -Wextra -Wsign-conversion -Wconversion $(UWS_INCLUDE) -DUWS_WITH_PROXY UWS_CXX_FLAGS = $(UWS_CC_FLAGS) -std=gnu++17 UWS_LDFLAGS = -I$(BUN_DEPS_DIR)/boringssl/include @@ -1032,7 +1034,7 @@ wasm-return1: # We do this outside of build.zig for performance reasons # The C compilation stuff with build.zig is really slow and we don't need to run this as often as the rest $(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp - $(CXX) $(CLANG_FLAGS) \ + $(CXX) $(CLANG_FLAGS) $(UWS_INCLUDE) \ $(MACOS_MIN_FLAG) \ $(OPTIMIZATION_LEVEL) \ -fno-exceptions \ diff --git a/integration/bunjs-only-snippets/globals.test.js b/integration/bunjs-only-snippets/globals.test.js index 831bbd93f..b498e0e8e 100644 --- a/integration/bunjs-only-snippets/globals.test.js +++ b/integration/bunjs-only-snippets/globals.test.js @@ -16,7 +16,8 @@ it("extendable", () => { var Foo = class extends Class {}; var bar = new Foo(); expect(bar instanceof Class).toBe(true); - expect(Class.prototype instanceof Class).toBe(true); + expect(!!Class.prototype).toBe(true); + expect(typeof Class.prototype).toBe("object"); } expect(true).toBe(true); }); diff --git a/integration/bunjs-only-snippets/html-rewriter.test.js b/integration/bunjs-only-snippets/html-rewriter.test.js index a4ca965aa..68e2849d9 100644 --- a/integration/bunjs-only-snippets/html-rewriter.test.js +++ b/integration/bunjs-only-snippets/html-rewriter.test.js @@ -26,10 +26,6 @@ describe("HTMLRewriter", () => { expect(await res.text()).toBe("<div><span>replace</span></div>"); }); - it("exists globally", async () => { - expect(typeof HTMLRewriter).toBe("function"); - expect(typeof HTMLRewriter.constructor).toBe("function"); - }); it("supports element handlers", async () => { var rewriter = new HTMLRewriter(); rewriter.on("div", { diff --git a/integration/bunjs-only-snippets/mmap.test.js b/integration/bunjs-only-snippets/mmap.test.js index 03ce75e87..3dd3aadb9 100644 --- a/integration/bunjs-only-snippets/mmap.test.js +++ b/integration/bunjs-only-snippets/mmap.test.js @@ -10,13 +10,13 @@ it("mmap finalizer", async () => { map = null; Bun.gc(true); - await new Promise(resolve => setTimeout(resolve, 1)); + await new Promise((resolve) => setTimeout(resolve, 1)); }); -it('mmap passed to other syscalls', async () => { +it("mmap passed to other syscalls", async () => { const map = Bun.mmap(path); - await Bun.write(path + '1', map); - const text = await (await Bun.file(path + '1')).text(); + await Bun.write(path + "1", map); + const text = await (await Bun.file(path + "1")).text(); expect(text).toBe(new TextDecoder().decode(map)); }); @@ -46,4 +46,4 @@ it("mmap private", () => { map2[0] = 0; expect(map2[0]).toBe(0); expect(map[0]).toBe(old); -});
\ No newline at end of file +}); diff --git a/integration/bunjs-only-snippets/response.file.test.js b/integration/bunjs-only-snippets/response.file.test.js index d094c0a62..6a879c758 100644 --- a/integration/bunjs-only-snippets/response.file.test.js +++ b/integration/bunjs-only-snippets/response.file.test.js @@ -2,12 +2,28 @@ import fs from "fs"; import { it, expect } from "bun:test"; import path from "path"; +function gc() { + Bun.gc(true); +} + +// we must ensure that finalizers are run +// so that the reference-counting logic is exercised +function gcTick() { + gc(); + return new Promise((resolve) => { + setTimeout(resolve, 0); + }); +} + it("Bun.file not found returns ENOENT", async () => { try { + await gcTick(); await Bun.file("/does/not/exist.txt").text(); + await gcTick(); } catch (exception) { expect(exception.code).toBe("ENOENT"); } + await gcTick(); }); it("Bun.write('out.txt', 'string')", async () => { @@ -17,11 +33,15 @@ it("Bun.write('out.txt', 'string')", async () => { fs.unlinkSync(path.join("/tmp", "out.txt")); } catch (e) {} } - + await gcTick(); await Bun.write("/tmp/out.txt", "string"); + await gcTick(); const out = Bun.file("/tmp/out.txt"); + await gcTick(); expect(await out.text()).toBe("string"); + await gcTick(); expect(await out.text()).toBe(fs.readFileSync("/tmp/out.txt", "utf8")); + await gcTick(); } }); @@ -30,38 +50,47 @@ it("Bun.write blob", async () => { Bun.file("/tmp/response-file.test.txt"), Bun.file(path.join(import.meta.dir, "fetch.js.txt")) ); + await gcTick(); await Bun.write(Bun.file("/tmp/response-file.test.txt"), "blah blah blha"); + await gcTick(); await Bun.write( Bun.file("/tmp/response-file.test.txt"), new Uint32Array(1024) ); + await gcTick(); await Bun.write("/tmp/response-file.test.txt", new Uint32Array(1024)); + await gcTick(); expect( await Bun.write( new TextEncoder().encode("/tmp/response-file.test.txt"), new Uint32Array(1024) ) ).toBe(new Uint32Array(1024).byteLength); + await gcTick(); }); it("Bun.file -> Bun.file", async () => { try { fs.unlinkSync(path.join("/tmp", "fetch.js.in")); } catch (e) {} - + await gcTick(); try { fs.unlinkSync(path.join("/tmp", "fetch.js.out")); } catch (e) {} - + await gcTick(); const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); const text = fs.readFileSync(file, "utf8"); fs.writeFileSync("/tmp/fetch.js.in", text, { mode: 0644 }); + await gcTick(); { const result = await Bun.write( Bun.file("/tmp/fetch.js.out"), Bun.file("/tmp/fetch.js.in") ); + await gcTick(); expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text); + await gcTick(); } { @@ -75,14 +104,18 @@ it("Bun.file -> Bun.file", async () => { } { + await gcTick(); await Bun.write("/tmp/fetch.js.in", Bun.file("/tmp/fetch.js.out")); + await gcTick(); expect(await Bun.file("/tmp/fetch.js.in").text()).toBe(text); } }); it("Bun.file", async () => { const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); expect(await Bun.file(file).text()).toBe(fs.readFileSync(file, "utf8")); + await gcTick(); }); it("Bun.file as a Blob", async () => { @@ -92,27 +125,34 @@ it("Bun.file as a Blob", async () => { // internally, instead of a byte array, it stores the file path! // this enables several performance optimizations var blob = Bun.file(filePath); + await gcTick(); // no size because we haven't read it from disk yet expect(blob.size).toBe(0); + await gcTick(); // now it reads "./fetch.js.txt" from the filesystem // it's lazy, only loads once we ask for it // if it fails, the promise will reject at this point expect(await blob.text()).toBe(fixture); + await gcTick(); // now that it's loaded, the size updates expect(blob.size).toBe(fixture.length); + await gcTick(); // and it only loads once for _all_ blobs pointing to that file path // until all references are released expect((await blob.arrayBuffer()).byteLength).toBe(fixture.length); + await gcTick(); const array = new Uint8Array(await blob.arrayBuffer()); + await gcTick(); const text = fixture; for (let i = 0; i < text.length; i++) { expect(array[i]).toBe(text.charCodeAt(i)); } + await gcTick(); expect(blob.size).toBe(fixture.length); blob = null; - Bun.gc(true); + await gcTick(); await new Promise((resolve) => setTimeout(resolve, 1)); // now we're back var blob = Bun.file(filePath); @@ -121,9 +161,13 @@ it("Bun.file as a Blob", async () => { it("Response -> Bun.file", async () => { const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); const text = fs.readFileSync(file, "utf8"); + await gcTick(); const response = new Response(Bun.file(file)); + await gcTick(); expect(await response.text()).toBe(text); + await gcTick(); }); it("Bun.file -> Response", async () => { @@ -131,18 +175,29 @@ it("Bun.file -> Response", async () => { try { fs.unlinkSync("/tmp/fetch.js.out"); } catch {} - + await gcTick(); const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); const text = fs.readFileSync(file, "utf8"); + await gcTick(); const resp = await fetch("https://example.com"); + await gcTick(); expect(await Bun.write("/tmp/fetch.js.out", resp)).toBe(text.length); + await gcTick(); expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text); + await gcTick(); }); it("Response -> Bun.file -> Response -> text", async () => { + await gcTick(); const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); const text = fs.readFileSync(file, "utf8"); + await gcTick(); const response = new Response(Bun.file(file)); + await gcTick(); const response2 = response.clone(); + await gcTick(); expect(await response2.text()).toBe(text); + await gcTick(); }); diff --git a/src/deps/_libusockets.h b/src/deps/_libusockets.h index f0825e294..d63fc38ca 100644 --- a/src/deps/_libusockets.h +++ b/src/deps/_libusockets.h @@ -1,3 +1,4 @@ +#pragma once #ifndef LIBUWS_CAPI_HEADER #define LIBUWS_CAPI_HEADER @@ -7,6 +8,15 @@ #include <stdint.h> #include <uws/src/App.h> #include <uws/src/AsyncSocket.h> + +#ifndef STRING_POINTER +#define STRING_POINTER +typedef struct StringPointer { + uint32_t off; + uint32_t len; +} StringPointer; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -282,11 +292,6 @@ void uws_loop_addPreHandler(us_loop_t *loop, void *key, void uws_loop_removePreHandler(us_loop_t *loop, void *ctx_); void uws_loop_defer(us_loop_t *loop, void *ctx, void (*cb)(void *ctx)); -typedef struct StringPointer { - uint32_t off; - uint32_t len; -} StringPointer; - void uws_res_write_headers(int ssl, uws_res_t *res, const StringPointer *names, const StringPointer *values, size_t count, const char *buf); diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig index e3a3c6d75..ac6190f7e 100644 --- a/src/javascript/jsc/api/html_rewriter.zig +++ b/src/javascript/jsc/api/html_rewriter.zig @@ -217,7 +217,7 @@ pub const HTMLRewriter = struct { pub fn returnEmptyResponse(this: *HTMLRewriter, global: *JSGlobalObject, response: *Response) JSValue { var result = bun.default_allocator.create(Response) catch unreachable; - response.cloneInto(result, getAllocator(global.ref())); + response.cloneInto(result, getAllocator(global.ref()), global); this.finalizeWithoutDestroy(); return JSValue.fromRef(Response.makeMaybePooled(global.ref(), result)); } @@ -280,7 +280,6 @@ pub const HTMLRewriter = struct { .body = .{ .init = .{ .status_code = 200, - .headers = null, }, .value = .{ .Locked = .{ diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig index 38fad7c0c..aeb13b6bc 100644 --- a/src/javascript/jsc/api/server.zig +++ b/src/javascript/jsc/api/server.zig @@ -377,7 +377,7 @@ pub fn NewServer(comptime ssl_enabled: bool, comptime debug_mode: bool) type { response_ptr: ?*JSC.WebCore.Response = null, blob: JSC.WebCore.Blob = JSC.WebCore.Blob{}, promise: ?*JSC.JSValue = null, - response_headers: ?*JSC.WebCore.Headers.RefCountedHeaders = null, + response_headers: ?*JSC.FetchHeaders = null, has_abort_handler: bool = false, has_sendfile_ctx: bool = false, has_called_error_handler: bool = false, @@ -559,25 +559,18 @@ pub fn NewServer(comptime ssl_enabled: bool, comptime debug_mode: bool) type { fn writeHeaders( this: *RequestContext, - headers_: *Headers.RefCountedHeaders, + headers: *JSC.FetchHeaders, ) void { - var headers: *JSC.WebCore.Headers = headers_.get(); - if (headers.getHeaderIndex("content-length")) |index| { - headers.entries.orderedRemove(index); - } - - if (this.blob.content_type.len > 0 and headers.getHeaderIndex("content-type") == null) { - this.resp.writeHeader("content-type", this.blob.content_type); - } else if (MimeType.sniff(this.blob.sharedView())) |content| { - this.resp.writeHeader("content-type", content.value); + headers.remove(&ZigString.init("content-length")); + if (!headers.has(&ZigString.init("content-type"))) { + if (this.blob.content_type.len > 0) { + this.resp.writeHeader("content-type", this.blob.content_type); + } else if (MimeType.sniff(this.blob.sharedView())) |content| { + this.resp.writeHeader("content-type", content.value); + } } - defer headers_.deref(); - var entries = headers.entries.slice(); - const names = entries.items(.name); - const values = entries.items(.value); - - this.resp.writeHeaders(names, values, headers.buf.items); + headers.toUWSResponse(ssl_enabled, this.resp); } pub fn writeStatus(this: *RequestContext, status: u16) void { @@ -834,8 +827,9 @@ pub fn NewServer(comptime ssl_enabled: bool, comptime debug_mode: bool) type { this.writeStatus(status); - if (response.body.init.headers) |headers_| { + if (response.body.init.headers.as(JSC.FetchHeaders)) |headers_| { this.writeHeaders(headers_); + headers_.deref(); } else if (this.blob.content_type.len > 0) { this.resp.writeHeader("content-type", this.blob.content_type); } else if (MimeType.sniff(this.blob.sharedView())) |content| { diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index 20752e8e6..aa9e9abd2 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -21,7 +21,6 @@ const Response = WebCore.Response; const Request = WebCore.Request; const Router = @import("./api/router.zig"); const FetchEvent = WebCore.FetchEvent; -const Headers = WebCore.Headers.RefCountedHeaders; const IdentityContext = @import("../../identity_context.zig").IdentityContext; const Body = WebCore.Body; @@ -2536,7 +2535,6 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{ ExpectPrototype, FetchEvent, FetchTaskletContext, - Headers, HTMLRewriter, JSNode, LazyPropertiesObject, diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index a99087abf..178fe3d1d 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -86,10 +86,11 @@ #include "JSCustomEvent.h" #include "JSAbortController.h" #include "JSEvent.h" +#include "JSFetchHeaders.h" #include "Process.h" -#include "JavaScriptCore/RemoteInspectorServer.h" +#include <JavaScriptCore/RemoteInspectorServer.h> using JSGlobalObject = JSC::JSGlobalObject; using Exception = JSC::Exception; @@ -372,6 +373,17 @@ JSC_DEFINE_CUSTOM_GETTER(JSCustomEvent_getter, WebCore::JSCustomEvent::getConstructor(JSC::getVM(lexicalGlobalObject), thisObject)); } +JSC_DECLARE_CUSTOM_GETTER(JSFetchHeaders_getter); + +JSC_DEFINE_CUSTOM_GETTER(JSFetchHeaders_getter, + (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, + JSC::PropertyName)) +{ + Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(lexicalGlobalObject); + return JSC::JSValue::encode( + WebCore::JSFetchHeaders::getConstructor(JSC::getVM(lexicalGlobalObject), thisObject)); +} + JSC_DECLARE_CUSTOM_GETTER(JSEventTarget_getter); JSC_DEFINE_CUSTOM_GETTER(JSEventTarget_getter, @@ -849,11 +861,14 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "CustomEvent"), JSC::CustomGetterSetter::create(vm, JSCustomEvent_getter, nullptr), JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "Headers"), JSC::CustomGetterSetter::create(vm, JSFetchHeaders_getter, nullptr), + JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + extraStaticGlobals.releaseBuffer(); - // this->setRemoteDebuggingEnabled(true); + this->setRemoteDebuggingEnabled(true); // auto& server = Inspector::RemoteInspectorServer::singleton(); - // if (server.start("127.0.0.1", 9222)) { + // if (server.start("localhost", 9222)) { // } } diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp index 3f5486c5f..9bb5b33f3 100644 --- a/src/javascript/jsc/bindings/bindings.cpp +++ b/src/javascript/jsc/bindings/bindings.cpp @@ -48,20 +48,234 @@ #include "wtf/text/StringView.h" #include "wtf/text/WTFString.h" +#include "JSFetchHeaders.h" +#include "FetchHeaders.h" #include "DOMURL.h" #include "JSDOMURL.h" +#include "_libusockets.h" +#include <string_view> +#include <uws/src/App.h> +#include <uws/uSockets/src/internal/internal.h> +#include "IDLTypes.h" +#include "JSDOMBinding.h" +#include "JSDOMConstructor.h" +#include "JSDOMConvertBase.h" +#include "JSDOMConvertBoolean.h" +#include "JSDOMConvertInterface.h" +#include "JSDOMConvertNullable.h" +#include "JSDOMConvertRecord.h" +#include "JSDOMConvertSequences.h" +#include "JSDOMConvertStrings.h" +#include "JSDOMConvertUnion.h" +#include "JSDOMExceptionHandling.h" +#include "JSDOMGlobalObjectInlines.h" +#include "JSDOMIterator.h" +#include "JSDOMOperation.h" +#include "JSDOMWrapperCache.h" + +template<typename WebCoreType, typename OutType> +OutType* WebCoreCast(JSC__JSValue JSValue0, JSC::VM* vm) +{ + // we must use jsDynamicCast here so that we check that the type is correct + WebCoreType* jsdomURL = JSC::jsDynamicCast<WebCoreType*>(*vm, JSC::JSValue::decode(JSValue0)); + if (jsdomURL == nullptr) { + return nullptr; + } + + return reinterpret_cast<OutType*>(&jsdomURL->wrapped()); +} + +template<typename UWSResponse> +static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) +{ + auto iter = headers->createIterator(); + uint32_t i = 0; + unsigned count = 0; + for (auto pair = iter.next(); pair; pair = iter.next()) { + auto name = pair->key; + auto value = pair->value; + res->writeHeader(std::string_view(reinterpret_cast<const char*>(name.characters8()), name.length()), std::string_view(reinterpret_cast<const char*>(value.characters8()), value.length())); + } +} + extern "C" { -WebCore__DOMURL* WebCore__DOMURL__cast(JSC__JSValue JSValue0) +void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool is_ssl, void* arg2) { - auto* jsdomURL = JSC::jsCast<WebCore::JSDOMURL*>(JSC::JSValue::decode(JSValue0)); - if (jsdomURL == nullptr) { - return nullptr; + if (is_ssl) { + copyToUWS<uWS::HttpResponse<true>>(arg0, reinterpret_cast<uWS::HttpResponse<true>*>(arg2)); + } else { + copyToUWS<uWS::HttpResponse<false>>(arg0, reinterpret_cast<uWS::HttpResponse<false>*>(arg2)); } +} - return &jsdomURL->wrapped(); +void WebCore__FetchHeaders__append(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2) +{ + headers->append(Zig::toString(*arg1), Zig::toString(*arg2)); +} +WebCore__FetchHeaders* WebCore__FetchHeaders__cast_(JSC__JSValue JSValue0, JSC__VM* vm) +{ + return WebCoreCast<WebCore::JSFetchHeaders, WebCore__FetchHeaders>(JSValue0, vm); } + +using namespace WebCore; + +WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject* lexicalGlobalObject, JSC__JSValue argument0_) +{ + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + EnsureStillAliveScope argument0 = JSC::JSValue::decode(argument0_); + auto throwScope = DECLARE_THROW_SCOPE(lexicalGlobalObject->vm()); + auto init = argument0.value().isUndefined() ? std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>() : std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>(convert<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>(*lexicalGlobalObject, argument0.value())); + RETURN_IF_EXCEPTION(throwScope, nullptr); + return WebCoreCast<WebCore::JSFetchHeaders, WebCore__FetchHeaders>( + JSC::JSValue::encode(WebCore::toJSNewlyCreated(lexicalGlobalObject, globalObject, WebCore::FetchHeaders::create(WTFMove(init)).releaseReturnValue())), + &lexicalGlobalObject->vm()); +} + +JSC__JSValue WebCore__FetchHeaders__toJS(WebCore__FetchHeaders* headers, JSC__JSGlobalObject* lexicalGlobalObject) +{ + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + + return JSC::JSValue::encode(WebCore::toJS(lexicalGlobalObject, globalObject, headers)); +} +JSC__JSValue WebCore__FetchHeaders__clone(WebCore__FetchHeaders* headers, JSC__JSGlobalObject* arg1) +{ + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg1); + auto clone = WebCore::FetchHeaders::create(); + if (headers->size() > 0) { + auto iter = headers->createIterator(); + uint32_t i = 0; + unsigned count = 0; + for (auto pair = iter.next(); pair; pair = iter.next()) { + auto name = pair->key; + auto value = pair->value; + clone->append(name.isolatedCopy(), value.isolatedCopy()); + } + } + return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg1, globalObject, WTFMove(clone))); +} + +// WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* headers) +// { + +// return JSC::JSValue::encode(WebCore::toJSNewlyCreated(globalObject, globalObject, WebCore::FetchHeaders::create(*headers))); +// } + +void WebCore__FetchHeaders__copyTo(WebCore__FetchHeaders* headers, StringPointer* names, StringPointer* values, unsigned char* buf) +{ + auto iter = headers->createIterator(); + uint32_t i = 0; + unsigned count = 0; + for (auto pair = iter.next(); pair; pair = iter.next()) { + auto name = pair->key; + auto value = pair->value; + names[count] = { i, name.length() }; + memcpy(&buf[i], name.characters8(), name.length()); + i += name.length(); + values[count++] = { i, value.length() }; + memcpy(&buf[i], value.characters8(), value.length()); + i += value.length(); + } +} +void WebCore__FetchHeaders__count(WebCore__FetchHeaders* headers, uint32_t* count, uint32_t* buf_len) +{ + auto iter = headers->createIterator(); + uint32_t i = 0; + for (auto pair = iter.next(); pair; pair = iter.next()) { + i += pair->key.length(); + i += pair->value.length(); + } + + *count = headers->size(); + *buf_len = i; +} +typedef struct PicoHTTPHeader { + unsigned const char* name; + size_t name_len; + unsigned const char* value; + size_t value_len; +} PicoHTTPHeader; + +typedef struct PicoHTTPHeaders { + const PicoHTTPHeader* ptr; + size_t len; +} PicoHTTPHeaders; +JSC__JSValue WebCore__FetchHeaders__createFromPicoHeaders_(JSC__JSGlobalObject* arg0, const void* arg1) +{ + PicoHTTPHeaders pico_headers = *reinterpret_cast<const PicoHTTPHeaders*>(arg1); + Vector<KeyValuePair<String, String>> pairs; + pairs.reserveCapacity(pico_headers.len); + for (size_t i = 0; i < pico_headers.len; i++) { + WTF::String name = WTF::String(pico_headers.ptr[i].name, pico_headers.ptr[i].name_len); + WTF::String value = WTF::String(pico_headers.ptr[i].value, pico_headers.ptr[i].value_len); + pairs.uncheckedAppend(KeyValuePair<String, String>(name, value)); + } + + Ref<WebCore::FetchHeaders> headers = WebCore::FetchHeaders::create(); + headers->fill(WebCore::FetchHeaders::Init(pairs)); + pairs.releaseBuffer(); + return JSC::JSValue::encode(WebCore::toJSNewlyCreated(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), WTFMove(headers))); +} +JSC__JSValue WebCore__FetchHeaders__createFromUWS(JSC__JSGlobalObject* arg0, void* arg1) +{ + uWS::HttpRequest req = *reinterpret_cast<uWS::HttpRequest*>(arg1); + Vector<KeyValuePair<String, String>> pairs; + pairs.reserveCapacity(55); + for (const auto& header : req) { + auto name = WTF::String(reinterpret_cast<const LChar*>(header.first.data()), header.first.length()); + auto value = WTF::String(reinterpret_cast<const LChar*>(header.second.data()), header.second.length()); + pairs.uncheckedAppend(KeyValuePair<String, String>(name, value)); + } + + Ref<WebCore::FetchHeaders> headers = WebCore::FetchHeaders::create(); + headers->fill(WebCore::FetchHeaders::Init(pairs)); + pairs.releaseBuffer(); + return JSC::JSValue::encode(WebCore::toJS(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), headers)); +} +void WebCore__FetchHeaders__deref(WebCore__FetchHeaders* arg0) +{ + arg0->deref(); +} + +JSC__JSValue WebCore__FetchHeaders__createValue(JSC__JSGlobalObject* arg0, StringPointer* arg1, StringPointer* arg2, const ZigString* arg3, uint32_t count) +{ + Vector<KeyValuePair<String, String>> pairs; + pairs.reserveCapacity(count); + ZigString buf = *arg3; + for (uint32_t i = 0; i < count; i++) { + WTF::String name = Zig::toStringCopy(buf, arg1[i]); + WTF::String value = Zig::toStringCopy(buf, arg2[i]); + pairs.uncheckedAppend(KeyValuePair<String, String>(name, value)); + } + + Ref<WebCore::FetchHeaders> headers = WebCore::FetchHeaders::create(); + headers->fill(WebCore::FetchHeaders::Init(pairs)); + pairs.releaseBuffer(); + return JSC::JSValue::encode(WebCore::toJS(arg0, reinterpret_cast<Zig::GlobalObject*>(arg0), headers)); +} +void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* headers, const ZigString* arg1, ZigString* arg2) +{ + *arg2 = Zig::toZigString(headers->get(Zig::toString(*arg1)).releaseReturnValue()); +} +bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* headers, const ZigString* arg1) +{ + return headers->has(Zig::toString(*arg1)).releaseReturnValue(); +} +void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* headers, const ZigString* arg1, const ZigString* arg2) +{ + headers->set(Zig::toString(*arg1), Zig::toString(*arg2)); +} +void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* headers, const ZigString* arg1) +{ + headers->remove(Zig::toString(*arg1)); +} + +WebCore__DOMURL* WebCore__DOMURL__cast_(JSC__JSValue JSValue0, JSC::VM* vm) +{ + return WebCoreCast<WebCore::JSDOMURL, WebCore__DOMURL>(JSValue0, vm); +} + void WebCore__DOMURL__href_(WebCore__DOMURL* domURL, ZigString* arg1) { const WTF::URL& href = domURL->href(); diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index 15f71a470..0f8d5de00 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -110,6 +110,24 @@ pub const ZigString = extern struct { pub const empty = Slice{ .allocator = bun.default_allocator, .ptr = undefined, .len = 0, .allocated = false }; + pub fn clone(this: Slice, allocator: std.mem.Allocator) !Slice { + if (!this.allocated) { + return Slice{ .allocator = allocator, .ptr = this.ptr, .len = this.len, .allocated = false }; + } + + var duped = try allocator.dupe(u8, this.ptr[0..this.len]); + return Slice{ .allocator = allocator, .ptr = duped.ptr, .len = this.len, .allocated = true }; + } + + pub fn cloneZ(this: Slice, allocator: std.mem.Allocator) !Slice { + if (this.allocated or this.len == 0) { + return this; + } + + var duped = try allocator.dupeZ(u8, this.ptr[0..this.len]); + return Slice{ .allocator = allocator, .ptr = duped.ptr, .len = this.len, .allocated = true }; + } + pub fn slice(this: Slice) []const u8 { return this.ptr[0..this.len]; } @@ -272,6 +290,28 @@ pub const ZigString = extern struct { }; } + pub fn toSliceZ(this: ZigString, allocator: std.mem.Allocator) Slice { + if (this.len == 0) + return Slice{ .ptr = "", .len = 0, .allocator = allocator, .allocated = false }; + + if (is16Bit(&this)) { + var buffer = std.fmt.allocPrintZ(allocator, "{}", .{this}) catch unreachable; + return Slice{ + .ptr = buffer.ptr, + .len = @truncate(u32, buffer.len), + .allocated = true, + .allocator = allocator, + }; + } + + return Slice{ + .ptr = untagged(this.ptr), + .len = @truncate(u32, this.len), + .allocated = false, + .allocator = allocator, + }; + } + pub fn sliceZBuf(this: ZigString, buf: *[bun.MAX_PATH_BYTES]u8) ![:0]const u8 { return try std.fmt.bufPrintZ(buf, "{}", .{this}); } @@ -355,8 +395,12 @@ pub const DOMURL = opaque { const cppFn = shim.cppFn; pub const name = "WebCore::DOMURL"; + pub fn cast_(value: JSValue, vm: *VM) ?*DOMURL { + return shim.cppFn("cast_", .{ value, vm }); + } + pub fn cast(value: JSValue) ?*DOMURL { - return shim.cppFn("cast", .{value}); + return cast_(value, JSC.VirtualMachine.vm.global.vm()); } pub fn href_(this: *DOMURL, out: *ZigString) void { @@ -380,12 +424,291 @@ pub const DOMURL = opaque { } pub const Extern = [_][]const u8{ - "cast", + "cast_", "href_", "pathname_", }; }; +const Api = @import("../../../api/schema.zig").Api; + +pub const FetchHeaders = opaque { + pub const shim = Shimmer("WebCore", "FetchHeaders", @This()); + + const cppFn = shim.cppFn; + pub const name = "WebCore::FetchHeaders"; + + pub fn createValue( + global: *JSGlobalObject, + names: [*c]Api.StringPointer, + values: [*c]Api.StringPointer, + buf: *const ZigString, + count_: u32, + ) JSValue { + return shim.cppFn("createValue", .{ + global, + names, + values, + buf, + count_, + }); + } + + pub fn createFromJS( + global: *JSGlobalObject, + value: JSValue, + ) ?*FetchHeaders { + return shim.cppFn("createFromJS", .{ + global, + value, + }); + } + + pub fn putDefault(this: *FetchHeaders, name_: []const u8, value: []const u8) void { + if (this.has(&ZigString.init(name_))) { + return; + } + + this.put_(&ZigString.init(name_), &ZigString.init(value)); + } + + pub fn from( + global: *JSGlobalObject, + names: [*c]Api.StringPointer, + values: [*c]Api.StringPointer, + buf: *const ZigString, + count_: u32, + ) JSValue { + return shim.cppFn("createValue", .{ + global, + names, + values, + buf, + count_, + }); + } + + pub fn createFromUWS( + global: *JSGlobalObject, + uws_request: *anyopaque, + ) JSValue { + return shim.cppFn("createFromUWS", .{ + global, + uws_request, + }); + } + + pub fn toUWSResponse( + headers: *FetchHeaders, + is_ssl: bool, + uws_response: *anyopaque, + ) void { + return shim.cppFn("toUWSResponse", .{ + headers, + is_ssl, + uws_response, + }); + } + + const PicoHeaders = extern struct { + ptr: *const anyopaque, + len: usize, + }; + + pub fn createEmpty( + global: *JSGlobalObject, + ) JSValue { + const pico_ = PicoHeaders{ .ptr = undefined, .len = 0 }; + return shim.cppFn("createFromPicoHeaders_", .{ + global, + &pico_, + }); + } + + pub fn createFromPicoHeaders( + global: *JSGlobalObject, + pico_headers: anytype, + ) JSValue { + const out = PicoHeaders{ .ptr = pico_headers.ptr, .len = pico_headers.len }; + const result = shim.cppFn("createFromPicoHeaders_", .{ + global, + &out, + }); + return result; + } + + pub fn createFromPicoHeaders_( + global: *JSGlobalObject, + pico_headers: *const anyopaque, + ) JSValue { + return shim.cppFn("createFromPicoHeaders_", .{ + global, + pico_headers, + }); + } + + pub fn append( + this: *FetchHeaders, + name_: *const ZigString, + value: *const ZigString, + ) void { + return shim.cppFn("append", .{ + this, + name_, + value, + }); + } + + pub fn put_( + this: *FetchHeaders, + name_: *const ZigString, + value: *const ZigString, + ) void { + return shim.cppFn("put_", .{ + this, + name_, + value, + }); + } + + pub fn put( + this: *FetchHeaders, + name_: []const u8, + value: []const u8, + ) void { + this.put_(&ZigString.init(name_), &ZigString.init(value)); + } + + pub fn get_( + this: *FetchHeaders, + name_: *const ZigString, + out: *ZigString, + ) void { + shim.cppFn("get_", .{ + this, + name_, + out, + }); + } + + pub fn get( + this: *FetchHeaders, + name_: []const u8, + ) ?[]const u8 { + var out = ZigString.Empty; + get_(this, &ZigString.init(name_), &out); + if (out.len > 0) { + return out.slice(); + } + + return null; + } + + pub fn has( + this: *FetchHeaders, + name_: *const ZigString, + ) bool { + return shim.cppFn("has", .{ + this, + name_, + }); + } + + pub fn remove( + this: *FetchHeaders, + name_: *const ZigString, + ) void { + return shim.cppFn("remove", .{ + this, + name_, + }); + } + + pub fn cast_(value: JSValue, vm: *VM) ?*FetchHeaders { + return shim.cppFn("cast_", .{ value, vm }); + } + + pub fn cast(value: JSValue) ?*FetchHeaders { + return cast_(value, JSC.VirtualMachine.vm.global.vm()); + } + + pub fn toJS(this: *FetchHeaders, globalThis: *JSGlobalObject) JSValue { + return shim.cppFn("toJS", .{ this, globalThis }); + } + + pub fn count( + this: *FetchHeaders, + names: *u32, + buf_len: *u32, + ) void { + return shim.cppFn("count", .{ + this, + names, + buf_len, + }); + } + + pub fn clone( + this: *FetchHeaders, + global: *JSGlobalObject, + ) JSValue { + return shim.cppFn("clone", .{ + this, + global, + }); + } + + pub fn cloneThis( + this: *FetchHeaders, + ) ?*FetchHeaders { + return shim.cppFn("cloneThis", .{ + this, + }); + } + + pub fn deref( + this: *FetchHeaders, + ) void { + return shim.cppFn("deref", .{ + this, + }); + } + + pub fn copyTo( + this: *FetchHeaders, + names: [*c]Api.StringPointer, + values: [*c]Api.StringPointer, + buf: [*]u8, + ) void { + return shim.cppFn("copyTo", .{ + this, + names, + values, + buf, + }); + } + + pub const Extern = [_][]const u8{ + "append", + "cast_", + "clone", + "cloneThis", + "copyTo", + "count", + "createFromJS", + "createFromPicoHeaders_", + "createFromUWS", + "createValue", + "deref", + "get_", + "has", + "put_", + "remove", + "toJS", + "toUWSResponse", + }; +}; + pub const SystemError = extern struct { errno: c_int = 0, /// label for errno @@ -1667,6 +1990,10 @@ pub const String = extern struct { }; }; +pub const BuiltinName = enum(u8) { + headers, +}; + pub const JSValue = enum(u64) { _, @@ -1935,13 +2262,17 @@ pub const JSValue = enum(u64) { } pub fn as(value: JSValue, comptime ZigType: type) ?*ZigType { - if (value.isUndefinedOrNull()) + if (value.isEmptyOrUndefinedOrNull()) return null; if (comptime ZigType == DOMURL) { return DOMURL.cast(value); } + if (comptime ZigType == FetchHeaders) { + return FetchHeaders.cast(value); + } + return JSC.GetJSPrivateData(ZigType, value.asObjectRef()); } @@ -2083,6 +2414,12 @@ pub const JSValue = enum(u64) { pub fn isNull(this: JSValue) bool { return @enumToInt(this) == 0x2; } + pub fn isEmptyOrUndefinedOrNull(this: JSValue) bool { + return switch (@enumToInt(this)) { + 0, 0xa, 0x2 => true, + else => false, + }; + } pub fn isUndefinedOrNull(this: JSValue) bool { return switch (@enumToInt(this)) { 0xa, 0x2 => true, @@ -2249,6 +2586,10 @@ pub const JSValue = enum(u64) { return cppFn("getIfPropertyExistsImpl", .{ this, global, ptr, len }); } + pub fn getHiddenIfPropertyExistsImpl(this: JSValue, global: *JSGlobalObject, ptr: [*]const u8, len: u32) JSValue { + return cppFn("getHiddenIfPropertyExistsImpl", .{ this, global, ptr, len }); + } + pub fn getSymbolDescription(this: JSValue, global: *JSGlobalObject, str: *ZigString) void { cppFn("getSymbolDescription", .{ this, global, str }); } @@ -2314,6 +2655,11 @@ pub const JSValue = enum(u64) { return if (@enumToInt(value) != 0) value else return null; } + pub fn getHidden(this: JSValue, global: *JSGlobalObject, property: []const u8) ?JSValue { + const value = getIfPropertyExistsImpl(this, global, property.ptr, @intCast(u32, property.len)); + return if (@enumToInt(value) != 0) value else return null; + } + pub fn getTruthy(this: JSValue, global: *JSGlobalObject, property: []const u8) ?JSValue { if (get(this, global, property)) |prop| { if (@enumToInt(prop) == 0 or prop.isUndefinedOrNull()) return null; diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index 8c6cb17c9..d58e45157 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -1695,11 +1695,6 @@ pub const ZigConsoleClient = struct { response.writeFormat(this, writer_, enable_ansi_colors) catch {}; return; }, - @field(JSPrivateDataPtr.Tag, @typeName(JSC.WebCore.Headers.RefCountedHeaders)) => { - var obj = priv_data.as(JSC.WebCore.Headers.RefCountedHeaders); - obj.leak().writeFormat(this, writer_, enable_ansi_colors) catch {}; - return; - }, .Request => { this.printAs(.JSON, Writer, writer_, value, .Object, enable_ansi_colors); return; diff --git a/src/javascript/jsc/bindings/header-gen.zig b/src/javascript/jsc/bindings/header-gen.zig index 612f86215..30467f698 100644 --- a/src/javascript/jsc/bindings/header-gen.zig +++ b/src/javascript/jsc/bindings/header-gen.zig @@ -41,6 +41,7 @@ pub fn cTypeLabel(comptime Type: type) ?[]const u8 { f64 => "double", f32 => "float", *anyopaque => "void*", + *const anyopaque => "const void*", [*]bool => "bool*", [*]usize => "size_t*", [*]isize => "int*", diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index ab5dead54..9d0ce2113 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1648722142 +//-- AUTOGENERATED FILE -- 1648790662 // clang-format off #pragma once @@ -256,8 +256,8 @@ extern "C" const size_t Zig__ConsoleClient_object_align_ = alignof(Zig::ConsoleC extern "C" const size_t Bun__Timer_object_size_ = sizeof(Bun__Timer); extern "C" const size_t Bun__Timer_object_align_ = alignof(Bun__Timer); -const size_t sizes[31] = {sizeof(JSC::JSObject), sizeof(WebCore::DOMURL), sizeof(SystemError), sizeof(JSC::JSCell), sizeof(JSC::JSString), sizeof(Inspector::ScriptArguments), sizeof(JSC::JSModuleLoader), sizeof(JSC::JSModuleRecord), sizeof(JSC::JSPromise), sizeof(JSC::JSInternalPromise), sizeof(JSC::SourceOrigin), sizeof(JSC::SourceCode), sizeof(JSC::JSFunction), sizeof(JSC::JSGlobalObject), sizeof(WTF::URL), sizeof(WTF::String), sizeof(JSC::JSValue), sizeof(JSC::PropertyName), sizeof(JSC::Exception), sizeof(JSC::VM), sizeof(JSC::ThrowScope), sizeof(JSC::CatchScope), sizeof(JSC::CallFrame), sizeof(JSC::Identifier), sizeof(WTF::StringImpl), sizeof(WTF::ExternalStringImpl), sizeof(WTF::StringView), sizeof(Zig::GlobalObject), sizeof(Bun__Readable), sizeof(Bun__Writable), sizeof(Bun__Path)}; +const size_t sizes[32] = {sizeof(JSC::JSObject), sizeof(WebCore::DOMURL), sizeof(WebCore::FetchHeaders), sizeof(SystemError), sizeof(JSC::JSCell), sizeof(JSC::JSString), sizeof(Inspector::ScriptArguments), sizeof(JSC::JSModuleLoader), sizeof(JSC::JSModuleRecord), sizeof(JSC::JSPromise), sizeof(JSC::JSInternalPromise), sizeof(JSC::SourceOrigin), sizeof(JSC::SourceCode), sizeof(JSC::JSFunction), sizeof(JSC::JSGlobalObject), sizeof(WTF::URL), sizeof(WTF::String), sizeof(JSC::JSValue), sizeof(JSC::PropertyName), sizeof(JSC::Exception), sizeof(JSC::VM), sizeof(JSC::ThrowScope), sizeof(JSC::CatchScope), sizeof(JSC::CallFrame), sizeof(JSC::Identifier), sizeof(WTF::StringImpl), sizeof(WTF::ExternalStringImpl), sizeof(WTF::StringView), sizeof(Zig::GlobalObject), sizeof(Bun__Readable), sizeof(Bun__Writable), sizeof(Bun__Path)}; -const char* names[31] = {"JSC__JSObject", "WebCore__DOMURL", "SystemError", "JSC__JSCell", "JSC__JSString", "Inspector__ScriptArguments", "JSC__JSModuleLoader", "JSC__JSModuleRecord", "JSC__JSPromise", "JSC__JSInternalPromise", "JSC__SourceOrigin", "JSC__SourceCode", "JSC__JSFunction", "JSC__JSGlobalObject", "WTF__URL", "WTF__String", "JSC__JSValue", "JSC__PropertyName", "JSC__Exception", "JSC__VM", "JSC__ThrowScope", "JSC__CatchScope", "JSC__CallFrame", "JSC__Identifier", "WTF__StringImpl", "WTF__ExternalStringImpl", "WTF__StringView", "Zig__GlobalObject", "Bun__Readable", "Bun__Writable", "Bun__Path"}; +const char* names[32] = {"JSC__JSObject", "WebCore__DOMURL", "WebCore__FetchHeaders", "SystemError", "JSC__JSCell", "JSC__JSString", "Inspector__ScriptArguments", "JSC__JSModuleLoader", "JSC__JSModuleRecord", "JSC__JSPromise", "JSC__JSInternalPromise", "JSC__SourceOrigin", "JSC__SourceCode", "JSC__JSFunction", "JSC__JSGlobalObject", "WTF__URL", "WTF__String", "JSC__JSValue", "JSC__PropertyName", "JSC__Exception", "JSC__VM", "JSC__ThrowScope", "JSC__CatchScope", "JSC__CallFrame", "JSC__Identifier", "WTF__StringImpl", "WTF__ExternalStringImpl", "WTF__StringView", "Zig__GlobalObject", "Bun__Readable", "Bun__Writable", "Bun__Path"}; -const size_t aligns[31] = {alignof(JSC::JSObject), alignof(WebCore::DOMURL), alignof(SystemError), alignof(JSC::JSCell), alignof(JSC::JSString), alignof(Inspector::ScriptArguments), alignof(JSC::JSModuleLoader), alignof(JSC::JSModuleRecord), alignof(JSC::JSPromise), alignof(JSC::JSInternalPromise), alignof(JSC::SourceOrigin), alignof(JSC::SourceCode), alignof(JSC::JSFunction), alignof(JSC::JSGlobalObject), alignof(WTF::URL), alignof(WTF::String), alignof(JSC::JSValue), alignof(JSC::PropertyName), alignof(JSC::Exception), alignof(JSC::VM), alignof(JSC::ThrowScope), alignof(JSC::CatchScope), alignof(JSC::CallFrame), alignof(JSC::Identifier), alignof(WTF::StringImpl), alignof(WTF::ExternalStringImpl), alignof(WTF::StringView), alignof(Zig::GlobalObject), alignof(Bun__Readable), alignof(Bun__Writable), alignof(Bun__Path)}; +const size_t aligns[32] = {alignof(JSC::JSObject), alignof(WebCore::DOMURL), alignof(WebCore::FetchHeaders), alignof(SystemError), alignof(JSC::JSCell), alignof(JSC::JSString), alignof(Inspector::ScriptArguments), alignof(JSC::JSModuleLoader), alignof(JSC::JSModuleRecord), alignof(JSC::JSPromise), alignof(JSC::JSInternalPromise), alignof(JSC::SourceOrigin), alignof(JSC::SourceCode), alignof(JSC::JSFunction), alignof(JSC::JSGlobalObject), alignof(WTF::URL), alignof(WTF::String), alignof(JSC::JSValue), alignof(JSC::PropertyName), alignof(JSC::Exception), alignof(JSC::VM), alignof(JSC::ThrowScope), alignof(JSC::CatchScope), alignof(JSC::CallFrame), alignof(JSC::Identifier), alignof(WTF::StringImpl), alignof(WTF::ExternalStringImpl), alignof(WTF::StringView), alignof(Zig::GlobalObject), alignof(Bun__Readable), alignof(Bun__Writable), alignof(Bun__Path)}; diff --git a/src/javascript/jsc/bindings/headers-handwritten.h b/src/javascript/jsc/bindings/headers-handwritten.h index 0c4cc2754..8bfc1a09d 100644 --- a/src/javascript/jsc/bindings/headers-handwritten.h +++ b/src/javascript/jsc/bindings/headers-handwritten.h @@ -175,12 +175,15 @@ typedef struct { uint8_t cell_type; } Bun__ArrayBuffer; -#ifdef __cplusplus - +#ifndef STRING_POINTER +#define STRING_POINTER typedef struct StringPointer { uint32_t off; uint32_t len; } StringPointer; +#endif + +#ifdef __cplusplus extern "C" ZigErrorCode Zig_ErrorCodeParserError; diff --git a/src/javascript/jsc/bindings/headers-replacements.zig b/src/javascript/jsc/bindings/headers-replacements.zig index fd1e7b4e9..c503d3288 100644 --- a/src/javascript/jsc/bindings/headers-replacements.zig +++ b/src/javascript/jsc/bindings/headers-replacements.zig @@ -60,3 +60,5 @@ pub const Bun__Readable = bindings.NodeReadableStream; pub const Bun__Writable = bindings.NodeWritableStream; pub const Bun__ArrayBuffer = bindings.ArrayBuffer; pub const struct_WebCore__DOMURL = bindings.DOMURL; +pub const struct_WebCore__FetchHeaders = bindings.FetchHeaders; +pub const StringPointer = @import("../../../api/schema.zig").Api.StringPointer; diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index d99f24e58..9175274cb 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1648722142 +//-- AUTOGENERATED FILE -- 1648790662 #pragma once #include <stddef.h> @@ -152,9 +152,10 @@ typedef struct JSC__IteratorPrototype JSC__IteratorPrototype; // JSC::IteratorPr typedef Bun__Readable Bun__Readable; typedef bJSC__JSInternalPromise JSC__JSInternalPromise; // JSC::JSInternalPromise typedef Bun__Writable Bun__Writable; +typedef struct JSC__MapIteratorPrototype JSC__MapIteratorPrototype; // JSC::MapIteratorPrototype typedef struct JSC__RegExpPrototype JSC__RegExpPrototype; // JSC::RegExpPrototype typedef bJSC__CallFrame JSC__CallFrame; // JSC::CallFrame -typedef struct JSC__MapIteratorPrototype JSC__MapIteratorPrototype; // JSC::MapIteratorPrototype +typedef struct WebCore__FetchHeaders WebCore__FetchHeaders; // WebCore::FetchHeaders typedef bWTF__StringView WTF__StringView; // WTF::StringView typedef bJSC__ThrowScope JSC__ThrowScope; // JSC::ThrowScope typedef bWTF__StringImpl WTF__StringImpl; // WTF::StringImpl @@ -230,6 +231,7 @@ class JSMicrotaskCallback; } namespace WebCore { class DOMURL; +class FetchHeaders; } namespace Inspector { class ScriptArguments; @@ -287,6 +289,7 @@ using WTF__StringView = WTF::StringView; using WTF__ExternalStringImpl = WTF::ExternalStringImpl; using Zig__JSMicrotaskCallback = Zig::JSMicrotaskCallback; using WebCore__DOMURL = WebCore::DOMURL; +using WebCore__FetchHeaders = WebCore::FetchHeaders; using Inspector__ScriptArguments = Inspector::ScriptArguments; #endif @@ -306,9 +309,27 @@ CPP_DECL JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSG CPP_DECL JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (*ArgFn2)(void* arg0, void* arg1, size_t arg2)); CPP_DECL JSC__JSValue ZigString__toValue(const ZigString* arg0, JSC__JSGlobalObject* arg1); CPP_DECL JSC__JSValue ZigString__toValueGC(const ZigString* arg0, JSC__JSGlobalObject* arg1); -CPP_DECL WebCore__DOMURL* WebCore__DOMURL__cast(JSC__JSValue JSValue0); +CPP_DECL WebCore__DOMURL* WebCore__DOMURL__cast_(JSC__JSValue JSValue0, JSC__VM* arg1); CPP_DECL void WebCore__DOMURL__href_(WebCore__DOMURL* arg0, ZigString* arg1); CPP_DECL void WebCore__DOMURL__pathname_(WebCore__DOMURL* arg0, ZigString* arg1); +CPP_DECL void WebCore__FetchHeaders__append(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2); +CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__cast_(JSC__JSValue JSValue0, JSC__VM* arg1); +CPP_DECL JSC__JSValue WebCore__FetchHeaders__clone(WebCore__FetchHeaders* arg0, JSC__JSGlobalObject* arg1); +CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__cloneThis(WebCore__FetchHeaders* arg0); +CPP_DECL void WebCore__FetchHeaders__copyTo(WebCore__FetchHeaders* arg0, StringPointer* arg1, StringPointer* arg2, unsigned char* arg3); +CPP_DECL void WebCore__FetchHeaders__count(WebCore__FetchHeaders* arg0, uint32_t* arg1, uint32_t* arg2); +CPP_DECL WebCore__FetchHeaders* WebCore__FetchHeaders__createFromJS(JSC__JSGlobalObject* arg0, JSC__JSValue JSValue1); +CPP_DECL JSC__JSValue WebCore__FetchHeaders__createFromPicoHeaders_(JSC__JSGlobalObject* arg0, const void* arg1); +CPP_DECL JSC__JSValue WebCore__FetchHeaders__createFromUWS(JSC__JSGlobalObject* arg0, void* arg1); +CPP_DECL JSC__JSValue WebCore__FetchHeaders__createValue(JSC__JSGlobalObject* arg0, StringPointer* arg1, StringPointer* arg2, const ZigString* arg3, uint32_t arg4); +CPP_DECL void WebCore__FetchHeaders__deref(WebCore__FetchHeaders* arg0); +CPP_DECL void WebCore__FetchHeaders__get_(WebCore__FetchHeaders* arg0, const ZigString* arg1, ZigString* arg2); +CPP_DECL bool WebCore__FetchHeaders__has(WebCore__FetchHeaders* arg0, const ZigString* arg1); +CPP_DECL uint32_t WebCore__FetchHeaders__keyCount(WebCore__FetchHeaders* arg0); +CPP_DECL void WebCore__FetchHeaders__put_(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2); +CPP_DECL void WebCore__FetchHeaders__remove(WebCore__FetchHeaders* arg0, const ZigString* arg1); +CPP_DECL JSC__JSValue WebCore__FetchHeaders__toJS(WebCore__FetchHeaders* arg0, JSC__JSGlobalObject* arg1); +CPP_DECL void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool arg1, void* arg2); CPP_DECL JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0, JSC__JSGlobalObject* arg1); #pragma mark - JSC::JSCell diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index e82ca8e70..2eb6ad46f 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -60,6 +60,8 @@ pub const Bun__Readable = bindings.NodeReadableStream; pub const Bun__Writable = bindings.NodeWritableStream; pub const Bun__ArrayBuffer = bindings.ArrayBuffer; pub const struct_WebCore__DOMURL = bindings.DOMURL; +pub const struct_WebCore__FetchHeaders = bindings.FetchHeaders; +pub const StringPointer = @import("../../../api/schema.zig").Api.StringPointer; // GENERATED CODE - DO NOT MODIFY BY HAND pub const ptrdiff_t = c_long; @@ -106,10 +108,12 @@ pub const WTF__URL = bWTF__URL; pub const JSC__IteratorPrototype = struct_JSC__IteratorPrototype; pub const JSC__JSInternalPromise = bJSC__JSInternalPromise; +pub const JSC__MapIteratorPrototype = struct_JSC__MapIteratorPrototype; + pub const JSC__RegExpPrototype = struct_JSC__RegExpPrototype; pub const JSC__CallFrame = bJSC__CallFrame; -pub const JSC__MapIteratorPrototype = struct_JSC__MapIteratorPrototype; +pub const WebCore__FetchHeaders = struct_WebCore__FetchHeaders; pub const WTF__StringView = bWTF__StringView; pub const JSC__ThrowScope = bJSC__ThrowScope; pub const WTF__StringImpl = bWTF__StringImpl; @@ -147,9 +151,27 @@ pub extern fn ZigString__toExternalValue(arg0: [*c]const ZigString, arg1: [*c]JS pub extern fn ZigString__toExternalValueWithCallback(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject, ArgFn2: ?fn (?*anyopaque, ?*anyopaque, usize) callconv(.C) void) JSC__JSValue; pub extern fn ZigString__toValue(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; pub extern fn ZigString__toValueGC(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; -pub extern fn WebCore__DOMURL__cast(JSValue0: JSC__JSValue) ?*WebCore__DOMURL; +pub extern fn WebCore__DOMURL__cast_(JSValue0: JSC__JSValue, arg1: [*c]JSC__VM) ?*WebCore__DOMURL; pub extern fn WebCore__DOMURL__href_(arg0: ?*WebCore__DOMURL, arg1: [*c]ZigString) void; pub extern fn WebCore__DOMURL__pathname_(arg0: ?*WebCore__DOMURL, arg1: [*c]ZigString) void; +pub extern fn WebCore__FetchHeaders__append(arg0: ?*WebCore__FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString) void; +pub extern fn WebCore__FetchHeaders__cast_(JSValue0: JSC__JSValue, arg1: [*c]JSC__VM) ?*WebCore__FetchHeaders; +pub extern fn WebCore__FetchHeaders__clone(arg0: ?*WebCore__FetchHeaders, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; +pub extern fn WebCore__FetchHeaders__cloneThis(arg0: ?*WebCore__FetchHeaders) ?*WebCore__FetchHeaders; +pub extern fn WebCore__FetchHeaders__copyTo(arg0: ?*WebCore__FetchHeaders, arg1: [*c]StringPointer, arg2: [*c]StringPointer, arg3: [*c]u8) void; +pub extern fn WebCore__FetchHeaders__count(arg0: ?*WebCore__FetchHeaders, arg1: [*c]u32, arg2: [*c]u32) void; +pub extern fn WebCore__FetchHeaders__createFromJS(arg0: [*c]JSC__JSGlobalObject, JSValue1: JSC__JSValue) ?*WebCore__FetchHeaders; +pub extern fn WebCore__FetchHeaders__createFromPicoHeaders_(arg0: [*c]JSC__JSGlobalObject, arg1: ?*const anyopaque) JSC__JSValue; +pub extern fn WebCore__FetchHeaders__createFromUWS(arg0: [*c]JSC__JSGlobalObject, arg1: ?*anyopaque) JSC__JSValue; +pub extern fn WebCore__FetchHeaders__createValue(arg0: [*c]JSC__JSGlobalObject, arg1: [*c]StringPointer, arg2: [*c]StringPointer, arg3: [*c]const ZigString, arg4: u32) JSC__JSValue; +pub extern fn WebCore__FetchHeaders__deref(arg0: ?*WebCore__FetchHeaders) void; +pub extern fn WebCore__FetchHeaders__get_(arg0: ?*WebCore__FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]ZigString) void; +pub extern fn WebCore__FetchHeaders__has(arg0: ?*WebCore__FetchHeaders, arg1: [*c]const ZigString) bool; +pub extern fn WebCore__FetchHeaders__keyCount(arg0: ?*WebCore__FetchHeaders) u32; +pub extern fn WebCore__FetchHeaders__put_(arg0: ?*WebCore__FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString) void; +pub extern fn WebCore__FetchHeaders__remove(arg0: ?*WebCore__FetchHeaders, arg1: [*c]const ZigString) void; +pub extern fn WebCore__FetchHeaders__toJS(arg0: ?*WebCore__FetchHeaders, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; +pub extern fn WebCore__FetchHeaders__toUWSResponse(arg0: ?*WebCore__FetchHeaders, arg1: bool, arg2: ?*anyopaque) void; pub extern fn SystemError__toErrorInstance(arg0: [*c]const SystemError, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSCell__getObject(arg0: [*c]JSC__JSCell) [*c]JSC__JSObject; pub extern fn JSC__JSCell__getString(arg0: [*c]JSC__JSCell, arg1: [*c]JSC__JSGlobalObject) bWTF__String; diff --git a/src/javascript/jsc/bindings/helpers.h b/src/javascript/jsc/bindings/helpers.h index 79c9a7ace..e4ceb5696 100644 --- a/src/javascript/jsc/bindings/helpers.h +++ b/src/javascript/jsc/bindings/helpers.h @@ -125,6 +125,36 @@ static const WTF::String toString(ZigString str) reinterpret_cast<const UChar*>(untag(str.ptr)), str.len)); } +static const WTF::String toString(ZigString str, StringPointer ptr) +{ + if (str.len == 0 || str.ptr == nullptr || ptr.len == 0) { + return WTF::String(); + } + if (UNLIKELY(isTaggedUTF8Ptr(str.ptr))) { + return WTF::String::fromUTF8(&untag(str.ptr)[ptr.off], ptr.len); + } + + return !isTaggedUTF16Ptr(str.ptr) + ? WTF::String(WTF::StringImpl::createWithoutCopying(&untag(str.ptr)[ptr.off], ptr.len)) + : WTF::String(WTF::StringImpl::createWithoutCopying( + &reinterpret_cast<const UChar*>(untag(str.ptr))[ptr.off], ptr.len)); +} + +static const WTF::String toStringCopy(ZigString str, StringPointer ptr) +{ + if (str.len == 0 || str.ptr == nullptr || ptr.len == 0) { + return WTF::String(); + } + if (UNLIKELY(isTaggedUTF8Ptr(str.ptr))) { + return WTF::String::fromUTF8(&untag(str.ptr)[ptr.off], ptr.len); + } + + return !isTaggedUTF16Ptr(str.ptr) + ? WTF::String(WTF::StringImpl::create(&untag(str.ptr)[ptr.off], ptr.len)) + : WTF::String(WTF::StringImpl::create( + &reinterpret_cast<const UChar*>(untag(str.ptr))[ptr.off], ptr.len)); +} + static const WTF::String toStringCopy(ZigString str) { if (str.len == 0 || str.ptr == nullptr) { diff --git a/src/javascript/jsc/bindings/webcore/DOMClientIsoSubspaces.h b/src/javascript/jsc/bindings/webcore/DOMClientIsoSubspaces.h index 3b496536b..ad1b6a32b 100644 --- a/src/javascript/jsc/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/javascript/jsc/bindings/webcore/DOMClientIsoSubspaces.h @@ -101,8 +101,8 @@ public: // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemDirectoryReader; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemEntry; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemFileEntry; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFetchHeaders; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFetchHeadersIterator; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFetchHeaders; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFetchHeadersIterator; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFetchRequest; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFetchResponse; // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForFileSystemDirectoryHandle; diff --git a/src/javascript/jsc/bindings/webcore/DOMIsoSubspaces.h b/src/javascript/jsc/bindings/webcore/DOMIsoSubspaces.h index bb4cc3327..75d91c0a2 100644 --- a/src/javascript/jsc/bindings/webcore/DOMIsoSubspaces.h +++ b/src/javascript/jsc/bindings/webcore/DOMIsoSubspaces.h @@ -89,8 +89,8 @@ public: // std::unique_ptr<IsoSubspace> m_subspaceForFileSystemDirectoryReader; // std::unique_ptr<IsoSubspace> m_subspaceForFileSystemEntry; // std::unique_ptr<IsoSubspace> m_subspaceForFileSystemFileEntry; - // std::unique_ptr<IsoSubspace> m_subspaceForFetchHeaders; - // std::unique_ptr<IsoSubspace> m_subspaceForFetchHeadersIterator; + std::unique_ptr<IsoSubspace> m_subspaceForFetchHeaders; + std::unique_ptr<IsoSubspace> m_subspaceForFetchHeadersIterator; // std::unique_ptr<IsoSubspace> m_subspaceForFetchRequest; // std::unique_ptr<IsoSubspace> m_subspaceForFetchResponse; // std::unique_ptr<IsoSubspace> m_subspaceForFileSystemDirectoryHandle; diff --git a/src/javascript/jsc/bindings/webcore/FetchHeaders.cpp b/src/javascript/jsc/bindings/webcore/FetchHeaders.cpp new file mode 100644 index 000000000..26af08f1a --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/FetchHeaders.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2016 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FetchHeaders.h" + +#include "HTTPParsers.h" + +namespace WebCore { + +// https://fetch.spec.whatwg.org/#concept-headers-remove-privileged-no-cors-request-headers +static void removePrivilegedNoCORSRequestHeaders(HTTPHeaderMap& headers) +{ + headers.remove(HTTPHeaderName::Range); +} + +static ExceptionOr<bool> canWriteHeader(const String& name, const String& value, const String& combinedValue, FetchHeaders::Guard guard) +{ + if (!isValidHTTPToken(name)) + return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; + ASSERT(value.isEmpty() || (!isHTTPSpace(value[0]) && !isHTTPSpace(value[value.length() - 1]))); + if (!isValidHTTPHeaderValue((value))) + return Exception { TypeError, makeString("Header '", name, "' has invalid value: '", value, "'") }; + if (guard == FetchHeaders::Guard::Immutable) + return Exception { TypeError, "Headers object's guard is 'immutable'"_s }; + if (guard == FetchHeaders::Guard::Request && isForbiddenHeaderName(name)) + return false; + if (guard == FetchHeaders::Guard::RequestNoCors && !combinedValue.isEmpty() && !isSimpleHeader(name, combinedValue)) + return false; + if (guard == FetchHeaders::Guard::Response && isForbiddenResponseHeaderName(name)) + return false; + return true; +} + +static ExceptionOr<void> appendToHeaderMap(const String& name, const String& value, HTTPHeaderMap& headers, FetchHeaders::Guard guard) +{ + String normalizedValue = stripLeadingAndTrailingHTTPSpaces(value); + String combinedValue = normalizedValue; + if (headers.contains(name)) + combinedValue = makeString(headers.get(name), ", ", normalizedValue); + auto canWriteResult = canWriteHeader(name, normalizedValue, combinedValue, guard); + if (canWriteResult.hasException()) + return canWriteResult.releaseException(); + if (!canWriteResult.releaseReturnValue()) + return { }; + headers.set(name, combinedValue); + + if (guard == FetchHeaders::Guard::RequestNoCors) + removePrivilegedNoCORSRequestHeaders(headers); + + return { }; +} + +static ExceptionOr<void> appendToHeaderMap(const HTTPHeaderMap::HTTPHeaderMapConstIterator::KeyValue& header, HTTPHeaderMap& headers, FetchHeaders::Guard guard) +{ + String normalizedValue = stripLeadingAndTrailingHTTPSpaces(header.value); + auto canWriteResult = canWriteHeader(header.key, normalizedValue, header.value, guard); + if (canWriteResult.hasException()) + return canWriteResult.releaseException(); + if (!canWriteResult.releaseReturnValue()) + return { }; + if (header.keyAsHTTPHeaderName) + headers.add(header.keyAsHTTPHeaderName.value(), header.value); + else + headers.add(header.key, header.value); + + if (guard == FetchHeaders::Guard::RequestNoCors) + removePrivilegedNoCORSRequestHeaders(headers); + + return { }; +} + +// https://fetch.spec.whatwg.org/#concept-headers-fill +static ExceptionOr<void> fillHeaderMap(HTTPHeaderMap& headers, const FetchHeaders::Init& headersInit, FetchHeaders::Guard guard) +{ + if (std::holds_alternative<Vector<Vector<String>>>(headersInit)) { + auto& sequence = std::get<Vector<Vector<String>>>(headersInit); + for (auto& header : sequence) { + if (header.size() != 2) + return Exception { TypeError, "Header sub-sequence must contain exactly two items"_s }; + auto result = appendToHeaderMap(header[0], header[1], headers, guard); + if (result.hasException()) + return result.releaseException(); + } + } else { + auto& record = std::get<Vector<KeyValuePair<String, String>>>(headersInit); + for (auto& header : record) { + auto result = appendToHeaderMap(header.key, header.value, headers, guard); + if (result.hasException()) + return result.releaseException(); + } + } + + return { }; +} + +ExceptionOr<Ref<FetchHeaders>> FetchHeaders::create(std::optional<Init>&& headersInit) +{ + HTTPHeaderMap headers; + + if (headersInit) { + auto result = fillHeaderMap(headers, *headersInit, Guard::None); + if (result.hasException()) + return result.releaseException(); + } + + return adoptRef(*new FetchHeaders { Guard::None, WTFMove(headers) }); +} + +ExceptionOr<void> FetchHeaders::fill(const Init& headerInit) +{ + return fillHeaderMap(m_headers, headerInit, m_guard); +} + +ExceptionOr<void> FetchHeaders::fill(const FetchHeaders& otherHeaders) +{ + for (auto& header : otherHeaders.m_headers) { + auto result = appendToHeaderMap(header, m_headers, m_guard); + if (result.hasException()) + return result.releaseException(); + } + + return { }; +} + +ExceptionOr<void> FetchHeaders::append(const String& name, const String& value) +{ + return appendToHeaderMap(name, value, m_headers, m_guard); +} + +// https://fetch.spec.whatwg.org/#dom-headers-delete +ExceptionOr<void> FetchHeaders::remove(const String& name) +{ + if (!isValidHTTPToken(name)) + return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; + if (m_guard == FetchHeaders::Guard::Immutable) + return Exception { TypeError, "Headers object's guard is 'immutable'"_s }; + if (m_guard == FetchHeaders::Guard::Request && isForbiddenHeaderName(name)) + return { }; + if (m_guard == FetchHeaders::Guard::RequestNoCors && !isNoCORSSafelistedRequestHeaderName(name) && !isPriviledgedNoCORSRequestHeaderName(name)) + return { }; + if (m_guard == FetchHeaders::Guard::Response && isForbiddenResponseHeaderName(name)) + return { }; + + m_headers.remove(name); + + if (m_guard == FetchHeaders::Guard::RequestNoCors) + removePrivilegedNoCORSRequestHeaders(m_headers); + + return { }; +} + +ExceptionOr<String> FetchHeaders::get(const String& name) const +{ + if (!isValidHTTPToken(name)) + return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; + return m_headers.get(name); +} + +ExceptionOr<bool> FetchHeaders::has(const String& name) const +{ + if (!isValidHTTPToken(name)) + return Exception { TypeError, makeString("Invalid header name: '", name, "'") }; + return m_headers.contains(name); +} + +ExceptionOr<void> FetchHeaders::set(const String& name, const String& value) +{ + String normalizedValue = stripLeadingAndTrailingHTTPSpaces(value); + auto canWriteResult = canWriteHeader(name, normalizedValue, normalizedValue, m_guard); + if (canWriteResult.hasException()) + return canWriteResult.releaseException(); + if (!canWriteResult.releaseReturnValue()) + return { }; + + m_headers.set(name, normalizedValue); + + if (m_guard == FetchHeaders::Guard::RequestNoCors) + removePrivilegedNoCORSRequestHeaders(m_headers); + + return { }; +} + +void FetchHeaders::filterAndFill(const HTTPHeaderMap& headers, Guard guard) +{ + for (auto& header : headers) { + String normalizedValue = stripLeadingAndTrailingHTTPSpaces(header.value); + auto canWriteResult = canWriteHeader(header.key, normalizedValue, header.value, guard); + if (canWriteResult.hasException()) + continue; + if (!canWriteResult.releaseReturnValue()) + continue; + if (header.keyAsHTTPHeaderName) + m_headers.add(header.keyAsHTTPHeaderName.value(), header.value); + else + m_headers.add(header.key, header.value); + } +} + +std::optional<KeyValuePair<String, String>> FetchHeaders::Iterator::next() +{ + while (m_currentIndex < m_keys.size()) { + auto key = m_keys[m_currentIndex++]; + auto value = m_headers->m_headers.get(key); + if (!value.isNull()) + return KeyValuePair<String, String> { WTFMove(key), WTFMove(value) }; + } + return std::nullopt; +} + +FetchHeaders::Iterator::Iterator(FetchHeaders& headers) + : m_headers(headers) +{ + m_keys.reserveInitialCapacity(headers.m_headers.size()); + for (auto& header : headers.m_headers) + m_keys.uncheckedAppend(header.key.convertToASCIILowercase()); + std::sort(m_keys.begin(), m_keys.end(), WTF::codePointCompareLessThan); +} + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/FetchHeaders.h b/src/javascript/jsc/bindings/webcore/FetchHeaders.h new file mode 100644 index 000000000..c6a74880f --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/FetchHeaders.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ExceptionOr.h" +#include "HTTPHeaderMap.h" +#include <variant> +#include <wtf/HashTraits.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class FetchHeaders : public RefCounted<FetchHeaders> { +public: + enum class Guard { + None, + Immutable, + Request, + RequestNoCors, + Response + }; + + using Init = std::variant<Vector<Vector<String>>, Vector<KeyValuePair<String, String>>>; + static ExceptionOr<Ref<FetchHeaders>> create(std::optional<Init>&&); + + static Ref<FetchHeaders> create(Guard guard = Guard::None, HTTPHeaderMap&& headers = {}) { return adoptRef(*new FetchHeaders { guard, WTFMove(headers) }); } + static Ref<FetchHeaders> create(const FetchHeaders& headers) { return adoptRef(*new FetchHeaders { headers }); } + + ExceptionOr<void> append(const String& name, const String& value); + ExceptionOr<void> remove(const String&); + ExceptionOr<String> get(const String&) const; + ExceptionOr<bool> has(const String&) const; + ExceptionOr<void> set(const String& name, const String& value); + + ExceptionOr<void> fill(const Init&); + ExceptionOr<void> fill(const FetchHeaders&); + void filterAndFill(const HTTPHeaderMap&, Guard); + + inline uint32_t size() + { + return m_headers.size(); + } + + String fastGet(HTTPHeaderName name) const { return m_headers.get(name); } + bool fastHas(HTTPHeaderName name) const { return m_headers.contains(name); } + void fastSet(HTTPHeaderName name, const String& value) { m_headers.set(name, value); } + + class Iterator { + public: + explicit Iterator(FetchHeaders&); + std::optional<KeyValuePair<String, String>> next(); + + private: + Ref<FetchHeaders> m_headers; + size_t m_currentIndex { 0 }; + Vector<String> m_keys; + }; + Iterator createIterator() { return Iterator { *this }; } + + void setInternalHeaders(HTTPHeaderMap&& headers) { m_headers = WTFMove(headers); } + const HTTPHeaderMap& internalHeaders() const { return m_headers; } + + void setGuard(Guard); + Guard guard() const { return m_guard; } + +private: + FetchHeaders(Guard, HTTPHeaderMap&&); + explicit FetchHeaders(const FetchHeaders&); + + Guard m_guard; + HTTPHeaderMap m_headers; +}; + +inline FetchHeaders::FetchHeaders(Guard guard, HTTPHeaderMap&& headers) + : m_guard(guard) + , m_headers(WTFMove(headers)) +{ +} + +inline FetchHeaders::FetchHeaders(const FetchHeaders& other) + : RefCounted<FetchHeaders>() + , m_guard(other.m_guard) + , m_headers(other.m_headers) +{ +} + +inline void FetchHeaders::setGuard(Guard guard) +{ + ASSERT(!m_headers.size()); + m_guard = guard; +} + +} // namespace WebCore + +namespace WTF { + +template<> struct EnumTraits<WebCore::FetchHeaders::Guard> { + using values = EnumValues< + WebCore::FetchHeaders::Guard, + WebCore::FetchHeaders::Guard::None, + WebCore::FetchHeaders::Guard::Immutable, + WebCore::FetchHeaders::Guard::Request, + WebCore::FetchHeaders::Guard::RequestNoCors, + WebCore::FetchHeaders::Guard::Response>; +}; + +} diff --git a/src/javascript/jsc/bindings/webcore/FetchHeaders.idl b/src/javascript/jsc/bindings/webcore/FetchHeaders.idl new file mode 100644 index 000000000..daa0ebb8e --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/FetchHeaders.idl @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 Canon Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions + * are required to be met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Canon Inc. nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +typedef (sequence<sequence<ByteString>> or record<ByteString, ByteString>) HeadersInit; + +[ + Exposed=(Window,Worker), + InterfaceName=Headers, +] interface FetchHeaders { + constructor(optional HeadersInit init); + + undefined append(ByteString name, ByteString value); + [ImplementedAs=remove] undefined delete(ByteString name); + ByteString? get(ByteString name); + boolean has(ByteString name); + undefined set(ByteString name, ByteString value); + + iterable<ByteString, ByteString>; +}; diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderField.cpp b/src/javascript/jsc/bindings/webcore/HTTPHeaderField.cpp new file mode 100644 index 000000000..a4d101bdd --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderField.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "HTTPHeaderField.h" + +namespace WebCore { + +namespace RFC7230 { + +bool isTokenCharacter(UChar c) +{ + return isASCIIAlpha(c) || isASCIIDigit(c) + || c == '!' || c == '#' || c == '$' + || c == '%' || c == '&' || c == '\'' + || c == '*' || c == '+' || c == '-' + || c == '.' || c == '^' || c == '_' + || c == '`' || c == '|' || c == '~'; +} + +bool isDelimiter(UChar c) +{ + return c == '(' || c == ')' || c == ',' + || c == '/' || c == ':' || c == ';' + || c == '<' || c == '=' || c == '>' + || c == '?' || c == '@' || c == '[' + || c == '\\' || c == ']' || c == '{' + || c == '}' || c == '"'; +} + +static bool isVisibleCharacter(UChar c) +{ + return isTokenCharacter(c) || isDelimiter(c); +} + +bool isWhitespace(UChar c) +{ + return c == ' ' || c == '\t'; +} + +template<size_t min, size_t max> +static bool isInRange(UChar c) +{ + return c >= min && c <= max; +} + +static bool isOBSText(UChar c) +{ + return isInRange<0x80, 0xFF>(c); +} + +static bool isQuotedTextCharacter(UChar c) +{ + return isWhitespace(c) + || c == 0x21 + || isInRange<0x23, 0x5B>(c) + || isInRange<0x5D, 0x7E>(c) + || isOBSText(c); +} + +bool isQuotedPairSecondOctet(UChar c) +{ + return isWhitespace(c) + || isVisibleCharacter(c) + || isOBSText(c); +} + +bool isCommentText(UChar c) +{ + return isWhitespace(c) + || isInRange<0x21, 0x27>(c) + || isInRange<0x2A, 0x5B>(c) + || isInRange<0x5D, 0x7E>(c) + || isOBSText(c); +} + +static bool isValidName(StringView name) +{ + if (!name.length()) + return false; + for (size_t i = 0; i < name.length(); ++i) { + if (!isTokenCharacter(name[i])) + return false; + } + return true; +} + +static bool isValidValue(StringView value) +{ + enum class State { + OptionalWhitespace, + Token, + QuotedString, + Comment, + }; + State state = State::OptionalWhitespace; + size_t commentDepth = 0; + bool hadNonWhitespace = false; + + for (size_t i = 0; i < value.length(); ++i) { + UChar c = value[i]; + switch (state) { + case State::OptionalWhitespace: + if (isWhitespace(c)) + continue; + hadNonWhitespace = true; + if (isTokenCharacter(c)) { + state = State::Token; + continue; + } + if (c == '"') { + state = State::QuotedString; + continue; + } + if (c == '(') { + ASSERT(!commentDepth); + ++commentDepth; + state = State::Comment; + continue; + } + return false; + + case State::Token: + if (isTokenCharacter(c)) + continue; + state = State::OptionalWhitespace; + continue; + case State::QuotedString: + if (c == '"') { + state = State::OptionalWhitespace; + continue; + } + if (c == '\\') { + ++i; + if (i == value.length()) + return false; + if (!isQuotedPairSecondOctet(value[i])) + return false; + continue; + } + if (!isQuotedTextCharacter(c)) + return false; + continue; + case State::Comment: + if (c == '(') { + ++commentDepth; + continue; + } + if (c == ')') { + --commentDepth; + if (!commentDepth) + state = State::OptionalWhitespace; + continue; + } + if (c == '\\') { + ++i; + if (i == value.length()) + return false; + if (!isQuotedPairSecondOctet(value[i])) + return false; + continue; + } + if (!isCommentText(c)) + return false; + continue; + } + } + + switch (state) { + case State::OptionalWhitespace: + case State::Token: + return hadNonWhitespace; + case State::QuotedString: + case State::Comment: + // Unclosed comments or quotes are invalid values. + break; + } + return false; +} + +} // namespace RFC7230 + +std::optional<HTTPHeaderField> HTTPHeaderField::create(String&& unparsedName, String&& unparsedValue) +{ + StringView strippedName = StringView(unparsedName).stripLeadingAndTrailingMatchedCharacters(RFC7230::isWhitespace); + StringView strippedValue = StringView(unparsedValue).stripLeadingAndTrailingMatchedCharacters(RFC7230::isWhitespace); + if (!RFC7230::isValidName(strippedName) || !RFC7230::isValidValue(strippedValue)) + return std::nullopt; + + String name = strippedName.length() == unparsedName.length() ? WTFMove(unparsedName) : strippedName.toString(); + String value = strippedValue.length() == unparsedValue.length() ? WTFMove(unparsedValue) : strippedValue.toString(); + return {{ WTFMove(name), WTFMove(value) }}; +} + +} diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderField.h b/src/javascript/jsc/bindings/webcore/HTTPHeaderField.h new file mode 100644 index 000000000..c5fbc1b43 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderField.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class WEBCORE_EXPORT HTTPHeaderField { +public: + static std::optional<HTTPHeaderField> create(String&& name, String&& value); + + const String& name() const { return m_name; } + const String& value() const { return m_value; } + + template<class Encoder> void encode(Encoder&) const; + template<class Decoder> static std::optional<HTTPHeaderField> decode(Decoder&); + +private: + HTTPHeaderField(String&& name, String&& value) + : m_name(WTFMove(name)) + , m_value(WTFMove(value)) + { } + String m_name; + String m_value; +}; + +template<class Encoder> +void HTTPHeaderField::encode(Encoder& encoder) const +{ + encoder << m_name; + encoder << m_value; +} + +template<class Decoder> +std::optional<HTTPHeaderField> HTTPHeaderField::decode(Decoder& decoder) +{ + std::optional<String> name; + decoder >> name; + if (!name) + return std::nullopt; + + std::optional<String> value; + decoder >> value; + if (!value) + return std::nullopt; + + return {{ WTFMove(*name), WTFMove(*value) }}; +} + +namespace RFC7230 { +bool isTokenCharacter(UChar); +bool isWhitespace(UChar); +bool isCommentText(UChar); +bool isQuotedPairSecondOctet(UChar); +bool isDelimiter(UChar); +} // namespace RFC7230 + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderMap.cpp b/src/javascript/jsc/bindings/webcore/HTTPHeaderMap.cpp new file mode 100644 index 000000000..bd516cf6c --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderMap.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2022 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "HTTPHeaderMap.h" + +#include <utility> +#include <wtf/CrossThreadCopier.h> +#include <wtf/text/StringView.h> + +namespace WebCore { + +HTTPHeaderMap::HTTPHeaderMap() +{ +} + +HTTPHeaderMap HTTPHeaderMap::isolatedCopy() const & +{ + HTTPHeaderMap map; + map.m_commonHeaders = crossThreadCopy(m_commonHeaders); + map.m_uncommonHeaders = crossThreadCopy(m_uncommonHeaders); + return map; +} + +HTTPHeaderMap HTTPHeaderMap::isolatedCopy() && +{ + HTTPHeaderMap map; + map.m_commonHeaders = crossThreadCopy(WTFMove(m_commonHeaders)); + map.m_uncommonHeaders = crossThreadCopy(WTFMove(m_uncommonHeaders)); + return map; +} + +String HTTPHeaderMap::get(const String& name) const +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) + return get(headerName); + + return getUncommonHeader(name); +} + +String HTTPHeaderMap::getUncommonHeader(const String& name) const +{ + auto index = m_uncommonHeaders.findIf([&](auto& header) { + return equalIgnoringASCIICase(header.key, name); + }); + return index != notFound ? m_uncommonHeaders[index].value : String(); +} + +#if USE(CF) + +void HTTPHeaderMap::set(CFStringRef name, const String& value) +{ + // Fast path: avoid constructing a temporary String in the common header case. + if (auto* nameCharacters = CFStringGetCStringPtr(name, kCFStringEncodingASCII)) { + unsigned length = CFStringGetLength(name); + HTTPHeaderName headerName; + if (findHTTPHeaderName(StringView(nameCharacters, length), headerName)) + set(headerName, value); + else + setUncommonHeader(String(nameCharacters, length), value); + + return; + } + + set(String(name), value); +} + +#endif // USE(CF) + +void HTTPHeaderMap::set(const String& name, const String& value) +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) { + set(headerName, value); + return; + } + + setUncommonHeader(name, value); +} + +void HTTPHeaderMap::setUncommonHeader(const String& name, const String& value) +{ + auto index = m_uncommonHeaders.findIf([&](auto& header) { + return equalIgnoringASCIICase(header.key, name); + }); + if (index == notFound) + m_uncommonHeaders.append(UncommonHeader { name, value }); + else + m_uncommonHeaders[index].value = value; +} + +void HTTPHeaderMap::add(const String& name, const String& value) +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) { + add(headerName, value); + return; + } + auto index = m_uncommonHeaders.findIf([&](auto& header) { + return equalIgnoringASCIICase(header.key, name); + }); + if (index == notFound) + m_uncommonHeaders.append(UncommonHeader { name, value }); + else + m_uncommonHeaders[index].value = makeString(m_uncommonHeaders[index].value, ", ", value); +} + +void HTTPHeaderMap::append(const String& name, const String& value) +{ + ASSERT(!contains(name)); + + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) + m_commonHeaders.append(CommonHeader { headerName, value }); + else + m_uncommonHeaders.append(UncommonHeader { name, value }); +} + +bool HTTPHeaderMap::addIfNotPresent(HTTPHeaderName headerName, const String& value) +{ + if (contains(headerName)) + return false; + + m_commonHeaders.append(CommonHeader { headerName, value }); + return true; +} + +bool HTTPHeaderMap::contains(const String& name) const +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) + return contains(headerName); + + return m_uncommonHeaders.findIf([&](auto& header) { + return equalIgnoringASCIICase(header.key, name); + }) != notFound; +} + +bool HTTPHeaderMap::remove(const String& name) +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) + return remove(headerName); + + return m_uncommonHeaders.removeFirstMatching([&](auto& header) { + return equalIgnoringASCIICase(header.key, name); + }); +} + +String HTTPHeaderMap::get(HTTPHeaderName name) const +{ + auto index = m_commonHeaders.findIf([&](auto& header) { + return header.key == name; + }); + return index != notFound ? m_commonHeaders[index].value : String(); +} + +void HTTPHeaderMap::set(HTTPHeaderName name, const String& value) +{ + auto index = m_commonHeaders.findIf([&](auto& header) { + return header.key == name; + }); + if (index == notFound) + m_commonHeaders.append(CommonHeader { name, value }); + else + m_commonHeaders[index].value = value; +} + +bool HTTPHeaderMap::contains(HTTPHeaderName name) const +{ + return m_commonHeaders.findIf([&](auto& header) { + return header.key == name; + }) != notFound; +} + +bool HTTPHeaderMap::remove(HTTPHeaderName name) +{ + return m_commonHeaders.removeFirstMatching([&](auto& header) { + return header.key == name; + }); +} + +void HTTPHeaderMap::add(HTTPHeaderName name, const String& value) +{ + auto index = m_commonHeaders.findIf([&](auto& header) { + return header.key == name; + }); + if (index != notFound) + m_commonHeaders[index].value = makeString(m_commonHeaders[index].value, ", ", value); + else + m_commonHeaders.append(CommonHeader { name, value }); +} + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderMap.h b/src/javascript/jsc/bindings/webcore/HTTPHeaderMap.h new file mode 100644 index 000000000..1fe19d311 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderMap.h @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2006 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "HTTPHeaderNames.h" +#include <utility> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +// FIXME: Not every header fits into a map. Notably, multiple Set-Cookie header fields are needed to set multiple cookies. + +class HTTPHeaderMap { +public: + struct CommonHeader { + HTTPHeaderName key; + String value; + + CommonHeader isolatedCopy() const & { return { key , value.isolatedCopy() }; } + CommonHeader isolatedCopy() && { return { key , WTFMove(value).isolatedCopy() }; } + template <class Encoder> void encode(Encoder&) const; + template <class Decoder> static std::optional<CommonHeader> decode(Decoder&); + + bool operator==(const CommonHeader& other) const { return key == other.key && value == other.value; } + }; + + struct UncommonHeader { + String key; + String value; + + UncommonHeader isolatedCopy() const & { return { key.isolatedCopy() , value.isolatedCopy() }; } + UncommonHeader isolatedCopy() && { return { WTFMove(key).isolatedCopy() , WTFMove(value).isolatedCopy() }; } + template <class Encoder> void encode(Encoder&) const; + template <class Decoder> static std::optional<UncommonHeader> decode(Decoder&); + + bool operator==(const UncommonHeader& other) const { return key == other.key && value == other.value; } + }; + + typedef Vector<CommonHeader, 0, CrashOnOverflow, 6> CommonHeadersVector; + typedef Vector<UncommonHeader, 0, CrashOnOverflow, 0> UncommonHeadersVector; + + class HTTPHeaderMapConstIterator { + public: + HTTPHeaderMapConstIterator(const HTTPHeaderMap& table, CommonHeadersVector::const_iterator commonHeadersIt, UncommonHeadersVector::const_iterator uncommonHeadersIt) + : m_table(table) + , m_commonHeadersIt(commonHeadersIt) + , m_uncommonHeadersIt(uncommonHeadersIt) + { + if (!updateKeyValue(m_commonHeadersIt)) + updateKeyValue(m_uncommonHeadersIt); + } + + struct KeyValue { + String key; + std::optional<HTTPHeaderName> keyAsHTTPHeaderName; + String value; + }; + + const KeyValue* get() const + { + ASSERT(*this != m_table.end()); + return &m_keyValue; + } + const KeyValue& operator*() const { return *get(); } + const KeyValue* operator->() const { return get(); } + + HTTPHeaderMapConstIterator& operator++() + { + if (m_commonHeadersIt != m_table.m_commonHeaders.end()) { + if (updateKeyValue(++m_commonHeadersIt)) + return *this; + } else + ++m_uncommonHeadersIt; + + updateKeyValue(m_uncommonHeadersIt); + return *this; + } + + bool operator!=(const HTTPHeaderMapConstIterator& other) const { return !(*this == other); } + bool operator==(const HTTPHeaderMapConstIterator& other) const + { + return m_commonHeadersIt == other.m_commonHeadersIt && m_uncommonHeadersIt == other.m_uncommonHeadersIt; + } + + private: + bool updateKeyValue(CommonHeadersVector::const_iterator it) + { + if (it == m_table.commonHeaders().end()) + return false; + m_keyValue.key = httpHeaderNameString(it->key).toStringWithoutCopying(); + m_keyValue.keyAsHTTPHeaderName = it->key; + m_keyValue.value = it->value; + return true; + } + bool updateKeyValue(UncommonHeadersVector::const_iterator it) + { + if (it == m_table.uncommonHeaders().end()) + return false; + m_keyValue.key = it->key; + m_keyValue.keyAsHTTPHeaderName = std::nullopt; + m_keyValue.value = it->value; + return true; + } + + const HTTPHeaderMap& m_table; + CommonHeadersVector::const_iterator m_commonHeadersIt; + UncommonHeadersVector::const_iterator m_uncommonHeadersIt; + KeyValue m_keyValue; + }; + typedef HTTPHeaderMapConstIterator const_iterator; + + WEBCORE_EXPORT HTTPHeaderMap(); + + // Gets a copy of the data suitable for passing to another thread. + WEBCORE_EXPORT HTTPHeaderMap isolatedCopy() const &; + WEBCORE_EXPORT HTTPHeaderMap isolatedCopy() &&; + + bool isEmpty() const { return m_commonHeaders.isEmpty() && m_uncommonHeaders.isEmpty(); } + int size() const { return m_commonHeaders.size() + m_uncommonHeaders.size(); } + + void clear() + { + m_commonHeaders.clear(); + m_uncommonHeaders.clear(); + } + + void shrinkToFit() + { + m_commonHeaders.shrinkToFit(); + m_uncommonHeaders.shrinkToFit(); + } + + WEBCORE_EXPORT String get(const String& name) const; + WEBCORE_EXPORT void set(const String& name, const String& value); + WEBCORE_EXPORT void add(const String& name, const String& value); + WEBCORE_EXPORT void append(const String& name, const String& value); + WEBCORE_EXPORT bool contains(const String&) const; + WEBCORE_EXPORT bool remove(const String&); + +#if USE(CF) + void set(CFStringRef name, const String& value); +#ifdef __OBJC__ + void set(NSString *name, const String& value) { set((__bridge CFStringRef)name, value); } +#endif +#endif + + WEBCORE_EXPORT String get(HTTPHeaderName) const; + void set(HTTPHeaderName, const String& value); + void add(HTTPHeaderName, const String& value); + bool addIfNotPresent(HTTPHeaderName, const String&); + WEBCORE_EXPORT bool contains(HTTPHeaderName) const; + WEBCORE_EXPORT bool remove(HTTPHeaderName); + + // Instead of passing a string literal to any of these functions, just use a HTTPHeaderName instead. + template<size_t length> String get(const char (&)[length]) const = delete; + template<size_t length> void set(const char (&)[length], const String&) = delete; + template<size_t length> bool contains(const char (&)[length]) = delete; + template<size_t length> bool remove(const char (&)[length]) = delete; + + const CommonHeadersVector& commonHeaders() const { return m_commonHeaders; } + const UncommonHeadersVector& uncommonHeaders() const { return m_uncommonHeaders; } + CommonHeadersVector& commonHeaders() { return m_commonHeaders; } + UncommonHeadersVector& uncommonHeaders() { return m_uncommonHeaders; } + + const_iterator begin() const { return const_iterator(*this, m_commonHeaders.begin(), m_uncommonHeaders.begin()); } + const_iterator end() const { return const_iterator(*this, m_commonHeaders.end(), m_uncommonHeaders.end()); } + + friend bool operator==(const HTTPHeaderMap& a, const HTTPHeaderMap& b) + { + if (a.m_commonHeaders.size() != b.m_commonHeaders.size() || a.m_uncommonHeaders.size() != b.m_uncommonHeaders.size()) + return false; + for (auto& commonHeader : a.m_commonHeaders) { + if (b.get(commonHeader.key) != commonHeader.value) + return false; + } + for (auto& uncommonHeader : a.m_uncommonHeaders) { + if (b.getUncommonHeader(uncommonHeader.key) != uncommonHeader.value) + return false; + } + return true; + } + + friend bool operator!=(const HTTPHeaderMap& a, const HTTPHeaderMap& b) + { + return !(a == b); + } + + template <class Encoder> void encode(Encoder&) const; + template <class Decoder> static WARN_UNUSED_RETURN bool decode(Decoder&, HTTPHeaderMap&); + +private: + void setUncommonHeader(const String& name, const String& value); + WEBCORE_EXPORT String getUncommonHeader(const String& name) const; + + CommonHeadersVector m_commonHeaders; + UncommonHeadersVector m_uncommonHeaders; +}; + +template <class Encoder> +void HTTPHeaderMap::CommonHeader::encode(Encoder& encoder) const +{ + encoder << key; + encoder << value; +} + +template <class Decoder> +auto HTTPHeaderMap::CommonHeader::decode(Decoder& decoder) -> std::optional<CommonHeader> +{ + HTTPHeaderName name; + if (!decoder.decode(name)) + return std::nullopt; + String value; + if (!decoder.decode(value)) + return std::nullopt; + + return CommonHeader { name, WTFMove(value) }; +} + +template <class Encoder> +void HTTPHeaderMap::UncommonHeader::encode(Encoder& encoder) const +{ + encoder << key; + encoder << value; +} + +template <class Decoder> +auto HTTPHeaderMap::UncommonHeader::decode(Decoder& decoder) -> std::optional<UncommonHeader> +{ + String name; + if (!decoder.decode(name)) + return std::nullopt; + String value; + if (!decoder.decode(value)) + return std::nullopt; + + return UncommonHeader { WTFMove(name), WTFMove(value) }; +} + +template <class Encoder> +void HTTPHeaderMap::encode(Encoder& encoder) const +{ + encoder << m_commonHeaders; + encoder << m_uncommonHeaders; +} + +template <class Decoder> +bool HTTPHeaderMap::decode(Decoder& decoder, HTTPHeaderMap& headerMap) +{ + if (!decoder.decode(headerMap.m_commonHeaders)) + return false; + + if (!decoder.decode(headerMap.m_uncommonHeaders)) + return false; + + return true; +} + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.cpp b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.cpp new file mode 100644 index 000000000..2846f15e5 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.cpp @@ -0,0 +1,718 @@ +/* C++ code produced by gperf version 3.0.3 */ +/* Command-line: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/gperf --key-positions='*' -D -n -s 2 --output-file=HTTPHeaderNames.cpp HTTPHeaderNames.gperf */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>." +#endif + +#line 2 "HTTPHeaderNames.gperf" + +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/// This file is generated by create-http-header-name-table, do not edit. + +#include "config.h" +#include "HTTPHeaderNames.h" + +#include <wtf/text/StringView.h> + +IGNORE_WARNINGS_BEGIN("implicit-fallthrough") + +// Older versions of gperf like to use the `register` keyword. +#define register + +namespace WebCore { + +static const struct HeaderNameString { + const char* const name; + unsigned length; +} headerNameStrings[] = { + { "Accept", 6 }, + { "Accept-Charset", 14 }, + { "Accept-Encoding", 15 }, + { "Accept-Language", 15 }, + { "Accept-Ranges", 13 }, + { "Access-Control-Allow-Credentials", 32 }, + { "Access-Control-Allow-Headers", 28 }, + { "Access-Control-Allow-Methods", 28 }, + { "Access-Control-Allow-Origin", 27 }, + { "Access-Control-Expose-Headers", 29 }, + { "Access-Control-Max-Age", 22 }, + { "Access-Control-Request-Headers", 30 }, + { "Access-Control-Request-Method", 29 }, + { "Age", 3 }, + { "Authorization", 13 }, + { "Cache-Control", 13 }, + { "Connection", 10 }, + { "Content-Disposition", 19 }, + { "Content-Encoding", 16 }, + { "Content-Language", 16 }, + { "Content-Length", 14 }, + { "Content-Location", 16 }, + { "Content-Range", 13 }, + { "Content-Security-Policy", 23 }, + { "Content-Security-Policy-Report-Only", 35 }, + { "Content-Type", 12 }, + { "Cookie", 6 }, + { "Cookie2", 7 }, + { "Cross-Origin-Embedder-Policy", 28 }, + { "Cross-Origin-Embedder-Policy-Report-Only", 40 }, + { "Cross-Origin-Opener-Policy", 26 }, + { "Cross-Origin-Opener-Policy-Report-Only", 38 }, + { "Cross-Origin-Resource-Policy", 28 }, + { "DNT", 3 }, + { "Date", 4 }, + { "Default-Style", 13 }, + { "ETag", 4 }, + { "Expect", 6 }, + { "Expires", 7 }, + { "Host", 4 }, + { "Icy-MetaInt", 11 }, + { "Icy-Metadata", 12 }, + { "If-Match", 8 }, + { "If-Modified-Since", 17 }, + { "If-None-Match", 13 }, + { "If-Range", 8 }, + { "If-Unmodified-Since", 19 }, + { "Keep-Alive", 10 }, + { "Last-Event-ID", 13 }, + { "Last-Modified", 13 }, + { "Link", 4 }, + { "Location", 8 }, + { "Origin", 6 }, + { "Ping-From", 9 }, + { "Ping-To", 7 }, + { "Pragma", 6 }, + { "Proxy-Authorization", 19 }, + { "Purpose", 7 }, + { "Range", 5 }, + { "Referer", 7 }, + { "Referrer-Policy", 15 }, + { "Refresh", 7 }, + { "Report-To", 9 }, + { "Sec-Fetch-Dest", 14 }, + { "Sec-Fetch-Mode", 14 }, + { "Sec-WebSocket-Accept", 20 }, + { "Sec-WebSocket-Extensions", 24 }, + { "Sec-WebSocket-Key", 17 }, + { "Sec-WebSocket-Protocol", 22 }, + { "Sec-WebSocket-Version", 21 }, + { "Server-Timing", 13 }, + { "Service-Worker", 14 }, + { "Service-Worker-Allowed", 22 }, + { "Service-Worker-Navigation-Preload", 33 }, + { "Set-Cookie", 10 }, + { "Set-Cookie2", 11 }, + { "SourceMap", 9 }, + { "TE", 2 }, + { "Timing-Allow-Origin", 19 }, + { "Trailer", 7 }, + { "Transfer-Encoding", 17 }, + { "Upgrade", 7 }, + { "Upgrade-Insecure-Requests", 25 }, + { "User-Agent", 10 }, + { "Vary", 4 }, + { "Via", 3 }, + { "X-Content-Type-Options", 22 }, + { "X-DNS-Prefetch-Control", 22 }, + { "X-Frame-Options", 15 }, + { "X-SourceMap", 11 }, + { "X-Temp-Tablet", 13 }, + { "X-XSS-Protection", 16 }, +}; + + +#line 149 "HTTPHeaderNames.gperf" +struct HeaderNameHashEntry { + const char* name; + HTTPHeaderName headerName; +}; +enum + { + TOTAL_KEYWORDS = 92, + MIN_WORD_LENGTH = 2, + MAX_WORD_LENGTH = 40, + MIN_HASH_VALUE = 5, + MAX_HASH_VALUE = 815 + }; + +/* maximum key range = 811, duplicates = 0 */ + +#ifndef GPERF_DOWNCASE +#define GPERF_DOWNCASE 1 +static unsigned char gperf_downcase[256] = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255 + }; +#endif + +#ifndef GPERF_CASE_STRNCMP +#define GPERF_CASE_STRNCMP 1 +static int +gperf_case_strncmp (register const char *s1, register const char *s2, register unsigned int n) +{ + for (; n > 0;) + { + unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; + unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; + if (c1 != 0 && c1 == c2) + { + n--; + continue; + } + return (int)c1 - (int)c2; + } + return 0; +} +#endif + +class HTTPHeaderNamesHash +{ +private: + static inline unsigned int header_name_hash_function (const char *str, unsigned int len); +public: + static const struct HeaderNameHashEntry *findHeaderNameImpl (const char *str, unsigned int len); +}; + +inline unsigned int +HTTPHeaderNamesHash::header_name_hash_function (register const char *str, register unsigned int len) +{ + static const unsigned short asso_values[] = + { + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 0, 816, 816, 816, 816, + 5, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 0, 40, 0, 115, 0, + 120, 10, 155, 5, 816, 4, 45, 155, 5, 0, + 30, 0, 5, 60, 5, 5, 135, 55, 50, 15, + 60, 816, 816, 816, 816, 816, 816, 0, 40, 0, + 115, 0, 120, 10, 155, 5, 816, 4, 45, 155, + 5, 0, 30, 0, 5, 60, 5, 5, 135, 55, + 50, 15, 60, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816, 816, 816, 816, 816, + 816, 816, 816, 816, 816, 816 + }; + register unsigned int hval = 0; + + switch (len) + { + default: + hval += asso_values[(unsigned char)str[39]]; + /*FALLTHROUGH*/ + case 39: + hval += asso_values[(unsigned char)str[38]]; + /*FALLTHROUGH*/ + case 38: + hval += asso_values[(unsigned char)str[37]]; + /*FALLTHROUGH*/ + case 37: + hval += asso_values[(unsigned char)str[36]]; + /*FALLTHROUGH*/ + case 36: + hval += asso_values[(unsigned char)str[35]]; + /*FALLTHROUGH*/ + case 35: + hval += asso_values[(unsigned char)str[34]]; + /*FALLTHROUGH*/ + case 34: + hval += asso_values[(unsigned char)str[33]]; + /*FALLTHROUGH*/ + case 33: + hval += asso_values[(unsigned char)str[32]]; + /*FALLTHROUGH*/ + case 32: + hval += asso_values[(unsigned char)str[31]]; + /*FALLTHROUGH*/ + case 31: + hval += asso_values[(unsigned char)str[30]]; + /*FALLTHROUGH*/ + case 30: + hval += asso_values[(unsigned char)str[29]]; + /*FALLTHROUGH*/ + case 29: + hval += asso_values[(unsigned char)str[28]]; + /*FALLTHROUGH*/ + case 28: + hval += asso_values[(unsigned char)str[27]]; + /*FALLTHROUGH*/ + case 27: + hval += asso_values[(unsigned char)str[26]]; + /*FALLTHROUGH*/ + case 26: + hval += asso_values[(unsigned char)str[25]]; + /*FALLTHROUGH*/ + case 25: + hval += asso_values[(unsigned char)str[24]]; + /*FALLTHROUGH*/ + case 24: + hval += asso_values[(unsigned char)str[23]]; + /*FALLTHROUGH*/ + case 23: + hval += asso_values[(unsigned char)str[22]]; + /*FALLTHROUGH*/ + case 22: + hval += asso_values[(unsigned char)str[21]]; + /*FALLTHROUGH*/ + case 21: + hval += asso_values[(unsigned char)str[20]]; + /*FALLTHROUGH*/ + case 20: + hval += asso_values[(unsigned char)str[19]]; + /*FALLTHROUGH*/ + case 19: + hval += asso_values[(unsigned char)str[18]]; + /*FALLTHROUGH*/ + case 18: + hval += asso_values[(unsigned char)str[17]]; + /*FALLTHROUGH*/ + case 17: + hval += asso_values[(unsigned char)str[16]]; + /*FALLTHROUGH*/ + case 16: + hval += asso_values[(unsigned char)str[15]]; + /*FALLTHROUGH*/ + case 15: + hval += asso_values[(unsigned char)str[14]]; + /*FALLTHROUGH*/ + case 14: + hval += asso_values[(unsigned char)str[13]]; + /*FALLTHROUGH*/ + case 13: + hval += asso_values[(unsigned char)str[12]]; + /*FALLTHROUGH*/ + case 12: + hval += asso_values[(unsigned char)str[11]]; + /*FALLTHROUGH*/ + case 11: + hval += asso_values[(unsigned char)str[10]]; + /*FALLTHROUGH*/ + case 10: + hval += asso_values[(unsigned char)str[9]]; + /*FALLTHROUGH*/ + case 9: + hval += asso_values[(unsigned char)str[8]]; + /*FALLTHROUGH*/ + case 8: + hval += asso_values[(unsigned char)str[7]]; + /*FALLTHROUGH*/ + case 7: + hval += asso_values[(unsigned char)str[6]]; + /*FALLTHROUGH*/ + case 6: + hval += asso_values[(unsigned char)str[5]]; + /*FALLTHROUGH*/ + case 5: + hval += asso_values[(unsigned char)str[4]]; + /*FALLTHROUGH*/ + case 4: + hval += asso_values[(unsigned char)str[3]]; + /*FALLTHROUGH*/ + case 3: + hval += asso_values[(unsigned char)str[2]]; + /*FALLTHROUGH*/ + case 2: + hval += asso_values[(unsigned char)str[1]]; + /*FALLTHROUGH*/ + case 1: + hval += asso_values[(unsigned char)str[0]]; + break; + } + return hval; +} + +static const struct HeaderNameHashEntry header_name_wordlist[] = + { +#line 236 "HTTPHeaderNames.gperf" + {"TE", HTTPHeaderName::TE}, +#line 185 "HTTPHeaderNames.gperf" + {"Cookie", HTTPHeaderName::Cookie}, +#line 172 "HTTPHeaderNames.gperf" + {"Age", HTTPHeaderName::Age}, +#line 186 "HTTPHeaderNames.gperf" + {"Cookie2", HTTPHeaderName::Cookie2}, +#line 195 "HTTPHeaderNames.gperf" + {"ETag", HTTPHeaderName::ETag}, +#line 217 "HTTPHeaderNames.gperf" + {"Range", HTTPHeaderName::Range}, +#line 175 "HTTPHeaderNames.gperf" + {"Connection", HTTPHeaderName::Connection}, +#line 211 "HTTPHeaderNames.gperf" + {"Origin", HTTPHeaderName::Origin}, +#line 159 "HTTPHeaderNames.gperf" + {"Accept", HTTPHeaderName::Accept}, +#line 181 "HTTPHeaderNames.gperf" + {"Content-Range", HTTPHeaderName::ContentRange}, +#line 221 "HTTPHeaderNames.gperf" + {"Report-To", HTTPHeaderName::ReportTo}, +#line 213 "HTTPHeaderNames.gperf" + {"Ping-To", HTTPHeaderName::PingTo}, +#line 209 "HTTPHeaderNames.gperf" + {"Link", HTTPHeaderName::Link}, +#line 210 "HTTPHeaderNames.gperf" + {"Location", HTTPHeaderName::Location}, +#line 238 "HTTPHeaderNames.gperf" + {"Trailer", HTTPHeaderName::Trailer}, +#line 184 "HTTPHeaderNames.gperf" + {"Content-Type", HTTPHeaderName::ContentType}, +#line 233 "HTTPHeaderNames.gperf" + {"Set-Cookie", HTTPHeaderName::SetCookie}, +#line 234 "HTTPHeaderNames.gperf" + {"Set-Cookie2", HTTPHeaderName::SetCookie2}, +#line 180 "HTTPHeaderNames.gperf" + {"Content-Location", HTTPHeaderName::ContentLocation}, +#line 196 "HTTPHeaderNames.gperf" + {"Expect", HTTPHeaderName::Expect}, +#line 242 "HTTPHeaderNames.gperf" + {"User-Agent", HTTPHeaderName::UserAgent}, +#line 178 "HTTPHeaderNames.gperf" + {"Content-Language", HTTPHeaderName::ContentLanguage}, +#line 162 "HTTPHeaderNames.gperf" + {"Accept-Language", HTTPHeaderName::AcceptLanguage}, +#line 163 "HTTPHeaderNames.gperf" + {"Accept-Ranges", HTTPHeaderName::AcceptRanges}, +#line 193 "HTTPHeaderNames.gperf" + {"Date", HTTPHeaderName::Date}, +#line 192 "HTTPHeaderNames.gperf" + {"DNT", HTTPHeaderName::DNT}, +#line 216 "HTTPHeaderNames.gperf" + {"Purpose", HTTPHeaderName::Purpose}, +#line 218 "HTTPHeaderNames.gperf" + {"Referer", HTTPHeaderName::Referer}, +#line 244 "HTTPHeaderNames.gperf" + {"Via", HTTPHeaderName::Via}, +#line 204 "HTTPHeaderNames.gperf" + {"If-Range", HTTPHeaderName::IfRange}, +#line 197 "HTTPHeaderNames.gperf" + {"Expires", HTTPHeaderName::Expires}, +#line 243 "HTTPHeaderNames.gperf" + {"Vary", HTTPHeaderName::Vary}, +#line 177 "HTTPHeaderNames.gperf" + {"Content-Encoding", HTTPHeaderName::ContentEncoding}, +#line 240 "HTTPHeaderNames.gperf" + {"Upgrade", HTTPHeaderName::Upgrade}, +#line 161 "HTTPHeaderNames.gperf" + {"Accept-Encoding", HTTPHeaderName::AcceptEncoding}, +#line 199 "HTTPHeaderNames.gperf" + {"Icy-MetaInt", HTTPHeaderName::IcyMetaInt}, +#line 214 "HTTPHeaderNames.gperf" + {"Pragma", HTTPHeaderName::Pragma}, +#line 182 "HTTPHeaderNames.gperf" + {"Content-Security-Policy", HTTPHeaderName::ContentSecurityPolicy}, +#line 174 "HTTPHeaderNames.gperf" + {"Cache-Control", HTTPHeaderName::CacheControl}, +#line 206 "HTTPHeaderNames.gperf" + {"Keep-Alive", HTTPHeaderName::KeepAlive}, +#line 198 "HTTPHeaderNames.gperf" + {"Host", HTTPHeaderName::Host}, +#line 245 "HTTPHeaderNames.gperf" + {"X-Content-Type-Options", HTTPHeaderName::XContentTypeOptions}, +#line 219 "HTTPHeaderNames.gperf" + {"Referrer-Policy", HTTPHeaderName::ReferrerPolicy}, +#line 179 "HTTPHeaderNames.gperf" + {"Content-Length", HTTPHeaderName::ContentLength}, +#line 226 "HTTPHeaderNames.gperf" + {"Sec-WebSocket-Key", HTTPHeaderName::SecWebSocketKey}, +#line 173 "HTTPHeaderNames.gperf" + {"Authorization", HTTPHeaderName::Authorization}, +#line 235 "HTTPHeaderNames.gperf" + {"SourceMap", HTTPHeaderName::SourceMap}, +#line 224 "HTTPHeaderNames.gperf" + {"Sec-WebSocket-Accept", HTTPHeaderName::SecWebSocketAccept}, +#line 160 "HTTPHeaderNames.gperf" + {"Accept-Charset", HTTPHeaderName::AcceptCharset}, +#line 230 "HTTPHeaderNames.gperf" + {"Service-Worker", HTTPHeaderName::ServiceWorker}, +#line 250 "HTTPHeaderNames.gperf" + {"X-XSS-Protection", HTTPHeaderName::XXSSProtection}, +#line 189 "HTTPHeaderNames.gperf" + {"Cross-Origin-Opener-Policy", HTTPHeaderName::CrossOriginOpenerPolicy}, +#line 200 "HTTPHeaderNames.gperf" + {"Icy-Metadata", HTTPHeaderName::IcyMetadata}, +#line 248 "HTTPHeaderNames.gperf" + {"X-SourceMap", HTTPHeaderName::XSourceMap}, +#line 227 "HTTPHeaderNames.gperf" + {"Sec-WebSocket-Protocol", HTTPHeaderName::SecWebSocketProtocol}, +#line 176 "HTTPHeaderNames.gperf" + {"Content-Disposition", HTTPHeaderName::ContentDisposition}, +#line 183 "HTTPHeaderNames.gperf" + {"Content-Security-Policy-Report-Only", HTTPHeaderName::ContentSecurityPolicyReportOnly}, +#line 191 "HTTPHeaderNames.gperf" + {"Cross-Origin-Resource-Policy", HTTPHeaderName::CrossOriginResourcePolicy}, +#line 212 "HTTPHeaderNames.gperf" + {"Ping-From", HTTPHeaderName::PingFrom}, +#line 249 "HTTPHeaderNames.gperf" + {"X-Temp-Tablet", HTTPHeaderName::XTempTablet}, +#line 239 "HTTPHeaderNames.gperf" + {"Transfer-Encoding", HTTPHeaderName::TransferEncoding}, +#line 220 "HTTPHeaderNames.gperf" + {"Refresh", HTTPHeaderName::Refresh}, +#line 215 "HTTPHeaderNames.gperf" + {"Proxy-Authorization", HTTPHeaderName::ProxyAuthorization}, +#line 167 "HTTPHeaderNames.gperf" + {"Access-Control-Allow-Origin", HTTPHeaderName::AccessControlAllowOrigin}, +#line 237 "HTTPHeaderNames.gperf" + {"Timing-Allow-Origin", HTTPHeaderName::TimingAllowOrigin}, +#line 207 "HTTPHeaderNames.gperf" + {"Last-Event-ID", HTTPHeaderName::LastEventID}, +#line 241 "HTTPHeaderNames.gperf" + {"Upgrade-Insecure-Requests", HTTPHeaderName::UpgradeInsecureRequests}, +#line 229 "HTTPHeaderNames.gperf" + {"Server-Timing", HTTPHeaderName::ServerTiming}, +#line 169 "HTTPHeaderNames.gperf" + {"Access-Control-Max-Age", HTTPHeaderName::AccessControlMaxAge}, +#line 190 "HTTPHeaderNames.gperf" + {"Cross-Origin-Opener-Policy-Report-Only", HTTPHeaderName::CrossOriginOpenerPolicyReportOnly}, +#line 225 "HTTPHeaderNames.gperf" + {"Sec-WebSocket-Extensions", HTTPHeaderName::SecWebSocketExtensions}, +#line 194 "HTTPHeaderNames.gperf" + {"Default-Style", HTTPHeaderName::DefaultStyle}, +#line 228 "HTTPHeaderNames.gperf" + {"Sec-WebSocket-Version", HTTPHeaderName::SecWebSocketVersion}, +#line 247 "HTTPHeaderNames.gperf" + {"X-Frame-Options", HTTPHeaderName::XFrameOptions}, +#line 201 "HTTPHeaderNames.gperf" + {"If-Match", HTTPHeaderName::IfMatch}, +#line 203 "HTTPHeaderNames.gperf" + {"If-None-Match", HTTPHeaderName::IfNoneMatch}, +#line 222 "HTTPHeaderNames.gperf" + {"Sec-Fetch-Dest", HTTPHeaderName::SecFetchDest}, +#line 231 "HTTPHeaderNames.gperf" + {"Service-Worker-Allowed", HTTPHeaderName::ServiceWorkerAllowed}, +#line 164 "HTTPHeaderNames.gperf" + {"Access-Control-Allow-Credentials", HTTPHeaderName::AccessControlAllowCredentials}, +#line 170 "HTTPHeaderNames.gperf" + {"Access-Control-Request-Headers", HTTPHeaderName::AccessControlRequestHeaders}, +#line 246 "HTTPHeaderNames.gperf" + {"X-DNS-Prefetch-Control", HTTPHeaderName::XDNSPrefetchControl}, +#line 223 "HTTPHeaderNames.gperf" + {"Sec-Fetch-Mode", HTTPHeaderName::SecFetchMode}, +#line 208 "HTTPHeaderNames.gperf" + {"Last-Modified", HTTPHeaderName::LastModified}, +#line 232 "HTTPHeaderNames.gperf" + {"Service-Worker-Navigation-Preload", HTTPHeaderName::ServiceWorkerNavigationPreload}, +#line 168 "HTTPHeaderNames.gperf" + {"Access-Control-Expose-Headers", HTTPHeaderName::AccessControlExposeHeaders}, +#line 165 "HTTPHeaderNames.gperf" + {"Access-Control-Allow-Headers", HTTPHeaderName::AccessControlAllowHeaders}, +#line 187 "HTTPHeaderNames.gperf" + {"Cross-Origin-Embedder-Policy", HTTPHeaderName::CrossOriginEmbedderPolicy}, +#line 171 "HTTPHeaderNames.gperf" + {"Access-Control-Request-Method", HTTPHeaderName::AccessControlRequestMethod}, +#line 202 "HTTPHeaderNames.gperf" + {"If-Modified-Since", HTTPHeaderName::IfModifiedSince}, +#line 205 "HTTPHeaderNames.gperf" + {"If-Unmodified-Since", HTTPHeaderName::IfUnmodifiedSince}, +#line 188 "HTTPHeaderNames.gperf" + {"Cross-Origin-Embedder-Policy-Report-Only", HTTPHeaderName::CrossOriginEmbedderPolicyReportOnly}, +#line 166 "HTTPHeaderNames.gperf" + {"Access-Control-Allow-Methods", HTTPHeaderName::AccessControlAllowMethods} + }; + +static const signed char lookup[] = + { + -1, -1, -1, -1, -1, 0, -1, -1, -1, 1, 2, -1, -1, -1, + 3, 4, -1, -1, -1, -1, 5, -1, -1, -1, -1, 6, -1, -1, + -1, -1, 7, -1, -1, -1, -1, 8, -1, -1, -1, -1, 9, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 10, -1, -1, -1, -1, 11, + -1, -1, -1, 12, 13, -1, -1, -1, -1, 14, -1, -1, -1, -1, + 15, -1, -1, -1, 16, -1, -1, -1, -1, 17, 18, -1, -1, -1, + -1, 19, -1, -1, -1, -1, 20, -1, -1, -1, -1, 21, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 22, -1, + -1, -1, -1, 23, -1, -1, -1, -1, 24, -1, -1, -1, -1, 25, + -1, -1, -1, -1, 26, -1, -1, -1, -1, 27, -1, -1, -1, -1, + 28, -1, -1, -1, -1, 29, -1, -1, -1, -1, 30, -1, -1, -1, + -1, 31, -1, -1, -1, -1, 32, -1, -1, -1, -1, 33, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 34, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, + -1, -1, -1, -1, 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 37, -1, -1, -1, -1, 38, -1, -1, -1, 39, 40, -1, -1, -1, + -1, 41, -1, -1, -1, -1, -1, -1, -1, -1, -1, 42, -1, -1, + -1, -1, 43, -1, -1, 44, -1, -1, -1, -1, -1, -1, 45, -1, + -1, -1, -1, 46, -1, -1, -1, 47, 48, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 49, 50, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 52, -1, -1, -1, -1, 53, -1, -1, + -1, 54, 55, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56, -1, + -1, -1, -1, 57, -1, -1, -1, -1, 58, -1, -1, -1, -1, 59, + -1, -1, -1, -1, 60, -1, -1, -1, -1, 61, -1, -1, -1, -1, + 62, -1, -1, -1, -1, 63, -1, -1, -1, -1, 64, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 65, -1, -1, + -1, -1, 66, -1, -1, -1, -1, -1, -1, -1, -1, -1, 67, -1, + -1, -1, -1, 68, -1, -1, -1, -1, 69, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 70, 71, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 72, 73, -1, -1, -1, -1, 74, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 75, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 76, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 77, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 78, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 79, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 80, -1, -1, -1, -1, 81, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 83, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 84, -1, -1, + -1, -1, 85, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 86, -1, -1, -1, -1, 87, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 88, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 89, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 90, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 91 + }; + +const struct HeaderNameHashEntry * +HTTPHeaderNamesHash::findHeaderNameImpl (register const char *str, register unsigned int len) +{ + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + unsigned int key = header_name_hash_function (str, len); + + if (key <= MAX_HASH_VALUE) + { + register int index = lookup[key]; + + if (index >= 0) + { + register const char *s = header_name_wordlist[index].name; + + if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0') + return &header_name_wordlist[index]; + } + } + } + return 0; +} +#line 251 "HTTPHeaderNames.gperf" + +bool findHTTPHeaderName(StringView stringView, HTTPHeaderName& headerName) +{ + unsigned length = stringView.length(); + if (length > maxHTTPHeaderNameLength || length < minHTTPHeaderNameLength) + return false; + + if (stringView.is8Bit()) { + if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast<const char*>(stringView.characters8()), length)) { + headerName = nameAndString->headerName; + return true; + } + } else { + LChar characters[maxHTTPHeaderNameLength]; + for (unsigned i = 0; i < length; ++i) { + UChar character = stringView.characters16()[i]; + if (!isASCII(character)) + return false; + + characters[i] = static_cast<LChar>(character); + } + + if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast<const char*>(characters), length)) { + headerName = nameAndString->headerName; + return true; + } + } + + return false; +} + +StringView httpHeaderNameString(HTTPHeaderName headerName) +{ + ASSERT(static_cast<unsigned>(headerName) < numHTTPHeaderNames); + + const auto& name = headerNameStrings[static_cast<unsigned>(headerName)]; + + return StringView { reinterpret_cast<const LChar*>(name.name), static_cast<unsigned>(name.length) }; +} + +} // namespace WebCore + +#if defined(__clang__) +IGNORE_WARNINGS_END +#endif diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.gperf b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.gperf new file mode 100644 index 000000000..66a4b24e8 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.gperf @@ -0,0 +1,295 @@ + +%{ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/// This file is generated by create-http-header-name-table, do not edit. + +#include "config.h" +#include "HTTPHeaderNames.h" + +#include <wtf/text/StringView.h> + +IGNORE_WARNINGS_BEGIN("implicit-fallthrough") + +// Older versions of gperf like to use the `register` keyword. +#define register + +namespace WebCore { + +static const struct HeaderNameString { + const char* const name; + unsigned length; +} headerNameStrings[] = { + { "Accept", 6 }, + { "Accept-Charset", 14 }, + { "Accept-Encoding", 15 }, + { "Accept-Language", 15 }, + { "Accept-Ranges", 13 }, + { "Access-Control-Allow-Credentials", 32 }, + { "Access-Control-Allow-Headers", 28 }, + { "Access-Control-Allow-Methods", 28 }, + { "Access-Control-Allow-Origin", 27 }, + { "Access-Control-Expose-Headers", 29 }, + { "Access-Control-Max-Age", 22 }, + { "Access-Control-Request-Headers", 30 }, + { "Access-Control-Request-Method", 29 }, + { "Age", 3 }, + { "Authorization", 13 }, + { "Cache-Control", 13 }, + { "Connection", 10 }, + { "Content-Disposition", 19 }, + { "Content-Encoding", 16 }, + { "Content-Language", 16 }, + { "Content-Length", 14 }, + { "Content-Location", 16 }, + { "Content-Range", 13 }, + { "Content-Security-Policy", 23 }, + { "Content-Security-Policy-Report-Only", 35 }, + { "Content-Type", 12 }, + { "Cookie", 6 }, + { "Cookie2", 7 }, + { "Cross-Origin-Embedder-Policy", 28 }, + { "Cross-Origin-Embedder-Policy-Report-Only", 40 }, + { "Cross-Origin-Opener-Policy", 26 }, + { "Cross-Origin-Opener-Policy-Report-Only", 38 }, + { "Cross-Origin-Resource-Policy", 28 }, + { "DNT", 3 }, + { "Date", 4 }, + { "Default-Style", 13 }, + { "ETag", 4 }, + { "Expect", 6 }, + { "Expires", 7 }, + { "Host", 4 }, + { "Icy-MetaInt", 11 }, + { "Icy-Metadata", 12 }, + { "If-Match", 8 }, + { "If-Modified-Since", 17 }, + { "If-None-Match", 13 }, + { "If-Range", 8 }, + { "If-Unmodified-Since", 19 }, + { "Keep-Alive", 10 }, + { "Last-Event-ID", 13 }, + { "Last-Modified", 13 }, + { "Link", 4 }, + { "Location", 8 }, + { "Origin", 6 }, + { "Ping-From", 9 }, + { "Ping-To", 7 }, + { "Pragma", 6 }, + { "Proxy-Authorization", 19 }, + { "Purpose", 7 }, + { "Range", 5 }, + { "Referer", 7 }, + { "Referrer-Policy", 15 }, + { "Refresh", 7 }, + { "Report-To", 9 }, + { "Sec-Fetch-Dest", 14 }, + { "Sec-Fetch-Mode", 14 }, + { "Sec-WebSocket-Accept", 20 }, + { "Sec-WebSocket-Extensions", 24 }, + { "Sec-WebSocket-Key", 17 }, + { "Sec-WebSocket-Protocol", 22 }, + { "Sec-WebSocket-Version", 21 }, + { "Server-Timing", 13 }, + { "Service-Worker", 14 }, + { "Service-Worker-Allowed", 22 }, + { "Service-Worker-Navigation-Preload", 33 }, + { "Set-Cookie", 10 }, + { "Set-Cookie2", 11 }, + { "SourceMap", 9 }, + { "TE", 2 }, + { "Timing-Allow-Origin", 19 }, + { "Trailer", 7 }, + { "Transfer-Encoding", 17 }, + { "Upgrade", 7 }, + { "Upgrade-Insecure-Requests", 25 }, + { "User-Agent", 10 }, + { "Vary", 4 }, + { "Via", 3 }, + { "X-Content-Type-Options", 22 }, + { "X-DNS-Prefetch-Control", 22 }, + { "X-Frame-Options", 15 }, + { "X-SourceMap", 11 }, + { "X-Temp-Tablet", 13 }, + { "X-XSS-Protection", 16 }, +}; + + +%} + +%language=C++ +%readonly-tables +%global-table +%compare-strncmp +%ignore-case +%struct-type +struct HeaderNameHashEntry { + const char* name; + HTTPHeaderName headerName; +}; +%define class-name HTTPHeaderNamesHash +%define lookup-function-name findHeaderNameImpl +%define hash-function-name header_name_hash_function +%define word-array-name header_name_wordlist +%enum +%% +Accept, HTTPHeaderName::Accept +Accept-Charset, HTTPHeaderName::AcceptCharset +Accept-Encoding, HTTPHeaderName::AcceptEncoding +Accept-Language, HTTPHeaderName::AcceptLanguage +Accept-Ranges, HTTPHeaderName::AcceptRanges +Access-Control-Allow-Credentials, HTTPHeaderName::AccessControlAllowCredentials +Access-Control-Allow-Headers, HTTPHeaderName::AccessControlAllowHeaders +Access-Control-Allow-Methods, HTTPHeaderName::AccessControlAllowMethods +Access-Control-Allow-Origin, HTTPHeaderName::AccessControlAllowOrigin +Access-Control-Expose-Headers, HTTPHeaderName::AccessControlExposeHeaders +Access-Control-Max-Age, HTTPHeaderName::AccessControlMaxAge +Access-Control-Request-Headers, HTTPHeaderName::AccessControlRequestHeaders +Access-Control-Request-Method, HTTPHeaderName::AccessControlRequestMethod +Age, HTTPHeaderName::Age +Authorization, HTTPHeaderName::Authorization +Cache-Control, HTTPHeaderName::CacheControl +Connection, HTTPHeaderName::Connection +Content-Disposition, HTTPHeaderName::ContentDisposition +Content-Encoding, HTTPHeaderName::ContentEncoding +Content-Language, HTTPHeaderName::ContentLanguage +Content-Length, HTTPHeaderName::ContentLength +Content-Location, HTTPHeaderName::ContentLocation +Content-Range, HTTPHeaderName::ContentRange +Content-Security-Policy, HTTPHeaderName::ContentSecurityPolicy +Content-Security-Policy-Report-Only, HTTPHeaderName::ContentSecurityPolicyReportOnly +Content-Type, HTTPHeaderName::ContentType +Cookie, HTTPHeaderName::Cookie +Cookie2, HTTPHeaderName::Cookie2 +Cross-Origin-Embedder-Policy, HTTPHeaderName::CrossOriginEmbedderPolicy +Cross-Origin-Embedder-Policy-Report-Only, HTTPHeaderName::CrossOriginEmbedderPolicyReportOnly +Cross-Origin-Opener-Policy, HTTPHeaderName::CrossOriginOpenerPolicy +Cross-Origin-Opener-Policy-Report-Only, HTTPHeaderName::CrossOriginOpenerPolicyReportOnly +Cross-Origin-Resource-Policy, HTTPHeaderName::CrossOriginResourcePolicy +DNT, HTTPHeaderName::DNT +Date, HTTPHeaderName::Date +Default-Style, HTTPHeaderName::DefaultStyle +ETag, HTTPHeaderName::ETag +Expect, HTTPHeaderName::Expect +Expires, HTTPHeaderName::Expires +Host, HTTPHeaderName::Host +Icy-MetaInt, HTTPHeaderName::IcyMetaInt +Icy-Metadata, HTTPHeaderName::IcyMetadata +If-Match, HTTPHeaderName::IfMatch +If-Modified-Since, HTTPHeaderName::IfModifiedSince +If-None-Match, HTTPHeaderName::IfNoneMatch +If-Range, HTTPHeaderName::IfRange +If-Unmodified-Since, HTTPHeaderName::IfUnmodifiedSince +Keep-Alive, HTTPHeaderName::KeepAlive +Last-Event-ID, HTTPHeaderName::LastEventID +Last-Modified, HTTPHeaderName::LastModified +Link, HTTPHeaderName::Link +Location, HTTPHeaderName::Location +Origin, HTTPHeaderName::Origin +Ping-From, HTTPHeaderName::PingFrom +Ping-To, HTTPHeaderName::PingTo +Pragma, HTTPHeaderName::Pragma +Proxy-Authorization, HTTPHeaderName::ProxyAuthorization +Purpose, HTTPHeaderName::Purpose +Range, HTTPHeaderName::Range +Referer, HTTPHeaderName::Referer +Referrer-Policy, HTTPHeaderName::ReferrerPolicy +Refresh, HTTPHeaderName::Refresh +Report-To, HTTPHeaderName::ReportTo +Sec-Fetch-Dest, HTTPHeaderName::SecFetchDest +Sec-Fetch-Mode, HTTPHeaderName::SecFetchMode +Sec-WebSocket-Accept, HTTPHeaderName::SecWebSocketAccept +Sec-WebSocket-Extensions, HTTPHeaderName::SecWebSocketExtensions +Sec-WebSocket-Key, HTTPHeaderName::SecWebSocketKey +Sec-WebSocket-Protocol, HTTPHeaderName::SecWebSocketProtocol +Sec-WebSocket-Version, HTTPHeaderName::SecWebSocketVersion +Server-Timing, HTTPHeaderName::ServerTiming +Service-Worker, HTTPHeaderName::ServiceWorker +Service-Worker-Allowed, HTTPHeaderName::ServiceWorkerAllowed +Service-Worker-Navigation-Preload, HTTPHeaderName::ServiceWorkerNavigationPreload +Set-Cookie, HTTPHeaderName::SetCookie +Set-Cookie2, HTTPHeaderName::SetCookie2 +SourceMap, HTTPHeaderName::SourceMap +TE, HTTPHeaderName::TE +Timing-Allow-Origin, HTTPHeaderName::TimingAllowOrigin +Trailer, HTTPHeaderName::Trailer +Transfer-Encoding, HTTPHeaderName::TransferEncoding +Upgrade, HTTPHeaderName::Upgrade +Upgrade-Insecure-Requests, HTTPHeaderName::UpgradeInsecureRequests +User-Agent, HTTPHeaderName::UserAgent +Vary, HTTPHeaderName::Vary +Via, HTTPHeaderName::Via +X-Content-Type-Options, HTTPHeaderName::XContentTypeOptions +X-DNS-Prefetch-Control, HTTPHeaderName::XDNSPrefetchControl +X-Frame-Options, HTTPHeaderName::XFrameOptions +X-SourceMap, HTTPHeaderName::XSourceMap +X-Temp-Tablet, HTTPHeaderName::XTempTablet +X-XSS-Protection, HTTPHeaderName::XXSSProtection +%% +bool findHTTPHeaderName(StringView stringView, HTTPHeaderName& headerName) +{ + unsigned length = stringView.length(); + if (length > maxHTTPHeaderNameLength || length < minHTTPHeaderNameLength) + return false; + + if (stringView.is8Bit()) { + if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast<const char*>(stringView.characters8()), length)) { + headerName = nameAndString->headerName; + return true; + } + } else { + LChar characters[maxHTTPHeaderNameLength]; + for (unsigned i = 0; i < length; ++i) { + UChar character = stringView.characters16()[i]; + if (!isASCII(character)) + return false; + + characters[i] = static_cast<LChar>(character); + } + + if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast<const char*>(characters), length)) { + headerName = nameAndString->headerName; + return true; + } + } + + return false; +} + +StringView httpHeaderNameString(HTTPHeaderName headerName) +{ + ASSERT(static_cast<unsigned>(headerName) < numHTTPHeaderNames); + + const auto& name = headerNameStrings[static_cast<unsigned>(headerName)]; + + return StringView { reinterpret_cast<const LChar*>(name.name), static_cast<unsigned>(name.length) }; +} + +} // namespace WebCore + +#if defined(__clang__) +IGNORE_WARNINGS_END +#endif diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.h b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.h new file mode 100644 index 000000000..066d40e90 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.h @@ -0,0 +1,242 @@ + +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/// This file is generated by create-http-header-name-table, do not edit. + +#ifndef HTTPHeaderNames_h +#define HTTPHeaderNames_h + +#include <wtf/Forward.h> + +namespace WebCore { + +enum class HTTPHeaderName { + Accept, + AcceptCharset, + AcceptEncoding, + AcceptLanguage, + AcceptRanges, + AccessControlAllowCredentials, + AccessControlAllowHeaders, + AccessControlAllowMethods, + AccessControlAllowOrigin, + AccessControlExposeHeaders, + AccessControlMaxAge, + AccessControlRequestHeaders, + AccessControlRequestMethod, + Age, + Authorization, + CacheControl, + Connection, + ContentDisposition, + ContentEncoding, + ContentLanguage, + ContentLength, + ContentLocation, + ContentRange, + ContentSecurityPolicy, + ContentSecurityPolicyReportOnly, + ContentType, + Cookie, + Cookie2, + CrossOriginEmbedderPolicy, + CrossOriginEmbedderPolicyReportOnly, + CrossOriginOpenerPolicy, + CrossOriginOpenerPolicyReportOnly, + CrossOriginResourcePolicy, + DNT, + Date, + DefaultStyle, + ETag, + Expect, + Expires, + Host, + IcyMetaInt, + IcyMetadata, + IfMatch, + IfModifiedSince, + IfNoneMatch, + IfRange, + IfUnmodifiedSince, + KeepAlive, + LastEventID, + LastModified, + Link, + Location, + Origin, + PingFrom, + PingTo, + Pragma, + ProxyAuthorization, + Purpose, + Range, + Referer, + ReferrerPolicy, + Refresh, + ReportTo, + SecFetchDest, + SecFetchMode, + SecWebSocketAccept, + SecWebSocketExtensions, + SecWebSocketKey, + SecWebSocketProtocol, + SecWebSocketVersion, + ServerTiming, + ServiceWorker, + ServiceWorkerAllowed, + ServiceWorkerNavigationPreload, + SetCookie, + SetCookie2, + SourceMap, + TE, + TimingAllowOrigin, + Trailer, + TransferEncoding, + Upgrade, + UpgradeInsecureRequests, + UserAgent, + Vary, + Via, + XContentTypeOptions, + XDNSPrefetchControl, + XFrameOptions, + XSourceMap, + XTempTablet, + XXSSProtection, +}; + +const unsigned numHTTPHeaderNames = 92; +const size_t minHTTPHeaderNameLength = 2; +const size_t maxHTTPHeaderNameLength = 40; + +bool findHTTPHeaderName(StringView, HTTPHeaderName&); +WEBCORE_EXPORT StringView httpHeaderNameString(HTTPHeaderName); + +} // namespace WebCore + +namespace WTF { + +template<> struct EnumTraits<WebCore::HTTPHeaderName> { + using values = EnumValues< + WebCore::HTTPHeaderName, + WebCore::HTTPHeaderName::Accept, + WebCore::HTTPHeaderName::AcceptCharset, + WebCore::HTTPHeaderName::AcceptEncoding, + WebCore::HTTPHeaderName::AcceptLanguage, + WebCore::HTTPHeaderName::AcceptRanges, + WebCore::HTTPHeaderName::AccessControlAllowCredentials, + WebCore::HTTPHeaderName::AccessControlAllowHeaders, + WebCore::HTTPHeaderName::AccessControlAllowMethods, + WebCore::HTTPHeaderName::AccessControlAllowOrigin, + WebCore::HTTPHeaderName::AccessControlExposeHeaders, + WebCore::HTTPHeaderName::AccessControlMaxAge, + WebCore::HTTPHeaderName::AccessControlRequestHeaders, + WebCore::HTTPHeaderName::AccessControlRequestMethod, + WebCore::HTTPHeaderName::Age, + WebCore::HTTPHeaderName::Authorization, + WebCore::HTTPHeaderName::CacheControl, + WebCore::HTTPHeaderName::Connection, + WebCore::HTTPHeaderName::ContentDisposition, + WebCore::HTTPHeaderName::ContentEncoding, + WebCore::HTTPHeaderName::ContentLanguage, + WebCore::HTTPHeaderName::ContentLength, + WebCore::HTTPHeaderName::ContentLocation, + WebCore::HTTPHeaderName::ContentRange, + WebCore::HTTPHeaderName::ContentSecurityPolicy, + WebCore::HTTPHeaderName::ContentSecurityPolicyReportOnly, + WebCore::HTTPHeaderName::ContentType, + WebCore::HTTPHeaderName::Cookie, + WebCore::HTTPHeaderName::Cookie2, + WebCore::HTTPHeaderName::CrossOriginEmbedderPolicy, + WebCore::HTTPHeaderName::CrossOriginEmbedderPolicyReportOnly, + WebCore::HTTPHeaderName::CrossOriginOpenerPolicy, + WebCore::HTTPHeaderName::CrossOriginOpenerPolicyReportOnly, + WebCore::HTTPHeaderName::CrossOriginResourcePolicy, + WebCore::HTTPHeaderName::DNT, + WebCore::HTTPHeaderName::Date, + WebCore::HTTPHeaderName::DefaultStyle, + WebCore::HTTPHeaderName::ETag, + WebCore::HTTPHeaderName::Expect, + WebCore::HTTPHeaderName::Expires, + WebCore::HTTPHeaderName::Host, + WebCore::HTTPHeaderName::IcyMetaInt, + WebCore::HTTPHeaderName::IcyMetadata, + WebCore::HTTPHeaderName::IfMatch, + WebCore::HTTPHeaderName::IfModifiedSince, + WebCore::HTTPHeaderName::IfNoneMatch, + WebCore::HTTPHeaderName::IfRange, + WebCore::HTTPHeaderName::IfUnmodifiedSince, + WebCore::HTTPHeaderName::KeepAlive, + WebCore::HTTPHeaderName::LastEventID, + WebCore::HTTPHeaderName::LastModified, + WebCore::HTTPHeaderName::Link, + WebCore::HTTPHeaderName::Location, + WebCore::HTTPHeaderName::Origin, + WebCore::HTTPHeaderName::PingFrom, + WebCore::HTTPHeaderName::PingTo, + WebCore::HTTPHeaderName::Pragma, + WebCore::HTTPHeaderName::ProxyAuthorization, + WebCore::HTTPHeaderName::Purpose, + WebCore::HTTPHeaderName::Range, + WebCore::HTTPHeaderName::Referer, + WebCore::HTTPHeaderName::ReferrerPolicy, + WebCore::HTTPHeaderName::Refresh, + WebCore::HTTPHeaderName::ReportTo, + WebCore::HTTPHeaderName::SecFetchDest, + WebCore::HTTPHeaderName::SecFetchMode, + WebCore::HTTPHeaderName::SecWebSocketAccept, + WebCore::HTTPHeaderName::SecWebSocketExtensions, + WebCore::HTTPHeaderName::SecWebSocketKey, + WebCore::HTTPHeaderName::SecWebSocketProtocol, + WebCore::HTTPHeaderName::SecWebSocketVersion, + WebCore::HTTPHeaderName::ServerTiming, + WebCore::HTTPHeaderName::ServiceWorker, + WebCore::HTTPHeaderName::ServiceWorkerAllowed, + WebCore::HTTPHeaderName::ServiceWorkerNavigationPreload, + WebCore::HTTPHeaderName::SetCookie, + WebCore::HTTPHeaderName::SetCookie2, + WebCore::HTTPHeaderName::SourceMap, + WebCore::HTTPHeaderName::TE, + WebCore::HTTPHeaderName::TimingAllowOrigin, + WebCore::HTTPHeaderName::Trailer, + WebCore::HTTPHeaderName::TransferEncoding, + WebCore::HTTPHeaderName::Upgrade, + WebCore::HTTPHeaderName::UpgradeInsecureRequests, + WebCore::HTTPHeaderName::UserAgent, + WebCore::HTTPHeaderName::Vary, + WebCore::HTTPHeaderName::Via, + WebCore::HTTPHeaderName::XContentTypeOptions, + WebCore::HTTPHeaderName::XDNSPrefetchControl, + WebCore::HTTPHeaderName::XFrameOptions, + WebCore::HTTPHeaderName::XSourceMap, + WebCore::HTTPHeaderName::XTempTablet, + WebCore::HTTPHeaderName::XXSSProtection + >; +}; + +} // namespace WTF + +#endif // HTTPHeaderNames_h diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.in b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.in new file mode 100644 index 000000000..9edb2b2d7 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderNames.in @@ -0,0 +1,119 @@ +// +// Copyright (C) 2014 Apple Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +// + +Accept +Accept-Charset +Accept-Language +Accept-Encoding +Accept-Ranges +Access-Control-Allow-Credentials +Access-Control-Allow-Headers +Access-Control-Allow-Methods +Access-Control-Allow-Origin +Access-Control-Expose-Headers +Access-Control-Max-Age +Access-Control-Request-Headers +Access-Control-Request-Method +Age +Authorization +Cache-Control +Connection +Content-Disposition +Content-Encoding +Content-Language +Content-Length +Content-Location +Content-Security-Policy +Content-Security-Policy-Report-Only +Content-Type +Content-Range +Cookie +Cookie2 +Cross-Origin-Embedder-Policy +Cross-Origin-Embedder-Policy-Report-Only +Cross-Origin-Opener-Policy +Cross-Origin-Opener-Policy-Report-Only +Cross-Origin-Resource-Policy +Date +DNT +Default-Style +ETag +Expect +Expires +Host +If-Match +If-Modified-Since +If-None-Match +If-Range +If-Unmodified-Since +Keep-Alive +Last-Event-ID +Last-Modified +Link +Location +Origin +Ping-From +Ping-To +Purpose +Pragma +Proxy-Authorization +Range +Referer +Referrer-Policy +Refresh +Report-To +Sec-Fetch-Dest +Sec-Fetch-Mode +Sec-WebSocket-Accept +Sec-WebSocket-Extensions +Sec-WebSocket-Key +Sec-WebSocket-Protocol +Sec-WebSocket-Version +Server-Timing +Service-Worker +Service-Worker-Allowed +Service-Worker-Navigation-Preload +Set-Cookie +Set-Cookie2 +SourceMap +TE +Timing-Allow-Origin +Trailer +Transfer-Encoding +Upgrade +Upgrade-Insecure-Requests +User-Agent +Vary +Via +X-Content-Type-Options +X-DNS-Prefetch-Control +X-Frame-Options +X-SourceMap +X-XSS-Protection +X-Temp-Tablet + +// These headers are specific to GStreamer. +Icy-MetaInt +Icy-Metadata diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderValues.cpp b/src/javascript/jsc/bindings/webcore/HTTPHeaderValues.cpp new file mode 100644 index 000000000..b4869e846 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderValues.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016-2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "HTTPHeaderValues.h" + +#include <wtf/NeverDestroyed.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +namespace HTTPHeaderValues { + +const String& textPlainContentType() +{ + static NeverDestroyed<const String> contentType(MAKE_STATIC_STRING_IMPL("text/plain;charset=UTF-8")); + return contentType; +} + +const String& formURLEncodedContentType() +{ + static NeverDestroyed<const String> contentType(MAKE_STATIC_STRING_IMPL("application/x-www-form-urlencoded;charset=UTF-8")); + return contentType; +} + +const String& applicationJSONContentType() +{ + // The default encoding is UTF-8: https://www.ietf.org/rfc/rfc4627.txt. + static NeverDestroyed<const String> contentType(MAKE_STATIC_STRING_IMPL("application/json")); + return contentType; +} + +const String& noCache() +{ + static NeverDestroyed<const String> value(MAKE_STATIC_STRING_IMPL("no-cache")); + return value; +} + +const String& maxAge0() +{ + static NeverDestroyed<const String> value(MAKE_STATIC_STRING_IMPL("max-age=0")); + return value; +} + +} + +} diff --git a/src/javascript/jsc/bindings/webcore/HTTPHeaderValues.h b/src/javascript/jsc/bindings/webcore/HTTPHeaderValues.h new file mode 100644 index 000000000..6345a9a8f --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPHeaderValues.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/Forward.h> + +namespace WebCore { + +namespace HTTPHeaderValues { + +const String& textPlainContentType(); +const String& formURLEncodedContentType(); +WEBCORE_EXPORT const String& applicationJSONContentType(); +const String& noCache(); +WEBCORE_EXPORT const String& maxAge0(); +} + +} diff --git a/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp b/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp new file mode 100644 index 000000000..d435cebf4 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPParsers.cpp @@ -0,0 +1,1034 @@ +/* + * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) + * Copyright (C) 2006-2017 Apple Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "HTTPParsers.h" + +#include "HTTPHeaderField.h" +#include "HTTPHeaderNames.h" +#include "ParsedContentType.h" +#include <wtf/DateMath.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/text/StringBuilder.h> +#include <wtf/text/StringToIntegerConversion.h> +#include <wtf/unicode/CharacterNames.h> + +namespace WebCore { + +// True if characters which satisfy the predicate are present, incrementing +// "pos" to the next character which does not satisfy the predicate. +// Note: might return pos == str.length(). +static inline bool skipWhile(const String& str, unsigned& pos, const Function<bool(const UChar)>& predicate) +{ + const unsigned start = pos; + const unsigned len = str.length(); + while (pos < len && predicate(str[pos])) + ++pos; + return pos != start; +} + +// true if there is more to parse, after incrementing pos past whitespace. +// Note: Might return pos == str.length() +static inline bool skipWhiteSpace(const String& str, unsigned& pos) +{ + skipWhile(str, pos, RFC7230::isWhitespace); + return pos < str.length(); +} + +// Returns true if the function can match the whole token (case insensitive) +// incrementing pos on match, otherwise leaving pos unchanged. +// Note: Might return pos == str.length() +static inline bool skipToken(const String& str, unsigned& pos, const char* token) +{ + unsigned len = str.length(); + unsigned current = pos; + + while (current < len && *token) { + if (toASCIILower(str[current]) != *token++) + return false; + ++current; + } + + if (*token) + return false; + + pos = current; + return true; +} + +// True if the expected equals sign is seen and there is more to follow. +static inline bool skipEquals(const String& str, unsigned &pos) +{ + return skipWhiteSpace(str, pos) && str[pos++] == '=' && skipWhiteSpace(str, pos); +} + +// True if a value present, incrementing pos to next space or semicolon, if any. +// Note: might return pos == str.length(). +static inline bool skipValue(const String& str, unsigned& pos) +{ + unsigned start = pos; + unsigned len = str.length(); + while (pos < len) { + if (str[pos] == ' ' || str[pos] == '\t' || str[pos] == ';') + break; + ++pos; + } + return pos != start; +} + +// See RFC 7230, Section 3.1.2. +bool isValidReasonPhrase(const String& value) +{ + for (unsigned i = 0; i < value.length(); ++i) { + UChar c = value[i]; + if (c == 0x7F || !isLatin1(c) || (c < 0x20 && c != '\t')) + return false; + } + return true; +} + +// See https://fetch.spec.whatwg.org/#concept-header +bool isValidHTTPHeaderValue(const String& value) +{ + UChar c = value[0]; + if (c == ' ' || c == '\t') + return false; + c = value[value.length() - 1]; + if (c == ' ' || c == '\t') + return false; + for (unsigned i = 0; i < value.length(); ++i) { + c = value[i]; + if (c == 0x00 || c == 0x0A || c == 0x0D) + return false; + } + return true; +} + +// See RFC 7231, Section 5.3.2. +bool isValidAcceptHeaderValue(const String& value) +{ + for (unsigned i = 0; i < value.length(); ++i) { + UChar c = value[i]; + + // First check for alphanumeric for performance reasons then allowlist four delimiter characters. + if (isASCIIAlphanumeric(c) || c == ',' || c == '/' || c == ';' || c == '=') + continue; + + ASSERT(isLatin1(c)); + if (c == 0x7F || (c < 0x20 && c != '\t')) + return false; + + if (RFC7230::isDelimiter(c)) + return false; + } + + return true; +} + +static bool containsCORSUnsafeRequestHeaderBytes(const String& value) +{ + for (unsigned i = 0; i < value.length(); ++i) { + UChar c = value[i]; + // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte + if ((c < 0x20 && c != '\t') || (c == '"' || c == '(' || c == ')' || c == ':' || c == '<' || c == '>' || c == '?' + || c == '@' || c == '[' || c == '\\' || c == ']' || c == 0x7B || c == '{' || c == '}' || c == 0x7F)) + return true; + } + + return false; +} + +// See RFC 7231, Section 5.3.5 and 3.1.3.2. +bool isValidLanguageHeaderValue(const String& value) +{ + for (unsigned i = 0; i < value.length(); ++i) { + UChar c = value[i]; + if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == ',' || c == '-' || c == '.' || c == ';' || c == '=') + continue; + return false; + } + + // FIXME: Validate further by splitting into language tags and optional quality + // values (q=) and then check each language tag. + // Language tags https://tools.ietf.org/html/rfc7231#section-3.1.3.1 + // Language tag syntax https://tools.ietf.org/html/bcp47#section-2.1 + return true; +} + +// See RFC 7230, Section 3.2.6. +bool isValidHTTPToken(StringView value) +{ + if (value.isEmpty()) + return false; + for (UChar c : value.codeUnits()) { + if (!RFC7230::isTokenCharacter(c)) + return false; + } + return true; +} + +bool isValidHTTPToken(const String& value) +{ + return isValidHTTPToken(StringView(value)); +} + +#if USE(GLIB) +// True if the character at the given position satisifies a predicate, incrementing "pos" by one. +// Note: Might return pos == str.length() +static inline bool skipCharacter(const String& value, unsigned& pos, Function<bool(const UChar)>&& predicate) +{ + if (pos < value.length() && predicate(value[pos])) { + ++pos; + return true; + } + return false; +} + +// True if the "expected" character is at the given position, incrementing "pos" by one. +// Note: Might return pos == str.length() +static inline bool skipCharacter(const String& value, unsigned& pos, const UChar expected) +{ + return skipCharacter(value, pos, [expected](const UChar c) { + return c == expected; + }); +} + +// True if a quoted pair is present, incrementing "pos" to the position after the quoted pair. +// Note: Might return pos == str.length() +// See RFC 7230, Section 3.2.6. +static constexpr auto QuotedPairStartCharacter = '\\'; +static bool skipQuotedPair(const String& value, unsigned& pos) +{ + // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + return skipCharacter(value, pos, QuotedPairStartCharacter) + && skipCharacter(value, pos, RFC7230::isQuotedPairSecondOctet); +} + +// True if a comment is present, incrementing "pos" to the position after the comment. +// Note: Might return pos == str.length() +// See RFC 7230, Section 3.2.6. +static constexpr auto CommentStartCharacter = '('; +static constexpr auto CommentEndCharacter = ')'; +static bool skipComment(const String& value, unsigned& pos) +{ + // comment = "(" *( ctext / quoted-pair / comment ) ")" + // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text + if (!skipCharacter(value, pos, CommentStartCharacter)) + return false; + + const unsigned end = value.length(); + while (pos < end && value[pos] != CommentEndCharacter) { + switch (value[pos]) { + case CommentStartCharacter: + if (!skipComment(value, pos)) + return false; + break; + case QuotedPairStartCharacter: + if (!skipQuotedPair(value, pos)) + return false; + break; + default: + if (!skipWhile(value, pos, RFC7230::isCommentText)) + return false; + } + } + return skipCharacter(value, pos, CommentEndCharacter); +} + +// True if an HTTP header token is present, incrementing "pos" to the position after it. +// Note: Might return pos == str.length() +// See RFC 7230, Section 3.2.6. +static bool skipHTTPToken(const String& value, unsigned& pos) +{ + return skipWhile(value, pos, RFC7230::isTokenCharacter); +} + +// True if a product specifier (as in an User-Agent header) is present, incrementing "pos" to the position after it. +// Note: Might return pos == str.length() +// See RFC 7231, Section 5.5.3. +static bool skipUserAgentProduct(const String& value, unsigned& pos) +{ + // product = token ["/" product-version] + // product-version = token + if (!skipHTTPToken(value, pos)) + return false; + if (skipCharacter(value, pos, '/')) + return skipHTTPToken(value, pos); + return true; +} + +// See RFC 7231, Section 5.5.3 +bool isValidUserAgentHeaderValue(const String& value) +{ + // User-Agent = product *( RWS ( product / comment ) ) + unsigned pos = 0; + if (!skipUserAgentProduct(value, pos)) + return false; + + while (pos < value.length()) { + if (!skipWhiteSpace(value, pos)) + return false; + if (value[pos] == CommentStartCharacter) { + if (!skipComment(value, pos)) + return false; + } else { + if (!skipUserAgentProduct(value, pos)) + return false; + } + } + + return pos == value.length(); +} +#endif + +static const size_t maxInputSampleSize = 128; +template<typename CharType> +static String trimInputSample(CharType* p, size_t length) +{ + String s = String(p, std::min<size_t>(length, maxInputSampleSize)); + if (length > maxInputSampleSize) + s.append(horizontalEllipsis); + return s; +} + +std::optional<WallTime> parseHTTPDate(const String& value) +{ + double dateInMillisecondsSinceEpoch = parseDateFromNullTerminatedCharacters(value.utf8().data()); + if (!std::isfinite(dateInMillisecondsSinceEpoch)) + return std::nullopt; + // This assumes system_clock epoch equals Unix epoch which is true for all implementations but unspecified. + // FIXME: The parsing function should be switched to WallTime too. + return WallTime::fromRawSeconds(dateInMillisecondsSinceEpoch / 1000.0); +} + +// FIXME: This function doesn't comply with RFC 6266. +// For example, this function doesn't handle the interaction between " and ; +// that arises from quoted-string, nor does this function properly unquote +// attribute values. Further this function appears to process parameter names +// in a case-sensitive manner. (There are likely other bugs as well.) +String filenameFromHTTPContentDisposition(StringView value) +{ + for (auto keyValuePair : value.split(';')) { + size_t valueStartPos = keyValuePair.find('='); + if (valueStartPos == notFound) + continue; + + auto key = keyValuePair.left(valueStartPos).stripWhiteSpace(); + + if (key.isEmpty() || key != "filename") + continue; + + auto value = keyValuePair.substring(valueStartPos + 1).stripWhiteSpace(); + + // Remove quotes if there are any + if (value.length() > 1 && value[0] == '\"') + value = value.substring(1, value.length() - 2); + + return value.toString(); + } + + return String(); +} + +String extractMIMETypeFromMediaType(const String& mediaType) +{ + unsigned position = 0; + unsigned length = mediaType.length(); + + for (; position < length; ++position) { + UChar c = mediaType[position]; + if (c != '\t' && c != ' ') + break; + } + + if (position == length) + return mediaType; + + unsigned typeStart = position; + + unsigned typeEnd = position; + for (; position < length; ++position) { + UChar c = mediaType[position]; + + // While RFC 2616 does not allow it, other browsers allow multiple values in the HTTP media + // type header field, Content-Type. In such cases, the media type string passed here may contain + // the multiple values separated by commas. For now, this code ignores text after the first comma, + // which prevents it from simply failing to parse such types altogether. Later for better + // compatibility we could consider using the first or last valid MIME type instead. + // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion. + if (c == ',') + break; + + if (c == '\t' || c == ' ' || c == ';') + break; + + typeEnd = position + 1; + } + + return mediaType.substring(typeStart, typeEnd - typeStart); +} + +String extractCharsetFromMediaType(const String& mediaType) +{ + unsigned charsetPos = 0, charsetLen = 0; + size_t pos = 0; + unsigned length = mediaType.length(); + + while (pos < length) { + pos = mediaType.findIgnoringASCIICase("charset", pos); + if (pos == notFound || pos == 0) { + charsetLen = 0; + break; + } + + // is what we found a beginning of a word? + if (mediaType[pos-1] > ' ' && mediaType[pos-1] != ';') { + pos += 7; + continue; + } + + pos += 7; + + // skip whitespace + while (pos != length && mediaType[pos] <= ' ') + ++pos; + + if (mediaType[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others + continue; + + while (pos != length && (mediaType[pos] <= ' ' || mediaType[pos] == '"' || mediaType[pos] == '\'')) + ++pos; + + // we don't handle spaces within quoted parameter values, because charset names cannot have any + unsigned endpos = pos; + while (pos != length && mediaType[endpos] > ' ' && mediaType[endpos] != '"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';') + ++endpos; + + charsetPos = pos; + charsetLen = endpos - pos; + break; + } + return mediaType.substring(charsetPos, charsetLen); +} + +XSSProtectionDisposition parseXSSProtectionHeader(const String& header, String& failureReason, unsigned& failurePosition, String& reportURL) +{ + static NeverDestroyed<String> failureReasonInvalidToggle(MAKE_STATIC_STRING_IMPL("expected 0 or 1")); + static NeverDestroyed<String> failureReasonInvalidSeparator(MAKE_STATIC_STRING_IMPL("expected semicolon")); + static NeverDestroyed<String> failureReasonInvalidEquals(MAKE_STATIC_STRING_IMPL("expected equals sign")); + static NeverDestroyed<String> failureReasonInvalidMode(MAKE_STATIC_STRING_IMPL("invalid mode directive")); + static NeverDestroyed<String> failureReasonInvalidReport(MAKE_STATIC_STRING_IMPL("invalid report directive")); + static NeverDestroyed<String> failureReasonDuplicateMode(MAKE_STATIC_STRING_IMPL("duplicate mode directive")); + static NeverDestroyed<String> failureReasonDuplicateReport(MAKE_STATIC_STRING_IMPL("duplicate report directive")); + static NeverDestroyed<String> failureReasonInvalidDirective(MAKE_STATIC_STRING_IMPL("unrecognized directive")); + + unsigned pos = 0; + + if (!skipWhiteSpace(header, pos)) + return XSSProtectionDisposition::Enabled; + + if (header[pos] == '0') + return XSSProtectionDisposition::Disabled; + + if (header[pos++] != '1') { + failureReason = failureReasonInvalidToggle; + return XSSProtectionDisposition::Invalid; + } + + XSSProtectionDisposition result = XSSProtectionDisposition::Enabled; + bool modeDirectiveSeen = false; + bool reportDirectiveSeen = false; + + while (1) { + // At end of previous directive: consume whitespace, semicolon, and whitespace. + if (!skipWhiteSpace(header, pos)) + return result; + + if (header[pos++] != ';') { + failureReason = failureReasonInvalidSeparator; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + + if (!skipWhiteSpace(header, pos)) + return result; + + // At start of next directive. + if (skipToken(header, pos, "mode")) { + if (modeDirectiveSeen) { + failureReason = failureReasonDuplicateMode; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + modeDirectiveSeen = true; + if (!skipEquals(header, pos)) { + failureReason = failureReasonInvalidEquals; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + if (!skipToken(header, pos, "block")) { + failureReason = failureReasonInvalidMode; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + result = XSSProtectionDisposition::BlockEnabled; + } else if (skipToken(header, pos, "report")) { + if (reportDirectiveSeen) { + failureReason = failureReasonDuplicateReport; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + reportDirectiveSeen = true; + if (!skipEquals(header, pos)) { + failureReason = failureReasonInvalidEquals; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + size_t startPos = pos; + if (!skipValue(header, pos)) { + failureReason = failureReasonInvalidReport; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + reportURL = header.substring(startPos, pos - startPos); + failurePosition = startPos; // If later semantic check deems unacceptable. + } else { + failureReason = failureReasonInvalidDirective; + failurePosition = pos; + return XSSProtectionDisposition::Invalid; + } + } +} + +ContentTypeOptionsDisposition parseContentTypeOptionsHeader(StringView header) +{ + StringView leftToken = header.left(header.find(',')); + if (equalLettersIgnoringASCIICase(stripLeadingAndTrailingHTTPSpaces(leftToken), "nosniff")) + return ContentTypeOptionsDisposition::Nosniff; + return ContentTypeOptionsDisposition::None; +} + +// For example: "HTTP/1.1 200 OK" => "OK". +// Note that HTTP/2 does not include a reason phrase, so we return the empty atom. +AtomString extractReasonPhraseFromHTTPStatusLine(const String& statusLine) +{ + StringView view = statusLine; + size_t spacePos = view.find(' '); + + // Remove status code from the status line. + spacePos = view.find(' ', spacePos + 1); + if (spacePos == notFound) + return emptyAtom(); + + return view.substring(spacePos + 1).toAtomString(); +} + +XFrameOptionsDisposition parseXFrameOptionsHeader(StringView header) +{ + XFrameOptionsDisposition result = XFrameOptionsDisposition::None; + + if (header.isEmpty()) + return result; + + for (auto currentHeader : header.split(',')) { + currentHeader = currentHeader.stripWhiteSpace(); + XFrameOptionsDisposition currentValue = XFrameOptionsDisposition::None; + if (equalLettersIgnoringASCIICase(currentHeader, "deny")) + currentValue = XFrameOptionsDisposition::Deny; + else if (equalLettersIgnoringASCIICase(currentHeader, "sameorigin")) + currentValue = XFrameOptionsDisposition::SameOrigin; + else if (equalLettersIgnoringASCIICase(currentHeader, "allowall")) + currentValue = XFrameOptionsDisposition::AllowAll; + else + currentValue = XFrameOptionsDisposition::Invalid; + + if (result == XFrameOptionsDisposition::None) + result = currentValue; + else if (result != currentValue) + return XFrameOptionsDisposition::Conflict; + } + return result; +} + +// https://fetch.spec.whatwg.org/#concept-header-list-get-structured-header +// FIXME: For now, this assumes the type is "item". +std::optional<std::pair<StringView, HashMap<String, String>>> parseStructuredFieldValue(StringView header) +{ + header = stripLeadingAndTrailingHTTPSpaces(header); + if (header.isEmpty()) + return std::nullopt; + + // Parse a token (https://datatracker.ietf.org/doc/html/rfc8941#section-4.2.6). + if (!isASCIIAlpha(header[0]) && header[0] != '*') + return std::nullopt; + size_t index = 1; + while (index < header.length()) { + UChar c = header[index]; + if (!RFC7230::isTokenCharacter(c) && c != ':' && c != '/') + break; + ++index; + } + StringView bareItem = header.substring(0, index); + + // Parse parameters (https://datatracker.ietf.org/doc/html/rfc8941#section-4.2.3.2). + HashMap<String, String> parameters; + while (index < header.length()) { + if (header[index] != ';') + break; + ++index; // Consume ';'. + while (index < header.length() && header[index] == ' ') + ++index; + if (index == header.length()) + return std::nullopt; + // Parse a key (https://datatracker.ietf.org/doc/html/rfc8941#section-4.2.3.3) + if (!isASCIILower(header[index])) + return std::nullopt; + size_t keyStart = index++; + while (index < header.length()) { + UChar c = header[index]; + if (!isASCIILower(c) && !isASCIIDigit(c) && c != '_' && c != '-' && c != '.' && c != '*') + break; + ++index; + } + String key = header.substring(keyStart, index - keyStart).toString(); + String value = "true"; + if (index < header.length() && header[index] == '=') { + ++index; // Consume '='. + if (isASCIIAlpha(header[index]) || header[index] == '*') { + // https://datatracker.ietf.org/doc/html/rfc8941#section-4.2.6 + size_t valueStart = index++; + while (index < header.length()) { + UChar c = header[index]; + if (!RFC7230::isTokenCharacter(c) && c != ':' && c != '/') + break; + ++index; + } + value = header.substring(valueStart, index - valueStart).toString(); + } else if (header[index] == '"') { + // https://datatracker.ietf.org/doc/html/rfc8941#section-4.2.5 + StringBuilder valueBuilder; + ++index; // Skip DQUOTE. + while (index < header.length()) { + if (header[index] == '\\') { + ++index; + if (index == header.length()) + return std::nullopt; + if (header[index] != '\\' && header[index] != '"') + return std::nullopt; + valueBuilder.append(header[index]); + } else if (header[index] == '\"') { + value = valueBuilder.toString(); + break; + } else if (header[index] <= 0x1F || (header[index] >= 0x7F && header[index] <= 0xFF)) // Not in VCHAR or SP. + return std::nullopt; + else + valueBuilder.append(header[index]); + ++index; + } + if (index == header.length()) + return std::nullopt; + ++index; // Skip DQUOTE. + } else + return std::nullopt; + } + parameters.set(WTFMove(key), WTFMove(value)); + } + if (index != header.length()) + return std::nullopt; + return std::make_pair(bareItem, parameters); +} + +bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd, long long& rangeSuffixLength) +{ + // The format of "Range" header is defined in RFC 2616 Section 14.35.1. + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 + // We don't support multiple range requests. + + rangeOffset = rangeEnd = rangeSuffixLength = -1; + + // The "bytes" unit identifier should be present. + static const unsigned bytesLength = 6; + if (!startsWithLettersIgnoringASCIICase(range, "bytes=")) + return false; + // FIXME: The rest of this should use StringView. + String byteRange = range.substring(bytesLength); + + // The '-' character needs to be present. + int index = byteRange.find('-'); + if (index == -1) + return false; + + // If the '-' character is at the beginning, the suffix length, which specifies the last N bytes, is provided. + // Example: + // -500 + if (!index) { + if (auto value = parseInteger<long long>(StringView { byteRange }.substring(index + 1))) + rangeSuffixLength = *value; + return true; + } + + // Otherwise, the first-byte-position and the last-byte-position are provied. + // Examples: + // 0-499 + // 500- + auto firstBytePos = parseInteger<long long>(StringView { byteRange }.left(index)); + if (!firstBytePos) + return false; + + auto lastBytePosStr = stripLeadingAndTrailingHTTPSpaces(StringView { byteRange }.substring(index + 1)); + long long lastBytePos = -1; + if (!lastBytePosStr.isEmpty()) { + auto value = parseInteger<long long>(lastBytePosStr); + if (!value) + return false; + lastBytePos = *value; + } + + if (*firstBytePos < 0 || !(lastBytePos == -1 || lastBytePos >= *firstBytePos)) + return false; + + rangeOffset = *firstBytePos; + rangeEnd = lastBytePos; + return true; +} + +template<typename CharacterType> +static inline bool isValidHeaderNameCharacter(CharacterType character) +{ + // https://tools.ietf.org/html/rfc7230#section-3.2 + // A header name should only contain one or more of + // alphanumeric or ! # $ % & ' * + - . ^ _ ` | ~ + if (isASCIIAlphanumeric(character)) + return true; + switch (character) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '-': + case '.': + case '^': + case '_': + case '`': + case '|': + case '~': + return true; + default: + return false; + } +} + +size_t parseHTTPHeader(const uint8_t* start, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict) +{ + auto p = start; + auto end = start + length; + + Vector<uint8_t> name; + Vector<uint8_t> value; + + bool foundFirstNameChar = false; + const uint8_t* namePtr = nullptr; + size_t nameSize = 0; + + nameStr = StringView(); + valueStr = String(); + + for (; p < end; p++) { + switch (*p) { + case '\r': + if (name.isEmpty()) { + if (p + 1 < end && *(p + 1) == '\n') + return (p + 2) - start; + failureReason = makeString("CR doesn't follow LF in header name at ", trimInputSample(p, end - p)); + return 0; + } + failureReason = makeString("Unexpected CR in header name at ", trimInputSample(name.data(), name.size())); + return 0; + case '\n': + failureReason = makeString("Unexpected LF in header name at ", trimInputSample(name.data(), name.size())); + return 0; + case ':': + break; + default: + if (!isValidHeaderNameCharacter(*p)) { + if (name.size() < 1) + failureReason = "Unexpected start character in header name"; + else + failureReason = makeString("Unexpected character in header name at ", trimInputSample(name.data(), name.size())); + return 0; + } + name.append(*p); + if (!foundFirstNameChar) { + namePtr = p; + foundFirstNameChar = true; + } + continue; + } + if (*p == ':') { + ++p; + break; + } + } + + nameSize = name.size(); + nameStr = StringView(namePtr, nameSize); + + for (; p < end && *p == 0x20; p++) { } + + for (; p < end; p++) { + switch (*p) { + case '\r': + break; + case '\n': + if (strict) { + failureReason = makeString("Unexpected LF in header value at ", trimInputSample(value.data(), value.size())); + return 0; + } + break; + default: + value.append(*p); + } + if (*p == '\r' || (!strict && *p == '\n')) { + ++p; + break; + } + } + if (p >= end || (strict && *p != '\n')) { + failureReason = makeString("CR doesn't follow LF after header value at ", trimInputSample(p, end - p)); + return 0; + } + valueStr = String::fromUTF8(value.data(), value.size()); + if (valueStr.isNull()) { + failureReason = "Invalid UTF-8 sequence in header value"_s; + return 0; + } + return p - start; +} + +size_t parseHTTPRequestBody(const uint8_t* data, size_t length, Vector<uint8_t>& body) +{ + body.clear(); + body.append(data, length); + + return length; +} + +// Implements <https://fetch.spec.whatwg.org/#forbidden-header-name>. +bool isForbiddenHeaderName(const String& name) +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) { + switch (headerName) { + case HTTPHeaderName::AcceptCharset: + case HTTPHeaderName::AcceptEncoding: + case HTTPHeaderName::AccessControlRequestHeaders: + case HTTPHeaderName::AccessControlRequestMethod: + case HTTPHeaderName::Connection: + case HTTPHeaderName::ContentLength: + case HTTPHeaderName::Cookie: + case HTTPHeaderName::Cookie2: + case HTTPHeaderName::Date: + case HTTPHeaderName::DNT: + case HTTPHeaderName::Expect: + case HTTPHeaderName::Host: + case HTTPHeaderName::KeepAlive: + case HTTPHeaderName::Origin: + case HTTPHeaderName::Referer: + case HTTPHeaderName::TE: + case HTTPHeaderName::Trailer: + case HTTPHeaderName::TransferEncoding: + case HTTPHeaderName::Upgrade: + case HTTPHeaderName::Via: + return true; + default: + break; + } + } + return startsWithLettersIgnoringASCIICase(name, "sec-") || startsWithLettersIgnoringASCIICase(name, "proxy-"); +} + +// Implements <https://fetch.spec.whatwg.org/#no-cors-safelisted-request-header-name>. +bool isNoCORSSafelistedRequestHeaderName(const String& name) +{ + HTTPHeaderName headerName; + if (findHTTPHeaderName(name, headerName)) { + switch (headerName) { + case HTTPHeaderName::Accept: + case HTTPHeaderName::AcceptLanguage: + case HTTPHeaderName::ContentLanguage: + case HTTPHeaderName::ContentType: + return true; + default: + break; + } + } + return false; +} + +// Implements <https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name>. +bool isPriviledgedNoCORSRequestHeaderName(const String& name) +{ + return equalLettersIgnoringASCIICase(name, "range"); +} + +// Implements <https://fetch.spec.whatwg.org/#forbidden-response-header-name>. +bool isForbiddenResponseHeaderName(const String& name) +{ + return equalLettersIgnoringASCIICase(name, "set-cookie") || equalLettersIgnoringASCIICase(name, "set-cookie2"); +} + +// Implements <https://fetch.spec.whatwg.org/#forbidden-method>. +bool isForbiddenMethod(const String& name) +{ + return equalLettersIgnoringASCIICase(name, "connect") || equalLettersIgnoringASCIICase(name, "trace") || equalLettersIgnoringASCIICase(name, "track"); +} + +bool isSimpleHeader(const String& name, const String& value) +{ + HTTPHeaderName headerName; + if (!findHTTPHeaderName(name, headerName)) + return false; + return isCrossOriginSafeRequestHeader(headerName, value); +} + +bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet) +{ + switch (name) { + case HTTPHeaderName::CacheControl: + case HTTPHeaderName::ContentLanguage: + case HTTPHeaderName::ContentLength: + case HTTPHeaderName::ContentType: + case HTTPHeaderName::Expires: + case HTTPHeaderName::LastModified: + case HTTPHeaderName::Pragma: + case HTTPHeaderName::Accept: + return true; + case HTTPHeaderName::SetCookie: + case HTTPHeaderName::SetCookie2: + return false; + default: + break; + } + return accessControlExposeHeaderSet.contains(httpHeaderNameString(name).toStringWithoutCopying()); +} + +bool isCrossOriginSafeHeader(const String& name, const HTTPHeaderSet& accessControlExposeHeaderSet) +{ +#if ASSERT_ENABLED + HTTPHeaderName headerName; + ASSERT(!findHTTPHeaderName(name, headerName)); +#endif + return accessControlExposeHeaderSet.contains(name); +} + +// Implements https://fetch.spec.whatwg.org/#cors-safelisted-request-header +bool isCrossOriginSafeRequestHeader(HTTPHeaderName name, const String& value) +{ + switch (name) { + case HTTPHeaderName::Accept: + if (!isValidAcceptHeaderValue(value)) + return false; + break; + case HTTPHeaderName::AcceptLanguage: + case HTTPHeaderName::ContentLanguage: + if (!isValidLanguageHeaderValue(value)) + return false; + break; + case HTTPHeaderName::ContentType: { + // Preflight is required for MIME types that can not be sent via form submission. + if (containsCORSUnsafeRequestHeaderBytes(value)) + return false; + auto parsedContentType = ParsedContentType::create(value); + if (!parsedContentType) + return false; + String mimeType = parsedContentType->mimeType(); + if (!(equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain"))) + return false; + break; + } + default: + // FIXME: Should we also make safe other headers (DPR, Downlink, Save-Data...)? That would require validating their values. + return false; + } + return value.length() <= 128; +} + +// Implements <https://fetch.spec.whatwg.org/#concept-method-normalize>. +String normalizeHTTPMethod(const String& method) +{ + const ASCIILiteral methods[] = { "DELETE"_s, "GET"_s, "HEAD"_s, "OPTIONS"_s, "POST"_s, "PUT"_s }; + for (auto value : methods) { + if (equalIgnoringASCIICase(method, value.characters())) { + // Don't bother allocating a new string if it's already all uppercase. + if (method == value) + break; + return value; + } + } + return method; +} + +// Defined by https://tools.ietf.org/html/rfc7231#section-4.2.1 +bool isSafeMethod(const String& method) +{ + const ASCIILiteral safeMethods[] = { "GET"_s, "HEAD"_s, "OPTIONS"_s, "TRACE"_s }; + for (auto value : safeMethods) { + if (equalIgnoringASCIICase(method, value.characters())) + return true; + } + return false; +} + +CrossOriginResourcePolicy parseCrossOriginResourcePolicyHeader(StringView header) +{ + auto strippedHeader = stripLeadingAndTrailingHTTPSpaces(header); + + if (strippedHeader.isEmpty()) + return CrossOriginResourcePolicy::None; + + if (strippedHeader == "same-origin") + return CrossOriginResourcePolicy::SameOrigin; + + if (strippedHeader == "same-site") + return CrossOriginResourcePolicy::SameSite; + + if (strippedHeader == "cross-origin") + return CrossOriginResourcePolicy::CrossOrigin; + + return CrossOriginResourcePolicy::Invalid; +} + +} diff --git a/src/javascript/jsc/bindings/webcore/HTTPParsers.h b/src/javascript/jsc/bindings/webcore/HTTPParsers.h new file mode 100644 index 000000000..920a5a5b6 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/HTTPParsers.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2011 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/HashSet.h> +#include <wtf/WallTime.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +typedef HashSet<String, ASCIICaseInsensitiveHash> HTTPHeaderSet; + +enum class HTTPHeaderName; + +enum class XSSProtectionDisposition { + Invalid, + Disabled, + Enabled, + BlockEnabled, +}; + +enum class ContentTypeOptionsDisposition : bool { + None, + Nosniff +}; + +enum class XFrameOptionsDisposition : uint8_t { + None, + Deny, + SameOrigin, + AllowAll, + Invalid, + Conflict +}; + +enum class CrossOriginResourcePolicy : uint8_t { + None, + CrossOrigin, + SameOrigin, + SameSite, + Invalid +}; + +bool isValidReasonPhrase(const String&); +bool isValidHTTPHeaderValue(const String&); +bool isValidAcceptHeaderValue(const String&); +bool isValidLanguageHeaderValue(const String&); +#if USE(GLIB) +WEBCORE_EXPORT bool isValidUserAgentHeaderValue(const String&); +#endif +bool isValidHTTPToken(const String&); +bool isValidHTTPToken(StringView); +std::optional<WallTime> parseHTTPDate(const String&); +String filenameFromHTTPContentDisposition(StringView); +WEBCORE_EXPORT String extractMIMETypeFromMediaType(const String&); +String extractCharsetFromMediaType(const String&); +XSSProtectionDisposition parseXSSProtectionHeader(const String& header, String& failureReason, unsigned& failurePosition, String& reportURL); +AtomString extractReasonPhraseFromHTTPStatusLine(const String&); +WEBCORE_EXPORT XFrameOptionsDisposition parseXFrameOptionsHeader(StringView); +std::optional<std::pair<StringView, HashMap<String, String>>> parseStructuredFieldValue(StringView header); + +// -1 could be set to one of the return parameters to indicate the value is not specified. +WEBCORE_EXPORT bool parseRange(const String&, long long& rangeOffset, long long& rangeEnd, long long& rangeSuffixLength); + +ContentTypeOptionsDisposition parseContentTypeOptionsHeader(StringView header); + +// Parsing Complete HTTP Messages. +size_t parseHTTPHeader(const uint8_t* data, size_t length, String& failureReason, StringView& nameStr, String& valueStr, bool strict = true); +size_t parseHTTPRequestBody(const uint8_t* data, size_t length, Vector<uint8_t>& body); + +// HTTP Header routine as per https://fetch.spec.whatwg.org/#terminology-headers +bool isForbiddenHeaderName(const String&); +bool isNoCORSSafelistedRequestHeaderName(const String&); +bool isPriviledgedNoCORSRequestHeaderName(const String&); +bool isForbiddenResponseHeaderName(const String&); +bool isForbiddenMethod(const String&); +bool isSimpleHeader(const String& name, const String& value); +bool isCrossOriginSafeHeader(HTTPHeaderName, const HTTPHeaderSet&); +bool isCrossOriginSafeHeader(const String&, const HTTPHeaderSet&); +bool isCrossOriginSafeRequestHeader(HTTPHeaderName, const String&); + +String normalizeHTTPMethod(const String&); +bool isSafeMethod(const String&); + +WEBCORE_EXPORT CrossOriginResourcePolicy parseCrossOriginResourcePolicyHeader(StringView); + +inline bool isHTTPSpace(UChar character) +{ + return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r'); +} + +// Strip leading and trailing whitespace as defined in https://fetch.spec.whatwg.org/#concept-header-value-normalize. +inline String stripLeadingAndTrailingHTTPSpaces(const String& string) +{ + return string.stripLeadingAndTrailingCharacters(isHTTPSpace); +} + +inline StringView stripLeadingAndTrailingHTTPSpaces(StringView string) +{ + return string.stripLeadingAndTrailingMatchedCharacters(isHTTPSpace); +} + +template<class HashType> +bool addToAccessControlAllowList(const String& string, unsigned start, unsigned end, HashSet<String, HashType>& set) +{ + StringImpl* stringImpl = string.impl(); + if (!stringImpl) + return true; + + // Skip white space from start. + while (start <= end && isHTTPSpace((*stringImpl)[start])) + ++start; + + // only white space + if (start > end) + return true; + + // Skip white space from end. + while (end && isHTTPSpace((*stringImpl)[end])) + --end; + + auto token = string.substring(start, end - start + 1); + if (!isValidHTTPToken(token)) + return false; + + set.add(WTFMove(token)); + return true; +} + +template<class HashType = DefaultHash<String>> +std::optional<HashSet<String, HashType>> parseAccessControlAllowList(const String& string) +{ + HashSet<String, HashType> set; + unsigned start = 0; + size_t end; + while ((end = string.find(',', start)) != notFound) { + if (start != end) { + if (!addToAccessControlAllowList(string, start, end - 1, set)) + return { }; + } + start = end + 1; + } + if (start != string.length()) { + if (!addToAccessControlAllowList(string, start, string.length() - 1, set)) + return { }; + } + return set; +} + +} diff --git a/src/javascript/jsc/bindings/webcore/JSFetchHeaders.cpp b/src/javascript/jsc/bindings/webcore/JSFetchHeaders.cpp new file mode 100644 index 000000000..91d79388b --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/JSFetchHeaders.cpp @@ -0,0 +1,502 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "config.h" +#include "JSFetchHeaders.h" + +#include "ActiveDOMObject.h" +#include "ExtendedDOMClientIsoSubspaces.h" +#include "ExtendedDOMIsoSubspaces.h" +#include "IDLTypes.h" +#include "JSDOMBinding.h" +#include "JSDOMConstructor.h" +#include "JSDOMConvertBase.h" +#include "JSDOMConvertBoolean.h" +#include "JSDOMConvertInterface.h" +#include "JSDOMConvertNullable.h" +#include "JSDOMConvertRecord.h" +#include "JSDOMConvertSequences.h" +#include "JSDOMConvertStrings.h" +#include "JSDOMConvertUnion.h" +#include "JSDOMExceptionHandling.h" +#include "JSDOMGlobalObjectInlines.h" +#include "JSDOMIterator.h" +#include "JSDOMOperation.h" +#include "JSDOMWrapperCache.h" +#include "ScriptExecutionContext.h" +#include "WebCoreJSClientData.h" +#include <JavaScriptCore/BuiltinNames.h> +#include <JavaScriptCore/FunctionPrototype.h> +#include <JavaScriptCore/HeapAnalyzer.h> +#include <JavaScriptCore/JSArray.h> +#include <JavaScriptCore/JSCInlines.h> +#include <JavaScriptCore/JSDestructibleObjectHeapCellType.h> +#include <JavaScriptCore/SlotVisitorMacros.h> +#include <JavaScriptCore/SubspaceInlines.h> +#include <variant> +#include <wtf/GetPtr.h> +#include <wtf/PointerPreparations.h> +#include <wtf/URL.h> +#include <wtf/Vector.h> + +namespace WebCore { +using namespace JSC; + +// Functions + +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_append); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_delete); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_get); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_has); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_set); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_entries); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_keys); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_values); +static JSC_DECLARE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_forEach); + +// Attributes + +static JSC_DECLARE_CUSTOM_GETTER(jsFetchHeadersConstructor); + +class JSFetchHeadersPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSFetchHeadersPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure) + { + JSFetchHeadersPrototype* ptr = new (NotNull, JSC::allocateCell<JSFetchHeadersPrototype>(vm)) JSFetchHeadersPrototype(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(JSFetchHeadersPrototype, 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: + JSFetchHeadersPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure) + : JSC::JSNonFinalObject(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSFetchHeadersPrototype, JSFetchHeadersPrototype::Base); + +using JSFetchHeadersDOMConstructor = JSDOMConstructor<JSFetchHeaders>; + +template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSFetchHeadersDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* castedThis = jsCast<JSFetchHeadersDOMConstructor*>(callFrame->jsCallee()); + ASSERT(castedThis); + EnsureStillAliveScope argument0 = callFrame->argument(0); + auto init = argument0.value().isUndefined() ? std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>() : std::optional<Converter<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>::ReturnType>(convert<IDLUnion<IDLSequence<IDLSequence<IDLByteString>>, IDLRecord<IDLByteString, IDLByteString>>>(*lexicalGlobalObject, argument0.value())); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + auto object = FetchHeaders::create(WTFMove(init)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef); + auto jsValue = toJSNewlyCreated<IDLInterface<FetchHeaders>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + setSubclassStructureIfNeeded<FetchHeaders>(lexicalGlobalObject, callFrame, asObject(jsValue)); + RETURN_IF_EXCEPTION(throwScope, {}); + return JSValue::encode(jsValue); +} +JSC_ANNOTATE_HOST_FUNCTION(JSFetchHeadersDOMConstructorConstruct, JSFetchHeadersDOMConstructor::construct); + +template<> const ClassInfo JSFetchHeadersDOMConstructor::s_info = { "Headers"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFetchHeadersDOMConstructor) }; + +template<> JSValue JSFetchHeadersDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject) +{ + UNUSED_PARAM(vm); + return globalObject.functionPrototype(); +} + +template<> void JSFetchHeadersDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject) +{ + putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + JSString* nameString = jsNontrivialString(vm, "Headers"_s); + m_originalName.set(vm, this, nameString); + putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + putDirect(vm, vm.propertyNames->prototype, JSFetchHeaders::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); +} + +/* Hash table for prototype */ + +static const HashTableValue JSFetchHeadersPrototypeTableValues[] = { + { "constructor", static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsFetchHeadersConstructor), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, + { "append", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_append), (intptr_t)(2) } }, + { "delete", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_delete), (intptr_t)(1) } }, + { "get", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_get), (intptr_t)(1) } }, + { "has", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_has), (intptr_t)(1) } }, + { "set", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_set), (intptr_t)(2) } }, + { "entries", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_entries), (intptr_t)(0) } }, + { "keys", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_keys), (intptr_t)(0) } }, + { "values", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_values), (intptr_t)(0) } }, + { "forEach", static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsFetchHeadersPrototypeFunction_forEach), (intptr_t)(1) } }, +}; + +const ClassInfo JSFetchHeadersPrototype::s_info = { "Headers"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFetchHeadersPrototype) }; + +void JSFetchHeadersPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSFetchHeaders::info(), JSFetchHeadersPrototypeTableValues, *this); + putDirect(vm, vm.propertyNames->iteratorSymbol, getDirect(vm, vm.propertyNames->builtinNames().entriesPublicName()), static_cast<unsigned>(JSC::PropertyAttribute::DontEnum)); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +const ClassInfo JSFetchHeaders::s_info = { "Headers"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSFetchHeaders) }; + +JSFetchHeaders::JSFetchHeaders(Structure* structure, JSDOMGlobalObject& globalObject, Ref<FetchHeaders>&& impl) + : JSDOMWrapper<FetchHeaders>(structure, globalObject, WTFMove(impl)) +{ +} + +void JSFetchHeaders::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(vm, info())); + + // static_assert(!std::is_base_of<ActiveDOMObject, FetchHeaders>::value, "Interface is not marked as [ActiveDOMObject] even though implementation class subclasses ActiveDOMObject."); +} + +JSObject* JSFetchHeaders::createPrototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return JSFetchHeadersPrototype::create(vm, &globalObject, JSFetchHeadersPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype())); +} + +JSObject* JSFetchHeaders::prototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return getDOMPrototype<JSFetchHeaders>(vm, globalObject); +} + +JSValue JSFetchHeaders::getConstructor(VM& vm, const JSGlobalObject* globalObject) +{ + return getDOMConstructor<JSFetchHeadersDOMConstructor, DOMConstructorID::FetchHeaders>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject)); +} + +void JSFetchHeaders::destroy(JSC::JSCell* cell) +{ + JSFetchHeaders* thisObject = static_cast<JSFetchHeaders*>(cell); + thisObject->JSFetchHeaders::~JSFetchHeaders(); +} + +JSC_DEFINE_CUSTOM_GETTER(jsFetchHeadersConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* prototype = jsDynamicCast<JSFetchHeadersPrototype*>(vm, JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(JSFetchHeaders::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject())); +} + +static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_appendBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSFetchHeaders>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + if (UNLIKELY(callFrame->argumentCount() < 2)) + return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + auto value = convert<IDLByteString>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.append(WTFMove(name), WTFMove(value)); }))); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_append, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_appendBody>(*lexicalGlobalObject, *callFrame, "append"); +} + +static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_deleteBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSFetchHeaders>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + if (UNLIKELY(callFrame->argumentCount() < 1)) + return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.remove(WTFMove(name)); }))); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_delete, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_deleteBody>(*lexicalGlobalObject, *callFrame, "delete"); +} + +static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_getBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSFetchHeaders>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + if (UNLIKELY(callFrame->argumentCount() < 1)) + return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLNullable<IDLByteString>>(*lexicalGlobalObject, throwScope, impl.get(WTFMove(name))))); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_get, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_getBody>(*lexicalGlobalObject, *callFrame, "get"); +} + +static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_hasBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSFetchHeaders>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + if (UNLIKELY(callFrame->argumentCount() < 1)) + return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLBoolean>(*lexicalGlobalObject, throwScope, impl.has(WTFMove(name))))); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_has, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_hasBody>(*lexicalGlobalObject, *callFrame, "has"); +} + +static inline JSC::EncodedJSValue jsFetchHeadersPrototypeFunction_setBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSFetchHeaders>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + if (UNLIKELY(callFrame->argumentCount() < 2)) + return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLByteString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + auto value = convert<IDLByteString>(*lexicalGlobalObject, argument1.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.set(WTFMove(name), WTFMove(value)); }))); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_set, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_setBody>(*lexicalGlobalObject, *callFrame, "set"); +} + +struct FetchHeadersIteratorTraits { + static constexpr JSDOMIteratorType type = JSDOMIteratorType::Map; + using KeyType = IDLByteString; + using ValueType = IDLByteString; +}; + +using FetchHeadersIteratorBase = JSDOMIteratorBase<JSFetchHeaders, FetchHeadersIteratorTraits>; +class FetchHeadersIterator final : public FetchHeadersIteratorBase { +public: + using Base = FetchHeadersIteratorBase; + 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<FetchHeadersIterator, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForFetchHeadersIterator.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForFetchHeadersIterator = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForFetchHeadersIterator.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForFetchHeadersIterator = WTFMove(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 FetchHeadersIterator* create(JSC::VM& vm, JSC::Structure* structure, JSFetchHeaders& iteratedObject, IterationKind kind) + { + auto* instance = new (NotNull, JSC::allocateCell<FetchHeadersIterator>(vm)) FetchHeadersIterator(structure, iteratedObject, kind); + instance->finishCreation(vm); + return instance; + } + +private: + FetchHeadersIterator(JSC::Structure* structure, JSFetchHeaders& iteratedObject, IterationKind kind) + : Base(structure, iteratedObject, kind) + { + } +}; + +using FetchHeadersIteratorPrototype = JSDOMIteratorPrototype<JSFetchHeaders, FetchHeadersIteratorTraits>; +JSC_ANNOTATE_HOST_FUNCTION(FetchHeadersIteratorPrototypeNext, FetchHeadersIteratorPrototype::next); + +template<> +const JSC::ClassInfo FetchHeadersIteratorBase::s_info = { "Headers Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(FetchHeadersIteratorBase) }; +const JSC::ClassInfo FetchHeadersIterator::s_info = { "Headers Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(FetchHeadersIterator) }; + +template<> +const JSC::ClassInfo FetchHeadersIteratorPrototype::s_info = { "Headers Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(FetchHeadersIteratorPrototype) }; + +static inline EncodedJSValue jsFetchHeadersPrototypeFunction_entriesCaller(JSGlobalObject*, CallFrame*, JSFetchHeaders* thisObject) +{ + return JSValue::encode(iteratorCreate<FetchHeadersIterator>(*thisObject, IterationKind::Entries)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_entries, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_entriesCaller>(*lexicalGlobalObject, *callFrame, "entries"); +} + +static inline EncodedJSValue jsFetchHeadersPrototypeFunction_keysCaller(JSGlobalObject*, CallFrame*, JSFetchHeaders* thisObject) +{ + return JSValue::encode(iteratorCreate<FetchHeadersIterator>(*thisObject, IterationKind::Keys)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_keys, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_keysCaller>(*lexicalGlobalObject, *callFrame, "keys"); +} + +static inline EncodedJSValue jsFetchHeadersPrototypeFunction_valuesCaller(JSGlobalObject*, CallFrame*, JSFetchHeaders* thisObject) +{ + return JSValue::encode(iteratorCreate<FetchHeadersIterator>(*thisObject, IterationKind::Values)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_values, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_valuesCaller>(*lexicalGlobalObject, *callFrame, "values"); +} + +static inline EncodedJSValue jsFetchHeadersPrototypeFunction_forEachCaller(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame, JSFetchHeaders* thisObject) +{ + return JSValue::encode(iteratorForEach<FetchHeadersIterator>(*lexicalGlobalObject, *callFrame, *thisObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFetchHeadersPrototypeFunction_forEach, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSFetchHeaders>::call<jsFetchHeadersPrototypeFunction_forEachCaller>(*lexicalGlobalObject, *callFrame, "forEach"); +} + +JSC::GCClient::IsoSubspace* JSFetchHeaders::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSFetchHeaders, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForFetchHeaders.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForFetchHeaders = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForFetchHeaders.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForFetchHeaders = WTFMove(space); }); +} + +void JSFetchHeaders::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSFetchHeaders*>(cell); + analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); + if (thisObject->scriptExecutionContext()) + analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + Base::analyzeHeap(cell, analyzer); +} + +bool JSFetchHeadersOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason) +{ + UNUSED_PARAM(handle); + UNUSED_PARAM(visitor); + UNUSED_PARAM(reason); + return false; +} + +void JSFetchHeadersOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context) +{ + auto* jsFetchHeaders = static_cast<JSFetchHeaders*>(handle.slot()->asCell()); + auto& world = *static_cast<DOMWrapperWorld*>(context); + uncacheWrapper(world, &jsFetchHeaders->wrapped(), jsFetchHeaders); +} + +#if ENABLE(BINDING_INTEGRITY) +#if PLATFORM(WIN) +#pragma warning(disable : 4483) +extern "C" { +extern void (*const __identifier("??_7FetchHeaders@WebCore@@6B@")[])(); +} +#else +extern "C" { +extern void* _ZTVN7WebCore12FetchHeadersE[]; +} +#endif +#endif + +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<FetchHeaders>&& impl) +{ + + if constexpr (std::is_polymorphic_v<FetchHeaders>) { +#if ENABLE(BINDING_INTEGRITY) + const void* actualVTablePointer = getVTablePointer(impl.ptr()); +#if PLATFORM(WIN) + void* expectedVTablePointer = __identifier("??_7FetchHeaders@WebCore@@6B@"); +#else + void* expectedVTablePointer = &_ZTVN7WebCore12FetchHeadersE[2]; +#endif + + // If you hit this assertion you either have a use after free bug, or + // FetchHeaders has subclasses. If FetchHeaders has subclasses that get passed + // to toJS() we currently require FetchHeaders you to opt out of binding hardening + // by adding the SkipVTableValidation attribute to the interface IDL definition + RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer); +#endif + } + return createWrapper<FetchHeaders>(globalObject, WTFMove(impl)); +} + +JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, FetchHeaders& impl) +{ + return wrap(lexicalGlobalObject, globalObject, impl); +} + +FetchHeaders* JSFetchHeaders::toWrapped(JSC::VM& vm, JSC::JSValue value) +{ + if (auto* wrapper = jsDynamicCast<JSFetchHeaders*>(vm, value)) + return &wrapper->wrapped(); + return nullptr; +} + +} diff --git a/src/javascript/jsc/bindings/webcore/JSFetchHeaders.dep b/src/javascript/jsc/bindings/webcore/JSFetchHeaders.dep new file mode 100644 index 000000000..e553164bd --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/JSFetchHeaders.dep @@ -0,0 +1 @@ +JSFetchHeaders.h : diff --git a/src/javascript/jsc/bindings/webcore/JSFetchHeaders.h b/src/javascript/jsc/bindings/webcore/JSFetchHeaders.h new file mode 100644 index 000000000..4761e3a66 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/JSFetchHeaders.h @@ -0,0 +1,93 @@ +/* + This file is part of the WebKit open source project. + This file has been generated by generate-bindings.pl. DO NOT MODIFY! + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include "FetchHeaders.h" +#include "JSDOMWrapper.h" +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +class JSFetchHeaders : public JSDOMWrapper<FetchHeaders> { +public: + using Base = JSDOMWrapper<FetchHeaders>; + static JSFetchHeaders* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<FetchHeaders>&& impl) + { + JSFetchHeaders* ptr = new (NotNull, JSC::allocateCell<JSFetchHeaders>(globalObject->vm())) JSFetchHeaders(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 FetchHeaders* 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(JSC::ObjectType, StructureFlags), info(), JSC::NonArray); + } + + 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&); +protected: + JSFetchHeaders(JSC::Structure*, JSDOMGlobalObject&, Ref<FetchHeaders>&&); + + void finishCreation(JSC::VM&); +}; + +class JSFetchHeadersOwner final : public JSC::WeakHandleOwner { +public: + bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown>, void* context, JSC::AbstractSlotVisitor&, const char**) final; + void finalize(JSC::Handle<JSC::Unknown>, void* context) final; +}; + +inline JSC::WeakHandleOwner* wrapperOwner(DOMWrapperWorld&, FetchHeaders*) +{ + static NeverDestroyed<JSFetchHeadersOwner> owner; + return &owner.get(); +} + +inline void* wrapperKey(FetchHeaders* wrappableObject) +{ + return wrappableObject; +} + +JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, FetchHeaders&); +inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, FetchHeaders* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); } +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<FetchHeaders>&&); +inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<FetchHeaders>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); } + +template<> struct JSDOMWrapperConverterTraits<FetchHeaders> { + using WrapperClass = JSFetchHeaders; + using ToWrappedReturnType = FetchHeaders*; +}; + +} // namespace WebCore diff --git a/src/javascript/jsc/bindings/webcore/ParsedContentType.cpp b/src/javascript/jsc/bindings/webcore/ParsedContentType.cpp new file mode 100644 index 000000000..9ef2a1720 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/ParsedContentType.cpp @@ -0,0 +1,422 @@ + /* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ParsedContentType.h" + +#include "HTTPParsers.h" +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> + +namespace WebCore { + +static void skipSpaces(StringView input, unsigned& startIndex) +{ + while (startIndex < input.length() && isHTTPSpace(input[startIndex])) + ++startIndex; +} + +static bool isQuotedStringTokenCharacter(UChar c) +{ + return (c >= ' ' && c <= '~') || (c >= 0x80 && c <= 0xFF) || c == '\t'; +} + +static bool isTokenCharacter(UChar c) +{ + return isASCII(c) && c > ' ' && c != '"' && c != '(' && c != ')' && c != ',' && c != '/' && (c < ':' || c > '@') && (c < '[' || c > ']'); +} + +using CharacterMeetsCondition = bool (*)(UChar); + +static StringView parseToken(StringView input, unsigned& startIndex, CharacterMeetsCondition characterMeetsCondition, Mode mode, bool skipTrailingWhitespace = false) +{ + unsigned inputLength = input.length(); + unsigned tokenStart = startIndex; + unsigned& tokenEnd = startIndex; + + if (tokenEnd >= inputLength) + return StringView(); + + while (tokenEnd < inputLength && characterMeetsCondition(input[tokenEnd])) { + if (mode == Mode::Rfc2045 && !isTokenCharacter(input[tokenEnd])) + break; + ++tokenEnd; + } + + if (tokenEnd == tokenStart) + return StringView(); + if (skipTrailingWhitespace) { + if (mode == Mode::Rfc2045) { + while (input[tokenEnd - 1] == ' ') + --tokenEnd; + } else { + while (isHTTPSpace(input[tokenEnd - 1])) + --tokenEnd; + } + } + return input.substring(tokenStart, tokenEnd - tokenStart); +} + +static bool isNotQuoteOrBackslash(UChar ch) +{ + return ch != '"' && ch != '\\'; +} + +static String collectHTTPQuotedString(StringView input, unsigned& startIndex) +{ + ASSERT(input[startIndex] == '"'); + unsigned inputLength = input.length(); + unsigned& position = startIndex; + position++; + StringBuilder builder; + while (true) { + unsigned positionStart = position; + parseToken(input, position, isNotQuoteOrBackslash, Mode::MimeSniff); + builder.append(input.substring(positionStart, position - positionStart)); + if (position >= inputLength) + break; + UChar quoteOrBackslash = input[position++]; + if (quoteOrBackslash == '\\') { + if (position >= inputLength) { + builder.append(quoteOrBackslash); + break; + } + builder.append(input[position++]); + } else { + ASSERT(quoteOrBackslash == '"'); + break; + } + + } + return builder.toString(); +} + +static bool containsNonTokenCharacters(StringView input, Mode mode) +{ + if (mode == Mode::MimeSniff) + return !isValidHTTPToken(input); + for (unsigned index = 0; index < input.length(); ++index) { + if (!isTokenCharacter(input[index])) + return true; + } + return false; +} + +static StringView parseQuotedString(StringView input, unsigned& startIndex) +{ + unsigned inputLength = input.length(); + unsigned quotedStringStart = startIndex + 1; + unsigned& quotedStringEnd = startIndex; + + if (quotedStringEnd >= inputLength) + return StringView(); + + if (input[quotedStringEnd++] != '"' || quotedStringEnd >= inputLength) + return StringView(); + + bool lastCharacterWasBackslash = false; + char currentCharacter; + while ((currentCharacter = input[quotedStringEnd++]) != '"' || lastCharacterWasBackslash) { + if (quotedStringEnd >= inputLength) + return StringView(); + if (currentCharacter == '\\' && !lastCharacterWasBackslash) { + lastCharacterWasBackslash = true; + continue; + } + if (lastCharacterWasBackslash) + lastCharacterWasBackslash = false; + } + if (input[quotedStringEnd - 1] == '"') + quotedStringEnd++; + return input.substring(quotedStringStart, quotedStringEnd - quotedStringStart); +} + +// From http://tools.ietf.org/html/rfc2045#section-5.1: +// +// content := "Content-Type" ":" type "/" subtype +// *(";" parameter) +// ; Matching of media type and subtype +// ; is ALWAYS case-insensitive. +// +// type := discrete-type / composite-type +// +// discrete-type := "text" / "image" / "audio" / "video" / +// "application" / extension-token +// +// composite-type := "message" / "multipart" / extension-token +// +// extension-token := ietf-token / x-token +// +// ietf-token := <An extension token defined by a +// standards-track RFC and registered +// with IANA.> +// +// x-token := <The two characters "X-" or "x-" followed, with +// no intervening white space, by any token> +// +// subtype := extension-token / iana-token +// +// iana-token := <A publicly-defined extension token. Tokens +// of this form must be registered with IANA +// as specified in RFC 2048.> +// +// parameter := attribute "=" value +// +// attribute := token +// ; Matching of attributes +// ; is ALWAYS case-insensitive. +// +// value := token / quoted-string +// +// token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, +// or tspecials> +// +// tspecials := "(" / ")" / "<" / ">" / "@" / +// "," / ";" / ":" / "\" / <"> +// "/" / "[" / "]" / "?" / "=" +// ; Must be in quoted-string, +// ; to use within parameter values + +static bool isNotForwardSlash(UChar ch) +{ + return ch != '/'; +} + +static bool isNotSemicolon(UChar ch) +{ + return ch != ';'; +} + +static bool isNotSemicolonOrEqualSign(UChar ch) +{ + return ch != ';' && ch != '='; +} + +static bool containsNewline(UChar ch) +{ + return ch == '\r' || ch == '\n'; +} + +bool ParsedContentType::parseContentType(Mode mode) +{ + if (mode == Mode::Rfc2045 && m_contentType.find(containsNewline) != notFound) + return false; + unsigned index = 0; + unsigned contentTypeLength = m_contentType.length(); + skipSpaces(m_contentType, index); + if (index >= contentTypeLength) { + LOG_ERROR("Invalid Content-Type string '%s'", m_contentType.ascii().data()); + return false; + } + + unsigned contentTypeStart = index; + auto typeRange = parseToken(m_contentType, index, isNotForwardSlash, mode); + if (typeRange.isNull() || containsNonTokenCharacters(typeRange, mode)) { + LOG_ERROR("Invalid Content-Type, invalid type value."); + return false; + } + + if (index >= contentTypeLength || m_contentType[index++] != '/') { + LOG_ERROR("Invalid Content-Type, missing '/'."); + return false; + } + + auto subTypeRange = parseToken(m_contentType, index, isNotSemicolon, mode, mode == Mode::MimeSniff); + if (subTypeRange.isNull() || containsNonTokenCharacters(subTypeRange, mode)) { + LOG_ERROR("Invalid Content-Type, invalid subtype value."); + return false; + } + + // There should not be any quoted strings until we reach the parameters. + size_t semiColonIndex = m_contentType.find(';', contentTypeStart); + if (semiColonIndex == notFound) { + setContentType(m_contentType.substring(contentTypeStart, contentTypeLength - contentTypeStart), mode); + return true; + } + + setContentType(m_contentType.substring(contentTypeStart, semiColonIndex - contentTypeStart), mode); + index = semiColonIndex + 1; + while (true) { + skipSpaces(m_contentType, index); + auto keyRange = parseToken(m_contentType, index, isNotSemicolonOrEqualSign, mode); + if (mode == Mode::Rfc2045 && (keyRange.isNull() || index >= contentTypeLength)) { + LOG_ERROR("Invalid Content-Type parameter name."); + return false; + } + + // Should we tolerate spaces here? + if (mode == Mode::Rfc2045) { + if (index >= contentTypeLength || m_contentType[index++] != '=') { + LOG_ERROR("Invalid Content-Type malformed parameter."); + return false; + } + } else { + if (index >= contentTypeLength) + break; + if (m_contentType[index] != '=' && m_contentType[index] != ';') { + LOG_ERROR("Invalid Content-Type malformed parameter."); + return false; + } + if (m_contentType[index++] == ';') + continue; + } + + // Should we tolerate spaces here? + String parameterValue; + StringView valueRange; + if (index < contentTypeLength && m_contentType[index] == '"') { + if (mode == Mode::MimeSniff) { + parameterValue = collectHTTPQuotedString(m_contentType, index); + parseToken(m_contentType, index, isNotSemicolon, mode); + } else + valueRange = parseQuotedString(m_contentType, index); + } else + valueRange = parseToken(m_contentType, index, isNotSemicolon, mode, mode == Mode::MimeSniff); + + if (parameterValue.isNull()) { + if (valueRange.isNull()) { + if (mode == Mode::MimeSniff) + continue; + LOG_ERROR("Invalid Content-Type, invalid parameter value."); + return false; + } + parameterValue = valueRange.toString(); + } + + // Should we tolerate spaces here? + if (mode == Mode::Rfc2045 && index < contentTypeLength && m_contentType[index++] != ';') { + LOG_ERROR("Invalid Content-Type, invalid character at the end of key/value parameter."); + return false; + } + + if (!keyRange.isNull()) + setContentTypeParameter(keyRange.toString(), parameterValue, mode); + + if (index >= contentTypeLength) + return true; + } + + return true; +} + +std::optional<ParsedContentType> ParsedContentType::create(const String& contentType, Mode mode) +{ + ParsedContentType parsedContentType(mode == Mode::Rfc2045 ? contentType : stripLeadingAndTrailingHTTPSpaces(contentType)); + if (!parsedContentType.parseContentType(mode)) + return std::nullopt; + return { WTFMove(parsedContentType) }; +} + +bool isValidContentType(const String& contentType, Mode mode) +{ + return ParsedContentType::create(contentType, mode) != std::nullopt; +} + +ParsedContentType::ParsedContentType(const String& contentType) + : m_contentType(contentType) +{ +} + +String ParsedContentType::charset() const +{ + return parameterValueForName("charset"); +} + +void ParsedContentType::setCharset(String&& charset) +{ + m_parameterValues.set("charset"_s, WTFMove(charset)); +} + +String ParsedContentType::parameterValueForName(const String& name) const +{ + return m_parameterValues.get(name); +} + +size_t ParsedContentType::parameterCount() const +{ + return m_parameterValues.size(); +} + +void ParsedContentType::setContentType(StringView contentRange, Mode mode) +{ + m_mimeType = contentRange.toString(); + if (mode == Mode::MimeSniff) + m_mimeType = stripLeadingAndTrailingHTTPSpaces(m_mimeType).convertToASCIILowercase(); + else + m_mimeType = m_mimeType.stripWhiteSpace(); +} + +static bool containsNonQuoteStringTokenCharacters(const String& input) +{ + for (unsigned index = 0; index < input.length(); ++index) { + if (!isQuotedStringTokenCharacter(input[index])) + return true; + } + return false; +} + +void ParsedContentType::setContentTypeParameter(const String& keyName, const String& keyValue, Mode mode) +{ + String name = keyName; + if (mode == Mode::MimeSniff) { + if (m_parameterValues.contains(name) || !isValidHTTPToken(name) || containsNonQuoteStringTokenCharacters(keyValue)) + return; + name = name.convertToASCIILowercase(); + } + m_parameterValues.set(name, keyValue); + m_parameterNames.append(name); +} + +String ParsedContentType::serialize() const +{ + StringBuilder builder; + builder.append(m_mimeType); + for (auto& name : m_parameterNames) { + builder.append(';'); + builder.append(name); + builder.append('='); + String value = m_parameterValues.get(name); + if (value.isEmpty() || !isValidHTTPToken(value)) { + builder.append('"'); + for (unsigned index = 0; index < value.length(); ++index) { + auto ch = value[index]; + if (ch == '\\' || ch =='"') + builder.append('\\'); + builder.append(ch); + } + builder.append('"'); + } else + builder.append(value); + } + return builder.toString(); +} + +} diff --git a/src/javascript/jsc/bindings/webcore/ParsedContentType.h b/src/javascript/jsc/bindings/webcore/ParsedContentType.h new file mode 100644 index 000000000..d22b97252 --- /dev/null +++ b/src/javascript/jsc/bindings/webcore/ParsedContentType.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <wtf/HashMap.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +enum class Mode { + Rfc2045, + MimeSniff +}; +WEBCORE_EXPORT bool isValidContentType(const String&, Mode = Mode::MimeSniff); + +// FIXME: add support for comments. +class ParsedContentType { +public: + WEBCORE_EXPORT static std::optional<ParsedContentType> create(const String&, Mode = Mode::MimeSniff); + ParsedContentType(ParsedContentType&&) = default; + + String mimeType() const { return m_mimeType; } + String charset() const; + void setCharset(String&&); + + // Note that in the case of multiple values for the same name, the last value is returned. + String parameterValueForName(const String&) const; + size_t parameterCount() const; + + WEBCORE_EXPORT String serialize() const; + +private: + ParsedContentType(const String&); + ParsedContentType(const ParsedContentType&) = delete; + ParsedContentType& operator=(ParsedContentType const&) = delete; + bool parseContentType(Mode); + void setContentType(StringView, Mode); + void setContentTypeParameter(const String&, const String&, Mode); + + typedef HashMap<String, String> KeyValuePairs; + String m_contentType; + KeyValuePairs m_parameterValues; + Vector<String> m_parameterNames; + String m_mimeType; +}; + +} diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index e17b6e720..66bd0c620 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -91,7 +91,6 @@ pub const GlobalConstructors = [_]type{ WebCore.TextEncoder.Constructor, Request.Constructor, Response.Constructor, - Headers.Constructor, JSC.Cloudflare.HTMLRewriter.Constructor, }; @@ -2199,7 +2198,7 @@ pub const EventListenerMixin = struct { fetch_event.* = FetchEvent{ .request_context = request_context, - .request = try Request.fromRequestContext(request_context), + .request = try Request.fromRequestContext(request_context, vm.global), .onPromiseRejectionCtx = @as(*anyopaque, ctx), .onPromiseRejectionHandler = FetchEventRejectionHandler.onRejection, }; diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 46cd37c82..a23ca69ce 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -11,7 +11,7 @@ const JSC = @import("../../../jsc.zig"); const js = JSC.C; const Method = @import("../../../http/method.zig").Method; - +const FetchHeaders = JSC.FetchHeaders; const ObjectPool = @import("../../../pool.zig").ObjectPool; const SystemError = JSC.SystemError; const Output = @import("../../../global.zig").Output; @@ -173,9 +173,8 @@ pub const Response = struct { } pub fn header(this: *const Response, comptime name: []const u8) ?[]const u8 { - const headers: *const Headers = (this.body.init.headers orelse return null).value; - const index = headers.getHeaderIndex(name) orelse return null; - return headers.asStr(headers.entries.items(.value)[index]); + const headers_ = (this.body.init.headers.as(FetchHeaders) orelse return null); + return headers_.get(name); } pub const Props = struct {}; @@ -282,21 +281,25 @@ pub const Response = struct { return js.JSValueMakeBoolean(ctx, this.isOK()); } - fn getOrCreateHeaders(this: *Response) *Headers.RefCountedHeaders { - if (this.body.init.headers == null) { - this.body.init.headers = Headers.RefCountedHeaders.init(Headers.empty(this.allocator), this.allocator) catch unreachable; + fn getOrCreateHeaders(this: *Response, globalThis: *JSGlobalObject) *FetchHeaders { + if (this.body.init.headers.isEmpty()) { + this.body.init.headers = FetchHeaders.createEmpty(globalThis).as(FetchHeaders); } - return this.body.init.headers.?; + return this.body.init.headers; } pub fn getHeaders( this: *Response, ctx: js.JSContextRef, - _: js.JSValueRef, + obj: js.JSValueRef, _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - return Headers.Class.make(ctx, this.getOrCreateHeaders().getRef()); + var headers_symbol = &ZigString.init("headers_"); + const thisValue = JSC.JSValue.fromRef(obj); + const headers_symbol_ = JSC.JSValue.symbolFor(ctx.ptr(), headers_symbol); + this.body.init.headers = JSC.JSValue.get(headers_symbol_) orelse FetchHeaders.createEmpty(ctx.ptr()); + return this.body.init.headers.asObjectRef(); } pub fn doClone( @@ -307,8 +310,16 @@ pub const Response = struct { _: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { - var cloned = this.clone(getAllocator(ctx)); - return Response.makeMaybePooled(ctx, cloned); + var cloned = this.clone(getAllocator(ctx), ctx.ptr()); + var val = Response.makeMaybePooled(ctx, cloned); + if (!cloned.body.init.headers.isEmpty()) { + var headers_symbol = &ZigString.init("headers_"); + const thisValue = JSC.JSValue.fromRef(val); + const headers_symbol_ = JSC.JSValue.symbolFor(ctx.ptr(), headers_symbol); + cloned.body.init.headers = thisValue.get(headers_symbol_) orelse FetchHeaders.createEmpty(ctx.ptr()); + } + + return val; } pub fn makeMaybePooled(ctx: js.JSContextRef, ptr: *Response) JSC.C.JSObjectRef { @@ -324,19 +335,24 @@ pub const Response = struct { return Response.Class.make(ctx, ptr); } - pub fn cloneInto(this: *const Response, new_response: *Response, allocator: std.mem.Allocator) void { + pub fn cloneInto( + this: *const Response, + new_response: *Response, + allocator: std.mem.Allocator, + globalThis: *JSGlobalObject, + ) void { new_response.* = Response{ .allocator = allocator, - .body = this.body.clone(allocator), + .body = this.body.clone(allocator, globalThis), .url = allocator.dupe(u8, this.url) catch unreachable, .status_text = allocator.dupe(u8, this.status_text) catch unreachable, .redirected = this.redirected, }; } - pub fn clone(this: *const Response, allocator: std.mem.Allocator) *Response { + pub fn clone(this: *const Response, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) *Response { var new_response = allocator.create(Response) catch unreachable; - this.cloneInto(new_response, allocator); + this.cloneInto(new_response, allocator, globalThis); return new_response; } @@ -376,14 +392,10 @@ pub const Response = struct { } pub fn mimeTypeWithDefault(response: *const Response, default: MimeType, request_ctx_: ?*const RequestContext) string { - if (response.body.init.headers) |headers_ref| { - var headers = headers_ref.get(); - defer headers_ref.deref(); + if (response.header("content-type")) |content_type| { // Remember, we always lowercase it // hopefully doesn't matter here tho - if (headers.getHeaderIndex("content-type")) |content_type| { - return headers.asStr(headers.entries.items(.value)[content_type]); - } + return content_type; } if (request_ctx_) |request_ctx| { @@ -419,7 +431,6 @@ pub const Response = struct { var response = Response{ .body = Body{ .init = Body.Init{ - .headers = null, .status_code = 200, }, .value = Body.Value.empty, @@ -461,8 +472,8 @@ pub const Response = struct { } } - var headers_ref = response.getOrCreateHeaders().leak(); - headers_ref.putDefaultHeader("content-type", MimeType.json.value); + var headers_ref = response.getOrCreateHeaders(ctx.ptr()); + headers_ref.putDefault("content-type", MimeType.json.value); var ptr = response.allocator.create(Response) catch unreachable; ptr.* = response; @@ -483,7 +494,6 @@ pub const Response = struct { var response = Response{ .body = Body{ .init = Body.Init{ - .headers = null, .status_code = 302, }, .value = Body.Value.empty, @@ -511,10 +521,10 @@ pub const Response = struct { } } - response.body.init.headers = response.getOrCreateHeaders(); + response.body.init.headers = response.getOrCreateHeaders(ctx.ptr()); response.body.init.status_code = 302; - var headers_ref = response.body.init.headers.?.leak(); - headers_ref.putHeaderNormalized("location", url_string_slice.slice(), false); + var headers_ref = response.body.init.headers.?; + headers_ref.put("location", url_string_slice.slice()); var ptr = response.allocator.create(Response) catch unreachable; ptr.* = response; @@ -532,7 +542,6 @@ pub const Response = struct { response.* = Response{ .body = Body{ .init = Body.Init{ - .headers = null, .status_code = 0, }, .value = Body.Value.empty, @@ -800,10 +809,7 @@ pub const Fetch = struct { .redirected = this.http.redirect_count > 0, .body = .{ .init = .{ - .headers = Headers.RefCountedHeaders.init( - Headers.fromPicoHeaders(allocator, http_response.headers) catch unreachable, - allocator, - ) catch unreachable, + .headers = FetchHeaders.createFromPicoHeaders(this.global_this, http_response.headers), .status_code = @truncate(u16, http_response.status_code), }, .value = .{ @@ -926,13 +932,12 @@ pub const Fetch = struct { } if (options.get(ctx.ptr(), "headers")) |headers_| { - var headers2: Headers = undefined; - if (headers_.as(Headers.RefCountedHeaders)) |headers__| { - headers__.leak().clone(&headers2) catch unreachable; - headers = headers2; - } else if (Headers.JS.headersInit(ctx, headers_.asObjectRef()) catch null) |headers__| { - headers__.clone(&headers2) catch unreachable; - headers = headers2; + if (headers_.as(FetchHeaders)) |headers__| { + headers = Headers.from(headers__, bun.default_allocator) catch unreachable; + // TODO: make this one pass + } else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| { + headers = Headers.from(headers__, bun.default_allocator) catch unreachable; + headers__.deref(); } } @@ -958,10 +963,8 @@ pub const Fetch = struct { } else if (first_arg.asCheckLoaded(Request)) |request| { url = ZigURL.parse(request.url.dupe(getAllocator(ctx)) catch unreachable); method = request.method; - if (request.headers) |head| { - var for_clone: Headers = undefined; - head.leak().clone(&for_clone) catch unreachable; - headers = for_clone; + if (request.headers.as(FetchHeaders)) |head| { + headers = Headers.from(head, bun.default_allocator) catch unreachable; } var blob = request.body.use(); // TODO: make RequestBody _NOT_ a MutableString @@ -1033,662 +1036,32 @@ pub const Headers = struct { entries: Headers.Entries = .{}, buf: std.ArrayListUnmanaged(u8) = .{}, allocator: std.mem.Allocator, - guard: Guard = Guard.none, - pub const RefCountedHeaders = bun.RefCount(Headers, true); - - pub fn deinit( - headers: *Headers, - ) void { - headers.buf.deinit(headers.allocator); - headers.entries.deinit(headers.allocator); - } - - pub fn clonefromUWSRequest(headers: *Headers, req: *uws.Request) void { - req.cloneHeaders(headers); - } - - pub fn preallocate(this: *Headers, buf_size: usize, header_count: usize) callconv(.C) void { - this.buf.ensureTotalCapacityPrecise(this.allocator, buf_size) catch unreachable; - this.entries.ensureTotalCapacity(this.allocator, header_count) catch unreachable; - } - - comptime { - // if (!JSC.is_bindgen) { - // Initially, these were function pointers - // However, some Zig Stage1 C ABI bug causes EXC_BAD_ACCESS - @export(preallocate, .{ .name = "Headers__preallocate" }); - @export(appendHeaderNormalizedC, .{ .name = "Headers__appendHeaderNormalized" }); - // } + pub fn asStr(this: *const Headers, ptr: Api.StringPointer) []const u8 { + return if (ptr.offset + ptr.length <= this.buf.items.len) + this.buf.items[ptr.offset..][0..ptr.length] + else + ""; } - pub fn empty(allocator: std.mem.Allocator) Headers { - return Headers{ + pub fn from(headers_ref: *FetchHeaders, allocator: std.mem.Allocator) !Headers { + var header_count: u32 = 0; + var buf_len: u32 = 0; + headers_ref.count(&header_count, &buf_len); + var headers = Headers{ .entries = .{}, .buf = .{}, .allocator = allocator, - .guard = Guard.none, - }; - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Headers#methods - pub const JS = struct { - - // https://developer.mozilla.org/en-US/docs/Web/API/Headers/get - pub fn get( - ref: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var this = ref.leak(); - if (arguments.len == 0) { - return js.JSValueMakeNull(ctx); - } - - const key_slice = ZigString.from(arguments[0], ctx).toSlice(bun.default_allocator); - if (key_slice.len == 0) { - return js.JSValueMakeNull(ctx); - } - defer key_slice.deinit(); - - if (this.getHeaderIndex(key_slice.slice())) |index| { - return ZigString.init(this.asStr(this.entries.items(.value)[index])) - .toValue(ctx.ptr()).asObjectRef(); - } else { - return js.JSValueMakeNull(ctx); - } - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Headers/set - // > The difference between set() and Headers.append is that if the specified header already exists and accepts multiple values - // > set() overwrites the existing value with the new one, whereas Headers.append appends the new value to the end of the set of values. - pub fn set( - ref: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var this = ref.leak(); - if (arguments.len == 0) { - return js.JSValueMakeNull(ctx); - } - const key_slice = ZigString.from(arguments[0], ctx); - if (key_slice.len == 0) { - return js.JSValueMakeNull(ctx); - } - - this.putHeaderFromJS(key_slice, ZigString.from(arguments[1], ctx), false); - return js.JSValueMakeUndefined(ctx); - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Headers/append - pub fn append( - ref: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var this = ref.leak(); - if (arguments.len == 0) { - return js.JSValueMakeNull(ctx); - } - const key_slice = ZigString.from(arguments[0], ctx); - if (key_slice.len == 0) { - return js.JSValueMakeNull(ctx); - } - - this.putHeaderFromJS(key_slice, ZigString.from(arguments[1], ctx), true); - return js.JSValueMakeUndefined(ctx); - } - pub fn delete( - ref: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var this: *Headers = ref.leak(); - - const key = ZigString.from(arguments[0], ctx); - if (key.len == 0) { - return js.JSValueMakeNull(ctx); - } - var str = key.toSlice(ref.allocator); - defer str.deinit(); - var entries_ = &this.entries; - - if (this.getHeaderIndex(str.slice())) |header_i| { - entries_.orderedRemove(header_i); - } - - return js.JSValueMakeUndefined(ctx); - } - pub fn entries( - _: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - Output.prettyErrorln("<r><b>Headers.entries()<r> is not implemented yet - sorry!!", .{}); - return js.JSValueMakeNull(ctx); - } - pub fn keys( - _: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - Output.prettyErrorln("H<r><b>Headers.keys()<r> is not implemented yet- sorry!!", .{}); - return js.JSValueMakeNull(ctx); - } - pub fn values( - _: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - Output.prettyErrorln("<r><b>Headers.values()<r> is not implemented yet - sorry!!", .{}); - return js.JSValueMakeNull(ctx); - } - - pub fn headersInit(ctx: js.JSContextRef, header_prop: js.JSObjectRef) !?Headers { - const header_keys = js.JSObjectCopyPropertyNames(ctx, header_prop); - defer js.JSPropertyNameArrayRelease(header_keys); - const total_header_count = js.JSPropertyNameArrayGetCount(header_keys); - if (total_header_count == 0) return null; - - // 2 passes through the headers - - // Pass #1: find the "real" count. - // The number of things which are strings or numbers. - // Anything else should be ignored. - // We could throw a TypeError, but ignoring silently is more JavaScript-like imo - var real_header_count: usize = 0; - var estimated_buffer_len: usize = 0; - var j: usize = 0; - while (j < total_header_count) : (j += 1) { - var key_ref = js.JSPropertyNameArrayGetNameAtIndex(header_keys, j); - var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null); - - switch (js.JSValueGetType(ctx, value_ref)) { - js.JSType.kJSTypeNumber => { - const key_len = js.JSStringGetLength(key_ref); - if (key_len > 0) { - real_header_count += 1; - estimated_buffer_len += key_len; - estimated_buffer_len += std.fmt.count("{d}", .{js.JSValueToNumber(ctx, value_ref, null)}); - } - }, - js.JSType.kJSTypeString => { - const key_len = js.JSStringGetLength(key_ref); - const value_len = js.JSStringGetLength(value_ref); - if (key_len > 0 and value_len > 0) { - real_header_count += 1; - estimated_buffer_len += key_len + value_len; - } - }, - else => {}, - } - } - - if (real_header_count == 0 or estimated_buffer_len == 0) return null; - - j = 0; - var allocator = getAllocator(ctx); - var headers = Headers{ - .allocator = allocator, - .buf = try std.ArrayListUnmanaged(u8).initCapacity(allocator, estimated_buffer_len), - .entries = Headers.Entries{}, - }; - errdefer headers.deinit(); - try headers.entries.ensureTotalCapacity(allocator, real_header_count); - - while (j < total_header_count) : (j += 1) { - var key_ref = js.JSPropertyNameArrayGetNameAtIndex(header_keys, j); - var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null); - - switch (js.JSValueGetType(ctx, value_ref)) { - js.JSType.kJSTypeNumber => { - if (js.JSStringGetLength(key_ref) == 0) continue; - try headers.appendInit(ctx, key_ref, .kJSTypeNumber, value_ref); - }, - js.JSType.kJSTypeString => { - if (js.JSStringGetLength(value_ref) == 0 or js.JSStringGetLength(key_ref) == 0) continue; - try headers.appendInit(ctx, key_ref, .kJSTypeString, value_ref); - }, - else => {}, - } - } - return headers; - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers - pub fn constructor( - ctx: js.JSContextRef, - _: js.JSObjectRef, - arguments: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSObjectRef { - var headers = getAllocator(ctx).create(RefCountedHeaders) catch unreachable; - if (arguments.len > 0 and js.JSValueIsObjectOfClass(ctx, arguments[0], Headers.Class.get().*)) { - var other = castObj(arguments[0], RefCountedHeaders).leak(); - other.clone(&headers.value) catch unreachable; - headers.count = 1; - headers.allocator = getAllocator(ctx); - } else if (arguments.len == 1 and js.JSValueIsObject(ctx, arguments[0])) { - headers.* = .{ - .value = (JS.headersInit(ctx, arguments[0]) catch null) orelse Headers{ - .entries = .{}, - .buf = .{}, - .allocator = getAllocator(ctx), - .guard = Guard.none, - }, - .allocator = getAllocator(ctx), - .count = 1, - }; - } else { - headers.* = .{ - .value = Headers.empty(getAllocator(ctx)), - .allocator = getAllocator(ctx), - .count = 1, - }; - } - - return Headers.Class.make(ctx, headers); - } - - pub fn finalize( - this: *RefCountedHeaders, - ) void { - this.deref(); - } - }; - pub const Constructor = JSC.NewConstructor( - Headers, - .{ - .@"constructor" = .{ - .rfn = JS.constructor, - .ts = d.ts{}, - }, - }, - .{}, - ); - pub const Class = NewClass( - RefCountedHeaders, - .{ - .name = "Headers", - .read_only = true, - }, - .{ - .@"get" = .{ - .rfn = JS.get, - }, - .@"set" = .{ - .rfn = JS.set, - .ts = d.ts{}, - }, - .@"append" = .{ - .rfn = JS.append, - .ts = d.ts{}, - }, - .@"delete" = .{ - .rfn = JS.delete, - .ts = d.ts{}, - }, - .@"entries" = .{ - .rfn = JS.entries, - .ts = d.ts{}, - }, - .@"keys" = .{ - .rfn = JS.keys, - .ts = d.ts{}, - }, - .@"values" = .{ - .rfn = JS.values, - .ts = d.ts{}, - }, - - .@"finalize" = .{ - .rfn = JS.finalize, - }, - .toJSON = .{ - .rfn = toJSON, - .name = "toJSON", - }, - }, - .{}, - ); - - // https://developer.mozilla.org/en-US/docs/Glossary/Guard - pub const Guard = enum { - immutable, - request, - @"request-no-cors", - response, - none, - }; - - pub fn fromPicoHeaders(allocator: std.mem.Allocator, picohttp_headers: []const picohttp.Header) !Headers { - var total_len: usize = 0; - for (picohttp_headers) |header| { - total_len += header.name.len; - total_len += header.value.len; - } - // for the null bytes - total_len += picohttp_headers.len * 2; - var headers = Headers{ - .allocator = allocator, - .entries = Headers.Entries{}, - .buf = std.ArrayListUnmanaged(u8){}, }; - try headers.entries.ensureTotalCapacity(allocator, picohttp_headers.len); - try headers.buf.ensureTotalCapacity(allocator, total_len); - headers.buf.expandToCapacity(); - headers.guard = Guard.request; - - for (picohttp_headers) |header| { - headers.entries.appendAssumeCapacity(.{ - .name = headers.appendString( - string, - header.name, - true, - true, - ), - .value = headers.appendString( - string, - header.value, - true, - true, - ), - }); - } - + headers.entries.ensureTotalCapacity(allocator, header_count) catch unreachable; + headers.buf.ensureTotalCapacity(allocator, buf_len) catch unreachable; + headers.buf.items.len = buf_len; + var sliced = headers.entries.slice(); + var names = sliced.items(.name); + var values = sliced.items(.value); + headers_ref.copyTo(names.ptr, values.ptr, headers.buf.items.ptr); return headers; } - - // TODO: is it worth making this lazy? instead of copying all the request headers, should we just do it on get/put/iterator? - pub fn fromRequestCtx(allocator: std.mem.Allocator, request: *RequestContext) !Headers { - return fromPicoHeaders(allocator, request.request.headers); - } - - pub fn asStr(headers: *const Headers, ptr: Api.StringPointer) []u8 { - return headers.buf.items[ptr.offset..][0..ptr.length]; - } - - pub fn putHeader(headers: *Headers, key_: []const u8, value_: []const u8, comptime append: bool) void { - var header_kv_buf: [4096]u8 = undefined; - - const key = strings.copyLowercase(strings.trim(key_, " \n\r"), &header_kv_buf); - const value = strings.copyLowercase(strings.trim(value_, " \n\r"), header_kv_buf[key.len..]); - - return headers.putHeaderNormalized(key, value, append); - } - - pub fn putHeaderFromJS(headers: *Headers, key_: ZigString, value_: ZigString, comptime append: bool) void { - var key_slice = key_.toSlice(headers.allocator); - var value_slice = value_.toSlice(headers.allocator); - - defer key_slice.deinit(); - defer value_slice.deinit(); - - headers.putHeader(key_slice.slice(), value_slice.slice(), append); - } - - pub fn putDefaultHeader( - headers: *Headers, - key: []const u8, - value: []const u8, - ) void { - return putHeaderNormalizedDefault(headers, key, value, false, true); - } - - pub fn putHeaderNormalizedDefault( - headers: *Headers, - key: []const u8, - value: []const u8, - comptime append: bool, - comptime default: bool, - ) void { - if (headers.getHeaderIndex(key)) |header_i| { - if (comptime default) return; - - const existing_value = headers.entries.items(.value)[header_i]; - if (append) { - const end = @truncate(u32, value.len + existing_value.length + 2); - const offset = headers.buf.items.len; - headers.buf.ensureUnusedCapacity(headers.allocator, end) catch unreachable; - headers.buf.appendSliceAssumeCapacity(headers.asStr(existing_value)); - headers.buf.appendSliceAssumeCapacity(", "); - headers.buf.appendSliceAssumeCapacity(value); - headers.entries.items(.value)[header_i] = Api.StringPointer{ .offset = @truncate(u32, offset), .length = @truncate(u32, headers.buf.items.len - offset) }; - // Can we get away with just overwriting in-place? - } else if (existing_value.length >= value.len) { - std.mem.copy(u8, headers.asStr(existing_value), value); - headers.entries.items(.value)[header_i].length = @truncate(u32, value.len); - // Otherwise, append to the buffer, and just don't bother dealing with the existing header value - // We assume that these header objects are going to be kind of short-lived. - } else { - headers.buf.ensureUnusedCapacity(headers.allocator, value.len + 1) catch unreachable; - headers.entries.items(.value)[header_i] = headers.appendString(string, value, false, false); - } - } else { - headers.appendHeader(key, value, false, false); - } - } - - pub fn putHeaderNormalized(headers: *Headers, key: []const u8, value: []const u8, comptime append: bool) void { - return putHeaderNormalizedDefault(headers, key, value, append, false); - } - - pub fn getHeaderIndex(headers: *const Headers, key: string) ?u32 { - for (headers.entries.items(.name)) |name, i| { - if (name.length == key.len and strings.eqlInsensitive(key, headers.asStr(name))) { - return @truncate(u32, i); - } - } - - return null; - } - - pub fn appendHeaderNormalizedC( - headers: *Headers, - key: [*]const u8, - key_len: usize, - value: [*]const u8, - value_len: usize, - ) callconv(.C) void { - headers.appendHeader(key[0..key_len], value[0..value_len], false, false); - } - - pub fn appendHeader( - headers: *Headers, - key: string, - value: string, - comptime needs_lowercase: bool, - comptime needs_normalize: bool, - ) void { - headers.buf.ensureUnusedCapacity(headers.allocator, key.len + value.len + 2) catch unreachable; - - headers.entries.append( - headers.allocator, - .{ - .name = headers.appendString( - string, - key, - needs_lowercase, - needs_normalize, - ), - .value = headers.appendString( - string, - value, - needs_lowercase, - needs_normalize, - ), - }, - ) catch unreachable; - } - - fn appendString( - this: *Headers, - comptime StringType: type, - str: StringType, - comptime needs_lowercase: bool, - comptime needs_normalize: bool, - ) Api.StringPointer { - var ptr = Api.StringPointer{ .offset = @truncate(u32, this.buf.items.len), .length = 0 }; - ptr.length = @truncate( - u32, - switch (comptime StringType) { - js.JSStringRef => js.JSStringGetLength(str), - else => str.len, - }, - ); - if (Environment.allow_assert) std.debug.assert(ptr.length > 0); - this.buf.ensureUnusedCapacity(this.allocator, ptr.length) catch unreachable; - var slice = this.buf.items; - slice.len += ptr.length; - slice = slice[ptr.offset..][0..ptr.length]; - - switch (comptime StringType) { - js.JSStringRef => { - ptr.length = @truncate(u32, js.JSStringGetUTF8CString(str, slice.ptr, slice.len) - 1); - }, - else => { - std.mem.copy(u8, slice, str); - }, - } - - if (comptime needs_normalize) { - slice = strings.trim(slice, " \r\n"); - } - - if (comptime needs_lowercase) { - for (slice) |c, i| { - slice[i] = std.ascii.toLower(c); - } - } - - ptr.length = @truncate(u32, slice.len); - this.buf.items.len += slice.len; - return ptr; - } - - pub fn toJSON( - ref: *RefCountedHeaders, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - var this = ref.leak(); - const slice = this.entries.slice(); - const keys = slice.items(.name); - const values = slice.items(.value); - const StackFallback = std.heap.StackFallbackAllocator(32 * 2 * @sizeOf(ZigString)); - var stack = StackFallback{ - .buffer = undefined, - .fallback_allocator = default_allocator, - .fixed_buffer_allocator = undefined, - }; - var allocator = stack.get(); - var key_strings_ = allocator.alloc(ZigString, keys.len * 2) catch unreachable; - var key_strings = key_strings_[0..keys.len]; - var value_strings = key_strings_[keys.len..]; - - for (keys) |key, i| { - key_strings[i] = ZigString.init(this.asStr(key)); - key_strings[i].detectEncoding(); - value_strings[i] = ZigString.init(this.asStr(values[i])); - value_strings[i].detectEncoding(); - } - - var result = JSValue.fromEntries(ctx.ptr(), key_strings.ptr, value_strings.ptr, keys.len, true).asObjectRef(); - allocator.free(key_strings_); - return result; - } - - pub fn writeFormat(this: *const Headers, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void { - if (this.entries.len == 0) { - try writer.writeAll("Headers (0 KB) {}"); - return; - } - - try writer.print("Headers ({}) {{\n", .{bun.fmt.size(this.buf.items.len)}); - const Writer = @TypeOf(writer); - { - var slice = this.entries.slice(); - const names = slice.items(.name); - const values = slice.items(.value); - formatter.indent += 1; - defer formatter.indent -|= 1; - - for (names) |name, i| { - if (i > 0) { - formatter.printComma(Writer, writer, enable_ansi_colors) catch unreachable; - writer.writeAll("\n") catch unreachable; - } - - const value = values[i]; - formatter.writeIndent(Writer, writer) catch unreachable; - try JSPrinter.writeJSONString(this.asStr(name), Writer, writer, false); - writer.writeAll(": ") catch unreachable; - try JSPrinter.writeJSONString(this.asStr(value), Writer, writer, false); - } - } - - try writer.writeAll("\n"); - try formatter.writeIndent(@TypeOf(writer), writer); - try writer.writeAll("}"); - } - - fn appendNumber(this: *Headers, num: f64) Api.StringPointer { - var ptr = Api.StringPointer{ .offset = @truncate(u32, this.buf.items.len), .length = @truncate( - u32, - std.fmt.count("{d}", .{num}), - ) }; - this.buf.ensureUnusedCapacity(this.allocator, ptr.length + 1) catch unreachable; - this.buf.items.len += ptr.length; - var slice = this.buf.items[ptr.offset..][0..ptr.length]; - var buf = std.fmt.bufPrint(slice, "{d}", .{num}) catch &[_]u8{}; - ptr.length = @truncate(u32, buf.len); - return ptr; - } - - pub fn appendInit(this: *Headers, ctx: js.JSContextRef, key: js.JSStringRef, comptime value_type: js.JSType, value: js.JSValueRef) !void { - this.entries.append(this.allocator, .{ - .name = this.appendString(js.JSStringRef, key, true, true), - .value = switch (comptime value_type) { - js.JSType.kJSTypeNumber => this.appendNumber(js.JSValueToNumber(ctx, value, null)), - js.JSType.kJSTypeString => this.appendString(js.JSStringRef, value, true, true), - else => unreachable, - }, - }) catch unreachable; - } - - pub fn clone(this: *const Headers, to: *Headers) !void { - var buf = this.buf; - to.* = Headers{ - .entries = try this.entries.clone(this.allocator), - .buf = try buf.clone(this.allocator), - .allocator = this.allocator, - .guard = Guard.none, - }; - } }; const PathOrBlob = union(enum) { @@ -2400,13 +1773,13 @@ pub const Blob = struct { } var store = this.store.?; - defer store.deref(); if (this.file_store.pathlike == .path) { VirtualMachine.vm.removeFileBlob(this.file_store.pathlike); } if (this.system_error) |err| { bun.default_allocator.destroy(this); + store.deref(); cb(cb_ctx, ResultType{ .err = err }); return; } @@ -2425,7 +1798,9 @@ pub const Blob = struct { } bun.default_allocator.destroy(this); + cb(cb_ctx, .{ .result = bytes }); + store.deref(); } pub fn run(this: *ReadFile, task: *ReadFileTask) void { this.runAsyncFrame = async this.runAsync(task); @@ -2929,7 +2304,12 @@ pub const Blob = struct { var source_buf: [bun.MAX_PATH_BYTES]u8 = undefined; var dest_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - switch (JSC.Node.Syscall.clonefile(this.source_file_store.pathlike.path.sliceZ(&source_buf), this.destination_file_store.pathlike.path.sliceZ(&dest_buf))) { + switch (JSC.Node.Syscall.clonefile( + this.source_file_store.pathlike.path.sliceZ(&source_buf), + this.destination_file_store.pathlike.path.sliceZ( + &dest_buf, + ), + )) { .err => |errno| { this.system_error = errno.toSystemError(); return AsyncIO.asError(errno.errno); @@ -3565,7 +2945,6 @@ pub const Blob = struct { promise.resolve(globalThis, Function(&blob, globalThis, comptime lifetime)); }, .err => |err| { - blob.detach(); promise.reject(globalThis, err.toErrorInstance(globalThis)); }, } @@ -4082,9 +3461,9 @@ pub const Body = struct { return this.value.use(); } - pub fn clone(this: Body, allocator: std.mem.Allocator) Body { + pub fn clone(this: Body, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Body { return Body{ - .init = this.init.clone(allocator), + .init = this.init.clone(globalThis), .value = this.value.clone(allocator), }; } @@ -4098,12 +3477,12 @@ pub const Body = struct { try formatter.printComma(Writer, writer, enable_ansi_colors); try writer.writeAll("\n"); - if (this.init.headers) |headers| { - try formatter.writeIndent(Writer, writer); - try writer.writeAll("headers: "); - try headers.leak().writeFormat(formatter, writer, comptime enable_ansi_colors); - try writer.writeAll("\n"); - } + // if (this.init.headers) |headers| { + // try formatter.writeIndent(Writer, writer); + // try writer.writeAll("headers: "); + // try headers.leak().writeFormat(formatter, writer, comptime enable_ansi_colors); + // try writer.writeAll("\n"); + // } try formatter.writeIndent(Writer, writer); try writer.writeAll("status: "); @@ -4111,36 +3490,25 @@ pub const Body = struct { } pub fn deinit(this: *Body, _: std.mem.Allocator) void { - if (this.init.headers) |headers| { - headers.deref(); - this.init.headers = null; - } - this.value.deinit(); } pub const Init = struct { - headers: ?*Headers.RefCountedHeaders = null, + headers: JSValue = JSValue.zero, status_code: u16, method: Method = Method.GET, - pub fn clone(this: Init, allocator: std.mem.Allocator) Init { + pub fn clone(this: Init, globalThis: *JSGlobalObject) Init { var that = this; - var headers = this.headers; - if (headers) |head| { - headers.?.value.allocator = allocator; - var new_headers = allocator.create(Headers.RefCountedHeaders) catch unreachable; - new_headers.allocator = allocator; - new_headers.count = 1; - head.leak().clone(&new_headers.value) catch unreachable; - that.headers = new_headers; + if (this.headers.as(FetchHeaders)) |head| { + that.headers = head.clone(globalThis); } return that; } - pub fn init(allocator: std.mem.Allocator, ctx: js.JSContextRef, init_ref: js.JSValueRef) !?Init { - var result = Init{ .headers = null, .status_code = 200 }; + pub fn init(_: std.mem.Allocator, ctx: js.JSContextRef, init_ref: js.JSValueRef) !?Init { + var result = Init{ .status_code = 200 }; var array = js.JSObjectCopyPropertyNames(ctx, init_ref); defer js.JSPropertyNameArrayRelease(array); const count = js.JSPropertyNameArrayGetCount(array); @@ -4153,16 +3521,14 @@ pub const Body = struct { if (js.JSStringIsEqualToUTF8CString(property_name_ref, "headers")) { // only support headers as an object for now. if (js.JSObjectGetProperty(ctx, init_ref, property_name_ref, null)) |header_prop| { - switch (js.JSValueGetType(ctx, header_prop)) { - js.JSType.kJSTypeObject => { - if (JSC.JSValue.fromRef(header_prop).as(Headers.RefCountedHeaders)) |headers| { - result.headers = try Headers.RefCountedHeaders.init(undefined, allocator); - try headers.leak().clone(&result.headers.?.value); - } else if (try Headers.JS.headersInit(ctx, header_prop)) |headers| { - result.headers = try Headers.RefCountedHeaders.init(headers, allocator); - } - }, - else => {}, + const header_val = JSValue.fromRef(header_prop); + if (header_val.as(FetchHeaders)) |orig| { + result.headers = orig.clone(ctx.ptr()); + } else { + result.headers = if (FetchHeaders.createFromJS(ctx.ptr(), header_val)) |headers| + headers.toJS(ctx.ptr()) + else + result.headers; } } } @@ -4185,7 +3551,7 @@ pub const Body = struct { } } - if (result.headers == null and result.status_code < 200) return null; + if (result.headers.isEmptyOrUndefinedOrNull() and result.status_code < 200) return null; return result; } }; @@ -4375,7 +3741,6 @@ pub const Body = struct { pub fn @"200"(_: js.JSContextRef) Body { return Body{ .init = Init{ - .headers = null, .status_code = 200, }, .value = Value.empty, @@ -4411,7 +3776,7 @@ pub const Body = struct { exception: js.ExceptionRef, ) Body { var body = Body{ - .init = Init{ .headers = null, .status_code = 200 }, + .init = Init{ .status_code = 200 }, }; const value = JSC.JSValue.fromRef(body_ref); var allocator = getAllocator(ctx); @@ -4443,30 +3808,28 @@ pub const Body = struct { // https://developer.mozilla.org/en-US/docs/Web/API/Request pub const Request = struct { url: ZigString = ZigString.Empty, - headers: ?*Headers.RefCountedHeaders = null, + headers: JSValue = JSValue.zero, body: Body.Value = Body.Value{ .Empty = .{} }, method: Method = Method.GET, uws_request: ?*uws.Request = null, - pub fn fromRequestContext(ctx: *RequestContext) !Request { + pub fn fromRequestContext(ctx: *RequestContext, global: *JSGlobalObject) !Request { var req = Request{ .url = ZigString.init(std.mem.span(ctx.getFullURL())), .body = Body.Value.empty, .method = ctx.method, - .headers = try Headers.RefCountedHeaders.init(Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable, bun.default_allocator), + .headers = FetchHeaders.createFromPicoHeaders(global, ctx.request.headers), }; req.url.mark(); return req; } pub fn mimeType(this: *const Request) string { - if (this.headers) |headers_ref| { - var headers = headers_ref.get(); - defer headers_ref.deref(); + if (this.headers.as(FetchHeaders)) |headers| { // Remember, we always lowercase it // hopefully doesn't matter here tho - if (headers.getHeaderIndex("content-type")) |content_type| { - return headers.asStr(headers.entries.items(.value)[content_type]); + if (headers.get("content-type")) |content_type| { + return content_type; } } @@ -4658,10 +4021,9 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - if (this.headers) |headers_ref| { - var headers = headers_ref.leak(); - if (headers.getHeaderIndex("referrer")) |i| { - return ZigString.init(headers.asStr(headers.entries.get(i).value)).toValueGC(ctx.ptr()).asObjectRef(); + if (this.headers.as(FetchHeaders)) |headers_ref| { + if (headers_ref.get("referrer")) |referrer| { + return ZigString.init(referrer).toValueGC(ctx.ptr()).asRef(); } } @@ -4759,7 +4121,7 @@ pub const Request = struct { _: []const js.JSValueRef, _: js.ExceptionRef, ) js.JSValueRef { - var cloned = this.clone(getAllocator(ctx)); + var cloned = this.clone(getAllocator(ctx), ctx.ptr()); return Request.Class.make(ctx, cloned); } @@ -4770,37 +4132,38 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - if (this.headers == null) { - this.headers = Headers.RefCountedHeaders.init(Headers.empty(bun.default_allocator), bun.default_allocator) catch unreachable; - + if (this.headers.isEmptyOrUndefinedOrNull()) { if (this.uws_request) |req| { - this.headers.?.value.allocator = bun.default_allocator; - this.headers.?.value.clonefromUWSRequest(req); + this.headers = FetchHeaders.createFromUWS(ctx.ptr(), req); + } else { + this.headers = FetchHeaders.createEmpty(ctx.ptr()); } } - return Headers.Class.make(ctx, this.headers.?.getRef()); + return this.headers.asObjectRef(); } - pub fn cloneInto(this: *const Request, req: *Request, allocator: std.mem.Allocator) void { + pub fn cloneInto( + this: *const Request, + req: *Request, + allocator: std.mem.Allocator, + globalThis: *JSGlobalObject, + ) void { req.* = Request{ .body = this.body.clone(allocator), .url = ZigString.init(allocator.dupe(u8, this.url.slice()) catch unreachable), .method = this.method, }; - if (this.headers) |head| { - var new_headers = Headers.RefCountedHeaders.init(undefined, allocator) catch unreachable; - head.leak().clone(&new_headers.value) catch unreachable; - req.headers = new_headers; + if (this.headers.as(FetchHeaders)) |head| { + req.headers = head.clone(globalThis); } else if (this.uws_request) |uws_req| { - req.headers = Headers.RefCountedHeaders.init(Headers.empty(allocator), allocator) catch unreachable; - req.headers.?.value.clonefromUWSRequest(uws_req); + req.headers = FetchHeaders.createFromUWS(globalThis, uws_req); } } - pub fn clone(this: *const Request, allocator: std.mem.Allocator) *Request { + pub fn clone(this: *const Request, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) *Request { var req = allocator.create(Request) catch unreachable; - this.cloneInto(req, allocator); + this.cloneInto(req, allocator, globalThis); return req; } }; @@ -5039,10 +4402,10 @@ pub const FetchEvent = struct { defer this.pending_promise = null; var needs_mime_type = true; var content_length: ?usize = null; - if (response.body.init.headers) |headers_ref| { - var headers = headers_ref.get(); - defer headers_ref.deref(); - request_context.clearHeaders() catch {}; + + if (response.body.init.headers.as(JSC.FetchHeaders)) |headers_ref| { + var headers = Headers.from(headers_ref, request_context.allocator) catch unreachable; + var i: usize = 0; while (i < headers.entries.len) : (i += 1) { var header = headers.entries.get(i); |