diff options
author | 2023-02-13 00:50:15 -0800 | |
---|---|---|
committer | 2023-02-13 00:50:15 -0800 | |
commit | aa0762e4660bb17b86890b923368e5a0dc8daf7b (patch) | |
tree | a134621368f9def9a85473e90a6189afb956b457 /src | |
parent | cdbc620104b939f7112fa613ca192e5fe6e02a7d (diff) | |
download | bun-aa0762e4660bb17b86890b923368e5a0dc8daf7b.tar.gz bun-aa0762e4660bb17b86890b923368e5a0dc8daf7b.tar.zst bun-aa0762e4660bb17b86890b923368e5a0dc8daf7b.zip |
Implement `FormData` (#2051)
* Backport std::forward change
* Implement `FormData`
* Fix io_darwin headers issue
* Implement `Blob` support in FormData
* Add test for file upload
* Fix bug with Blob not reading Content-Type
* Finish implementing FormData
* Add FormData to types
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Diffstat (limited to 'src')
29 files changed, 2194 insertions, 46 deletions
diff --git a/src/bun.js/api/bun/dns_resolver.zig b/src/bun.js/api/bun/dns_resolver.zig index ef1c6bc4a..83b2e9e87 100644 --- a/src/bun.js/api/bun/dns_resolver.zig +++ b/src/bun.js/api/bun/dns_resolver.zig @@ -867,6 +867,7 @@ pub const GetAddrInfoRequest = struct { pub fn onMachportChange(this: *GetAddrInfoRequest) void { if (comptime !Environment.isMac) unreachable; + bun.JSC.markBinding(@src()); if (!getaddrinfo_send_reply(this.backend.libinfo.machport.?, JSC.DNS.LibInfo.getaddrinfo_async_handle_reply().?)) { log("onMachportChange: getaddrinfo_send_reply failed", .{}); diff --git a/src/bun.js/bindings/DOMFormData.cpp b/src/bun.js/bindings/DOMFormData.cpp new file mode 100644 index 000000000..6c204c5f4 --- /dev/null +++ b/src/bun.js/bindings/DOMFormData.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2010 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: + * + * * 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 "DOMFormData.h" +#include "wtf/URLParser.h" + +namespace WebCore { + +DOMFormData::DOMFormData(ScriptExecutionContext* context) + : ContextDestructionObserver(context) +{ +} + +Ref<DOMFormData> DOMFormData::create(ScriptExecutionContext* context) +{ + return adoptRef(*new DOMFormData(context)); +} + +Ref<DOMFormData> DOMFormData::create(ScriptExecutionContext* context, StringView urlEncodedString) +{ + auto newFormData = adoptRef(*new DOMFormData(context)); + for (auto& entry : WTF::URLParser::parseURLEncodedForm(urlEncodedString)) { + newFormData->append(entry.key, entry.value); + } + + return newFormData; +} + +String DOMFormData::toURLEncodedString() +{ + WTF::URLParser::URLEncodedForm form; + form.reserveInitialCapacity(m_items.size()); + for (auto& item : m_items) { + if (auto value = std::get_if<String>(&item.data)) + form.append({ item.name, *value }); + } + + return WTF::URLParser::serialize(form); +} + +extern "C" void DOMFormData__forEach(DOMFormData* form, void* context, void (*callback)(void* context, ZigString*, void*, ZigString*, uint8_t)) +{ + for (auto& item : form->items()) { + auto name = toZigString(item.name); + if (auto value = std::get_if<String>(&item.data)) { + auto value_ = toZigString(*value); + callback(context, &name, &value_, nullptr, 0); + } else if (auto value = std::get_if<RefPtr<Blob>>(&item.data)) { + auto filename = toZigString(value->get()->fileName()); + callback(context, &name, value->get()->impl(), &filename, 1); + } + } +} + +Ref<DOMFormData> DOMFormData::clone() const +{ + auto newFormData = adoptRef(*new DOMFormData(scriptExecutionContext())); + newFormData->m_items = m_items; + + return newFormData; +} + +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry +static auto createStringEntry(const String& name, const String& value) -> DOMFormData::Item +{ + return { + replaceUnpairedSurrogatesWithReplacementCharacter(String(name)), + replaceUnpairedSurrogatesWithReplacementCharacter(String(value)), + }; +} + +void DOMFormData::append(const String& name, const String& value) +{ + m_items.append(createStringEntry(name, value)); +} + +void DOMFormData::append(const String& name, RefPtr<Blob> blob, const String& filename) +{ + blob->setFileName(replaceUnpairedSurrogatesWithReplacementCharacter(String(filename))); + m_items.append({ replaceUnpairedSurrogatesWithReplacementCharacter(String(name)), blob }); +} +void DOMFormData::remove(const String& name) +{ + m_items.removeAllMatching([&name](const auto& item) { + return item.name == name; + }); +} + +auto DOMFormData::get(const String& name) -> std::optional<FormDataEntryValue> +{ + for (auto& item : m_items) { + if (item.name == name) + return item.data; + } + + return std::nullopt; +} + +auto DOMFormData::getAll(const String& name) -> Vector<FormDataEntryValue> +{ + Vector<FormDataEntryValue> result; + + for (auto& item : m_items) { + if (item.name == name) + result.append(item.data); + } + + return result; +} + +bool DOMFormData::has(const String& name) +{ + for (auto& item : m_items) { + if (item.name == name) + return true; + } + + return false; +} + +void DOMFormData::set(const String& name, const String& value) +{ + set(name, { name, value }); +} + +void DOMFormData::set(const String& name, RefPtr<Blob> blob, const String& filename) +{ + blob->setFileName(filename); + set(name, { name, blob }); +} + +void DOMFormData::set(const String& name, Item&& item) +{ + std::optional<size_t> initialMatchLocation; + + // Find location of the first item with a matching name. + for (size_t i = 0; i < m_items.size(); ++i) { + if (name == m_items[i].name) { + initialMatchLocation = i; + break; + } + } + + if (initialMatchLocation) { + m_items[*initialMatchLocation] = WTFMove(item); + + m_items.removeAllMatching([&name](const auto& item) { + return item.name == name; + }, + *initialMatchLocation + 1); + return; + } + + m_items.append(WTFMove(item)); +} + +DOMFormData::Iterator::Iterator(DOMFormData& target) + : m_target(target) +{ +} + +std::optional<KeyValuePair<String, DOMFormData::FormDataEntryValue>> DOMFormData::Iterator::next() +{ + auto& items = m_target->items(); + if (m_index >= items.size()) + return std::nullopt; + + auto& item = items[m_index++]; + return makeKeyValuePair(item.name, item.data); +} + +} // namespace WebCore diff --git a/src/bun.js/bindings/DOMFormData.h b/src/bun.js/bindings/DOMFormData.h new file mode 100644 index 000000000..0970d9064 --- /dev/null +++ b/src/bun.js/bindings/DOMFormData.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010 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: + * + * * 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 "ContextDestructionObserver.h" +#include <variant> +#include <wtf/RefCounted.h> +#include <wtf/text/WTFString.h> +#include "blob.h" +namespace WebCore { + +template<typename> class ExceptionOr; +class HTMLElement; +class HTMLFormElement; + +class DOMFormData : public RefCounted<DOMFormData>, public ContextDestructionObserver { +public: + using FormDataEntryValue = std::variant<String, RefPtr<Blob>>; + + struct Item { + String name; + FormDataEntryValue data; + }; + + // static Ref<DOMFormData> create(ScriptExecutionContext*, const PAL::TextEncoding&); + static Ref<DOMFormData> create(ScriptExecutionContext*); + static Ref<DOMFormData> create(ScriptExecutionContext*, StringView urlEncodedString); + + const Vector<Item>& items() const { return m_items; } + // const PAL::TextEncoding& encoding() const { return m_encoding; } + + void append(const String& name, const String& value); + void append(const String& name, RefPtr<Blob>, const String& filename = {}); + void remove(const String& name); + std::optional<FormDataEntryValue> get(const String& name); + Vector<FormDataEntryValue> getAll(const String& name); + bool has(const String& name); + void set(const String& name, const String& value); + void set(const String& name, RefPtr<Blob>, const String& filename = {}); + Ref<DOMFormData> clone() const; + + size_t count() const { return m_items.size(); } + + String toURLEncodedString(); + + class Iterator { + public: + explicit Iterator(DOMFormData&); + std::optional<KeyValuePair<String, FormDataEntryValue>> next(); + + private: + Ref<DOMFormData> m_target; + size_t m_index { 0 }; + }; + Iterator createIterator() { return Iterator { *this }; } + +private: + // explicit DOMFormData(ScriptExecutionContext*, const PAL::TextEncoding& = PAL::UTF8Encoding()); + explicit DOMFormData(ScriptExecutionContext*); + + void set(const String& name, Item&&); + + // PAL::TextEncoding m_encoding; + Vector<Item> m_items; +}; + +} // namespace WebCore diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp index ac321a520..c1e7f674f 100644 --- a/src/bun.js/bindings/ZigGeneratedClasses.cpp +++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp @@ -103,6 +103,9 @@ extern "C" void BlobClass__finalize(void*); extern "C" EncodedJSValue BlobPrototype__getArrayBuffer(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(BlobPrototype__arrayBufferCallback); +extern "C" EncodedJSValue BlobPrototype__getFormData(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(BlobPrototype__formDataCallback); + extern "C" EncodedJSValue BlobPrototype__getJSON(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(BlobPrototype__jsonCallback); @@ -131,6 +134,7 @@ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBlobPrototype, JSBlobPrototype::Base); static const HashTableValue JSBlobPrototypeTableValues[] = { { "arrayBuffer"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BlobPrototype__arrayBufferCallback, 0 } }, + { "formData"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BlobPrototype__formDataCallback, 0 } }, { "json"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BlobPrototype__jsonCallback, 0 } }, { "size"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, BlobPrototype__sizeGetterWrap, 0 } }, { "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, BlobPrototype__sliceCallback, 2 } }, @@ -170,6 +174,22 @@ JSC_DEFINE_HOST_FUNCTION(BlobPrototype__arrayBufferCallback, (JSGlobalObject * l return BlobPrototype__getArrayBuffer(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(BlobPrototype__formDataCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSBlob* thisObject = jsDynamicCast<JSBlob*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return BlobPrototype__getFormData(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_HOST_FUNCTION(BlobPrototype__jsonCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = lexicalGlobalObject->vm(); @@ -6154,6 +6174,9 @@ JSC_DECLARE_CUSTOM_GETTER(RequestPrototype__credentialsGetterWrap); extern "C" JSC::EncodedJSValue RequestPrototype__getDestination(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); JSC_DECLARE_CUSTOM_GETTER(RequestPrototype__destinationGetterWrap); +extern "C" EncodedJSValue RequestPrototype__getFormData(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(RequestPrototype__formDataCallback); + extern "C" JSC::EncodedJSValue RequestPrototype__getHeaders(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); JSC_DECLARE_CUSTOM_GETTER(RequestPrototype__headersGetterWrap); @@ -6195,6 +6218,7 @@ static const HashTableValue JSRequestPrototypeTableValues[] = { { "clone"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, RequestPrototype__cloneCallback, 1 } }, { "credentials"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, RequestPrototype__credentialsGetterWrap, 0 } }, { "destination"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, RequestPrototype__destinationGetterWrap, 0 } }, + { "formData"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, RequestPrototype__formDataCallback, 0 } }, { "headers"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, RequestPrototype__headersGetterWrap, 0 } }, { "integrity"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, RequestPrototype__integrityGetterWrap, 0 } }, { "json"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, RequestPrototype__jsonCallback, 0 } }, @@ -6348,6 +6372,22 @@ JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__destinationGetterWrap, (JSGlobalObjec RELEASE_AND_RETURN(throwScope, result); } +JSC_DEFINE_HOST_FUNCTION(RequestPrototype__formDataCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSRequest* thisObject = jsDynamicCast<JSRequest*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return RequestPrototype__getFormData(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_CUSTOM_GETTER(RequestPrototype__headersGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = lexicalGlobalObject->vm(); @@ -6795,6 +6835,9 @@ JSC_DECLARE_CUSTOM_GETTER(ResponsePrototype__bodyUsedGetterWrap); extern "C" EncodedJSValue ResponsePrototype__doClone(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); JSC_DECLARE_HOST_FUNCTION(ResponsePrototype__cloneCallback); +extern "C" EncodedJSValue ResponsePrototype__getFormData(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame); +JSC_DECLARE_HOST_FUNCTION(ResponsePrototype__formDataCallback); + extern "C" JSC::EncodedJSValue ResponsePrototype__getHeaders(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject); JSC_DECLARE_CUSTOM_GETTER(ResponsePrototype__headersGetterWrap); @@ -6830,6 +6873,7 @@ static const HashTableValue JSResponsePrototypeTableValues[] = { { "body"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ResponsePrototype__bodyGetterWrap, 0 } }, { "bodyUsed"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ResponsePrototype__bodyUsedGetterWrap, 0 } }, { "clone"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponsePrototype__cloneCallback, 1 } }, + { "formData"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponsePrototype__formDataCallback, 0 } }, { "headers"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ResponsePrototype__headersGetterWrap, 0 } }, { "json"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::NativeFunctionType, ResponsePrototype__jsonCallback, 0 } }, { "ok"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, ResponsePrototype__okGetterWrap, 0 } }, @@ -6946,6 +6990,22 @@ JSC_DEFINE_HOST_FUNCTION(ResponsePrototype__cloneCallback, (JSGlobalObject * lex return ResponsePrototype__doClone(thisObject->wrapped(), lexicalGlobalObject, callFrame); } +JSC_DEFINE_HOST_FUNCTION(ResponsePrototype__formDataCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto& vm = lexicalGlobalObject->vm(); + + JSResponse* thisObject = jsDynamicCast<JSResponse*>(callFrame->thisValue()); + + if (UNLIKELY(!thisObject)) { + auto throwScope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(lexicalGlobalObject, throwScope); + } + + JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject); + + return ResponsePrototype__getFormData(thisObject->wrapped(), lexicalGlobalObject, callFrame); +} + JSC_DEFINE_CUSTOM_GETTER(ResponsePrototype__headersGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = lexicalGlobalObject->vm(); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index ce29e43a6..bcb462d42 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -167,6 +167,8 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers; #include "webcrypto/JSCryptoKey.h" #include "webcrypto/JSSubtleCrypto.h" +#include "JSDOMFormData.h" + constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10; #ifdef __APPLE__ @@ -614,6 +616,9 @@ WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSTextEncoder); WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSURLSearchParams); WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSURLSearchParams); +WEBCORE_GENERATED_CONSTRUCTOR_GETTER(JSDOMFormData); +WEBCORE_GENERATED_CONSTRUCTOR_SETTER(JSDOMFormData); + JSC_DECLARE_CUSTOM_GETTER(JSEvent_getter); JSC_DEFINE_CUSTOM_GETTER(JSEvent_getter, @@ -3234,6 +3239,7 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); PUT_WEBCORE_GENERATED_CONSTRUCTOR("TextEncoder"_s, JSTextEncoder); + PUT_WEBCORE_GENERATED_CONSTRUCTOR("FormData"_s, JSDOMFormData); PUT_WEBCORE_GENERATED_CONSTRUCTOR("MessageEvent"_s, JSMessageEvent); PUT_WEBCORE_GENERATED_CONSTRUCTOR("WebSocket"_s, JSWebSocket); PUT_WEBCORE_GENERATED_CONSTRUCTOR("Headers"_s, JSFetchHeaders); @@ -3552,6 +3558,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisObject->m_JSFetchHeadersSetterValue); visitor.append(thisObject->m_JSTextEncoderSetterValue); visitor.append(thisObject->m_JSURLSearchParamsSetterValue); + visitor.append(thisObject->m_JSDOMFormDataSetterValue); thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor); thisObject->m_JSBufferListClassStructure.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 2b688f09d..cad68f79a 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -89,9 +89,9 @@ public: return WebCore::subspaceForImpl<GlobalObject, WebCore::UseCustomHeapCellType::Yes>( vm, [](auto& spaces) { return spaces.m_clientSubspaceForWorkerGlobalScope.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWorkerGlobalScope = WTFMove(space); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWorkerGlobalScope = std::forward<decltype(space)>(space); }, [](auto& spaces) { return spaces.m_subspaceForWorkerGlobalScope.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForWorkerGlobalScope = WTFMove(space); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForWorkerGlobalScope = std::forward<decltype(space)>(space); }, [](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForJSWorkerGlobalScope; }); } @@ -344,6 +344,7 @@ public: mutable WriteBarrier<Unknown> m_JSTextEncoderSetterValue; mutable WriteBarrier<Unknown> m_JSURLSearchParamsSetterValue; mutable WriteBarrier<Unknown> m_JSWebSocketSetterValue; + mutable WriteBarrier<Unknown> m_JSDOMFormDataSetterValue; mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1]; diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 02a79c1b0..aa8a28601 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -85,6 +85,10 @@ #include "JavaScriptCore/HashMapImpl.h" #include "JavaScriptCore/HashMapImplInlines.h" +#include "DOMFormData.h" +#include "JSDOMFormData.h" +#include "ZigGeneratedClasses.h" + template<typename UWSResponse> static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) { @@ -3726,4 +3730,53 @@ extern "C" void JSC__JSGlobalObject__queueMicrotaskJob(JSC__JSGlobalObject* arg0 JSC::JSValue::decode(JSValue2), JSC::JSValue::decode(JSValue3), JSC::JSValue::decode(JSValue4)); +} + +#pragma mark - WebCore::DOMFormData + +CPP_DECL void WebCore__DOMFormData__append(WebCore__DOMFormData* arg0, ZigString* arg1, ZigString* arg2) +{ + arg0->append(toStringCopy(*arg1), toStringCopy(*arg2)); +} + +CPP_DECL void WebCore__DOMFormData__appendBlob(WebCore__DOMFormData* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, void* blobValueInner, ZigString* fileName) +{ + RefPtr<Blob> blob = WebCore::Blob::create(blobValueInner); + arg0->append(toStringCopy(*arg2), blob, toStringCopy(*fileName)); +} +CPP_DECL size_t WebCore__DOMFormData__count(WebCore__DOMFormData* arg0) +{ + return arg0->count(); +} + +extern "C" void DOMFormData__toQueryString( + DOMFormData* formData, + void* ctx, + void (*callback)(void* ctx, ZigString* encoded)) +{ + auto str = formData->toURLEncodedString(); + ZigString encoded = toZigString(str); + callback(ctx, &encoded); +} + +CPP_DECL JSC__JSValue WebCore__DOMFormData__createFromURLQuery(JSC__JSGlobalObject* arg0, ZigString* arg1) +{ + JSC::VM& vm = arg0->vm(); + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0); + // don't need to copy the string because it internally does. + auto formData = DOMFormData::create(globalObject->scriptExecutionContext(), toString(*arg1)); + return JSValue::encode(toJSNewlyCreated(arg0, globalObject, WTFMove(formData))); +} + +CPP_DECL JSC__JSValue WebCore__DOMFormData__create(JSC__JSGlobalObject* arg0) +{ + JSC::VM& vm = arg0->vm(); + Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0); + auto formData = DOMFormData::create(globalObject->scriptExecutionContext()); + return JSValue::encode(toJSNewlyCreated(arg0, globalObject, WTFMove(formData))); +} + +CPP_DECL WebCore__DOMFormData* WebCore__DOMFormData__fromJS(JSC__JSValue JSValue1) +{ + return WebCoreCast<WebCore::JSDOMFormData, WebCore__DOMFormData>(JSValue1); }
\ No newline at end of file diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 447c6c043..d0ccacd06 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -794,6 +794,157 @@ pub const DOMURL = opaque { const Api = @import("../../api/schema.zig").Api; +pub const DOMFormData = opaque { + pub const shim = Shimmer("WebCore", "DOMFormData", @This()); + + pub const name = "WebCore::DOMFormData"; + pub const include = "DOMFormData.h"; + pub const namespace = "WebCore"; + + const cppFn = shim.cppFn; + + pub fn create( + global: *JSGlobalObject, + ) JSValue { + return shim.cppFn("create", .{ + global, + }); + } + + pub fn createFromURLQuery( + global: *JSGlobalObject, + query: *ZigString, + ) JSValue { + return shim.cppFn("createFromURLQuery", .{ + global, + query, + }); + } + + extern fn DOMFormData__toQueryString( + *DOMFormData, + ctx: *anyopaque, + callback: *const fn (ctx: *anyopaque, *ZigString) callconv(.C) void, + ) void; + + pub fn toQueryString( + this: *DOMFormData, + comptime Ctx: type, + ctx: Ctx, + comptime callback: fn (ctx: Ctx, ZigString) callconv(.C) void, + ) void { + const Wrapper = struct { + const cb = callback; + pub fn run(c: *anyopaque, str: *ZigString) callconv(.C) void { + cb(@ptrCast(Ctx, c), str.*); + } + }; + + DOMFormData__toQueryString(this, ctx, &Wrapper.run); + } + + pub fn fromJS( + value: JSValue, + ) ?*DOMFormData { + return shim.cppFn("fromJS", .{ + value, + }); + } + + pub fn append( + this: *DOMFormData, + name_: *ZigString, + value_: *ZigString, + ) void { + return shim.cppFn("append", .{ + this, + name_, + value_, + }); + } + + pub fn appendBlob( + this: *DOMFormData, + global: *JSC.JSGlobalObject, + name_: *ZigString, + blob: *anyopaque, + filename_: *ZigString, + ) void { + return shim.cppFn("appendBlob", .{ + this, + global, + name_, + blob, + filename_, + }); + } + + pub fn count( + this: *DOMFormData, + ) usize { + return shim.cppFn("count", .{ + this, + }); + } + + const ForEachFunction = *const fn ( + ctx_ptr: ?*anyopaque, + name: *ZigString, + value_ptr: *anyopaque, + filename: ?*ZigString, + is_blob: u8, + ) callconv(.C) void; + + extern fn DOMFormData__forEach(*DOMFormData, ?*anyopaque, ForEachFunction) void; + pub const FormDataEntry = union(enum) { + string: ZigString, + file: struct { + blob: *JSC.WebCore.Blob, + filename: ZigString, + }, + }; + pub fn forEach( + this: *DOMFormData, + comptime Context: type, + ctx: *Context, + comptime callback_wrapper: *const fn (ctx: *Context, name: ZigString, value: FormDataEntry) void, + ) void { + const Wrap = struct { + const wrapper = callback_wrapper; + pub fn forEachWrapper( + ctx_ptr: ?*anyopaque, + name_: *ZigString, + value_ptr: *anyopaque, + filename: ?*ZigString, + is_blob: u8, + ) callconv(.C) void { + var ctx_ = bun.cast(*Context, ctx_ptr.?); + const value = if (is_blob == 0) + FormDataEntry{ .string = bun.cast(*ZigString, value_ptr).* } + else + FormDataEntry{ + .file = .{ + .blob = bun.cast(*JSC.WebCore.Blob, value_ptr), + .filename = (filename orelse &ZigString.Empty).*, + }, + }; + + wrapper(ctx_, name_.*, value); + } + }; + JSC.markBinding(@src()); + DOMFormData__forEach(this, ctx, Wrap.forEachWrapper); + } + + pub const Extern = [_][]const u8{ + "create", + "fromJS", + "append", + "appendBlob", + "count", + "createFromURLQuery", + }; +}; pub const FetchHeaders = opaque { pub const shim = Shimmer("WebCore", "FetchHeaders", @This()); diff --git a/src/bun.js/bindings/blob.cpp b/src/bun.js/bindings/blob.cpp new file mode 100644 index 000000000..257f230e1 --- /dev/null +++ b/src/bun.js/bindings/blob.cpp @@ -0,0 +1,17 @@ +#include "blob.h" + +extern "C" JSC::EncodedJSValue Blob__create(JSC::JSGlobalObject* globalObject, void* impl); + +namespace WebCore { + +JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, WebCore::Blob& impl) +{ + return JSC::JSValue::decode(Blob__create(lexicalGlobalObject, Blob__dupe(impl.impl()))); +} + +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Ref<WebCore::Blob>&& impl) +{ + return JSC::JSValue::decode(Blob__create(lexicalGlobalObject, impl->impl())); +} + +}
\ No newline at end of file diff --git a/src/bun.js/bindings/blob.h b/src/bun.js/bindings/blob.h new file mode 100644 index 000000000..83d6ff3af --- /dev/null +++ b/src/bun.js/bindings/blob.h @@ -0,0 +1,68 @@ +#pragma once + +#include "root.h" +#include "JSDOMGlobalObject.h" + +namespace WebCore { + +extern "C" void* Blob__dupeFromJS(JSC::EncodedJSValue impl); +extern "C" void* Blob__dupe(void* impl); +extern "C" void Blob__destroy(void* impl); + +class Blob : public RefCounted<Blob> { +public: + void* impl() + { + return m_impl; + } + + static RefPtr<Blob> create(JSC::JSValue impl) + { + void* implPtr = Blob__dupeFromJS(JSValue::encode(impl)); + if (!implPtr) + return nullptr; + + return adoptRef(*new Blob(implPtr)); + } + + static RefPtr<Blob> create(void* ptr) + { + void* implPtr = Blob__dupe(ptr); + if (!implPtr) + return nullptr; + + return adoptRef(*new Blob(implPtr)); + } + + ~Blob() + { + Blob__destroy(m_impl); + } + + String fileName() + { + return m_fileName; + } + + void setFileName(String fileName) + { + m_fileName = fileName; + } + +private: + Blob(void* impl, String fileName = String()) + { + m_impl = impl; + m_fileName = fileName; + } + + void* m_impl; + String m_fileName; +}; + +JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, Blob&); +inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Blob* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); } +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<Blob>&&); +inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<Blob>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); } + +} diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig index 2f4e7b0b6..599d05d7e 100644 --- a/src/bun.js/bindings/generated_classes.zig +++ b/src/bun.js/bindings/generated_classes.zig @@ -86,6 +86,8 @@ pub const JSBlob = struct { if (@TypeOf(Blob.getArrayBuffer) != CallbackType) @compileLog("Expected Blob.getArrayBuffer to be a callback but received " ++ @typeName(@TypeOf(Blob.getArrayBuffer))); + if (@TypeOf(Blob.getFormData) != CallbackType) + @compileLog("Expected Blob.getFormData to be a callback but received " ++ @typeName(@TypeOf(Blob.getFormData))); if (@TypeOf(Blob.getJSON) != CallbackType) @compileLog("Expected Blob.getJSON to be a callback but received " ++ @typeName(@TypeOf(Blob.getJSON))); if (@TypeOf(Blob.getSize) != GetterType) @@ -108,6 +110,7 @@ pub const JSBlob = struct { @export(Blob.constructor, .{ .name = "BlobClass__construct" }); @export(Blob.finalize, .{ .name = "BlobClass__finalize" }); @export(Blob.getArrayBuffer, .{ .name = "BlobPrototype__getArrayBuffer" }); + @export(Blob.getFormData, .{ .name = "BlobPrototype__getFormData" }); @export(Blob.getJSON, .{ .name = "BlobPrototype__getJSON" }); @export(Blob.getSize, .{ .name = "BlobPrototype__getSize" }); @export(Blob.getSlice, .{ .name = "BlobPrototype__getSlice" }); @@ -1771,6 +1774,8 @@ pub const JSRequest = struct { if (@TypeOf(Request.getDestination) != GetterType) @compileLog("Expected Request.getDestination to be a getter"); + if (@TypeOf(Request.getFormData) != CallbackType) + @compileLog("Expected Request.getFormData to be a callback but received " ++ @typeName(@TypeOf(Request.getFormData))); if (@TypeOf(Request.getHeaders) != GetterType) @compileLog("Expected Request.getHeaders to be a getter"); @@ -1811,6 +1816,7 @@ pub const JSRequest = struct { @export(Request.getCache, .{ .name = "RequestPrototype__getCache" }); @export(Request.getCredentials, .{ .name = "RequestPrototype__getCredentials" }); @export(Request.getDestination, .{ .name = "RequestPrototype__getDestination" }); + @export(Request.getFormData, .{ .name = "RequestPrototype__getFormData" }); @export(Request.getHeaders, .{ .name = "RequestPrototype__getHeaders" }); @export(Request.getIntegrity, .{ .name = "RequestPrototype__getIntegrity" }); @export(Request.getJSON, .{ .name = "RequestPrototype__getJSON" }); @@ -1990,6 +1996,8 @@ pub const JSResponse = struct { if (@TypeOf(Response.doClone) != CallbackType) @compileLog("Expected Response.doClone to be a callback but received " ++ @typeName(@TypeOf(Response.doClone))); + if (@TypeOf(Response.getFormData) != CallbackType) + @compileLog("Expected Response.getFormData to be a callback but received " ++ @typeName(@TypeOf(Response.getFormData))); if (@TypeOf(Response.getHeaders) != GetterType) @compileLog("Expected Response.getHeaders to be a getter"); @@ -2033,6 +2041,7 @@ pub const JSResponse = struct { @export(Response.getBlob, .{ .name = "ResponsePrototype__getBlob" }); @export(Response.getBody, .{ .name = "ResponsePrototype__getBody" }); @export(Response.getBodyUsed, .{ .name = "ResponsePrototype__getBodyUsed" }); + @export(Response.getFormData, .{ .name = "ResponsePrototype__getFormData" }); @export(Response.getHeaders, .{ .name = "ResponsePrototype__getHeaders" }); @export(Response.getJSON, .{ .name = "ResponsePrototype__getJSON" }); @export(Response.getOK, .{ .name = "ResponsePrototype__getOK" }); diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index f1e7de1dd..4dc7a2143 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1674546420 +//-- AUTOGENERATED FILE -- 1676266700 // clang-format off #pragma once @@ -16,6 +16,14 @@ extern "C" const size_t JSC__JSObject_object_size_ = sizeof(JSC::JSObject); extern "C" const size_t JSC__JSObject_object_align_ = alignof(JSC::JSObject); +#ifndef INCLUDED_DOMFormData_h +#define INCLUDED_DOMFormData_h +#include "DOMFormData.h" +#endif + +extern "C" const size_t WebCore__DOMFormData_object_size_ = sizeof(WebCore::DOMFormData); +extern "C" const size_t WebCore__DOMFormData_object_align_ = alignof(WebCore::DOMFormData); + #ifndef INCLUDED_FetchHeaders_h #define INCLUDED_FetchHeaders_h #include "FetchHeaders.h" @@ -152,8 +160,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[38] = {sizeof(JSC::JSObject), sizeof(WebCore::DOMURL), sizeof(WebCore::FetchHeaders), sizeof(SystemError), sizeof(JSC::JSCell), sizeof(JSC::JSString), sizeof(JSC::JSModuleLoader), sizeof(JSC::JSPromise), sizeof(JSC::JSInternalPromise), sizeof(JSC::JSFunction), sizeof(JSC::JSGlobalObject), sizeof(JSC::JSValue), sizeof(JSC::Exception), sizeof(JSC::VM), sizeof(JSC::ThrowScope), sizeof(JSC::CatchScope), sizeof(FFI__ptr), sizeof(Reader__u8), sizeof(Reader__u16), sizeof(Reader__u32), sizeof(Reader__ptr), sizeof(Reader__i8), sizeof(Reader__i16), sizeof(Reader__i32), sizeof(Reader__f32), sizeof(Reader__f64), sizeof(Reader__i64), sizeof(Reader__u64), sizeof(Reader__intptr), sizeof(Crypto__getRandomValues), sizeof(Crypto__randomUUID), sizeof(Crypto__timingSafeEqual), sizeof(Zig::GlobalObject), sizeof(Bun__Path), sizeof(ArrayBufferSink), sizeof(HTTPSResponseSink), sizeof(HTTPResponseSink), sizeof(FileSink)}; +const size_t sizes[39] = {sizeof(JSC::JSObject), sizeof(WebCore::DOMURL), sizeof(WebCore::DOMFormData), sizeof(WebCore::FetchHeaders), sizeof(SystemError), sizeof(JSC::JSCell), sizeof(JSC::JSString), sizeof(JSC::JSModuleLoader), sizeof(JSC::JSPromise), sizeof(JSC::JSInternalPromise), sizeof(JSC::JSFunction), sizeof(JSC::JSGlobalObject), sizeof(JSC::JSValue), sizeof(JSC::Exception), sizeof(JSC::VM), sizeof(JSC::ThrowScope), sizeof(JSC::CatchScope), sizeof(FFI__ptr), sizeof(Reader__u8), sizeof(Reader__u16), sizeof(Reader__u32), sizeof(Reader__ptr), sizeof(Reader__i8), sizeof(Reader__i16), sizeof(Reader__i32), sizeof(Reader__f32), sizeof(Reader__f64), sizeof(Reader__i64), sizeof(Reader__u64), sizeof(Reader__intptr), sizeof(Crypto__getRandomValues), sizeof(Crypto__randomUUID), sizeof(Crypto__timingSafeEqual), sizeof(Zig::GlobalObject), sizeof(Bun__Path), sizeof(ArrayBufferSink), sizeof(HTTPSResponseSink), sizeof(HTTPResponseSink), sizeof(FileSink)}; -const char* names[38] = {"JSC__JSObject", "WebCore__DOMURL", "WebCore__FetchHeaders", "SystemError", "JSC__JSCell", "JSC__JSString", "JSC__JSModuleLoader", "JSC__JSPromise", "JSC__JSInternalPromise", "JSC__JSFunction", "JSC__JSGlobalObject", "JSC__JSValue", "JSC__Exception", "JSC__VM", "JSC__ThrowScope", "JSC__CatchScope", "FFI__ptr", "Reader__u8", "Reader__u16", "Reader__u32", "Reader__ptr", "Reader__i8", "Reader__i16", "Reader__i32", "Reader__f32", "Reader__f64", "Reader__i64", "Reader__u64", "Reader__intptr", "Crypto__getRandomValues", "Crypto__randomUUID", "Crypto__timingSafeEqual", "Zig__GlobalObject", "Bun__Path", "ArrayBufferSink", "HTTPSResponseSink", "HTTPResponseSink", "FileSink"}; +const char* names[39] = {"JSC__JSObject", "WebCore__DOMURL", "WebCore__DOMFormData", "WebCore__FetchHeaders", "SystemError", "JSC__JSCell", "JSC__JSString", "JSC__JSModuleLoader", "JSC__JSPromise", "JSC__JSInternalPromise", "JSC__JSFunction", "JSC__JSGlobalObject", "JSC__JSValue", "JSC__Exception", "JSC__VM", "JSC__ThrowScope", "JSC__CatchScope", "FFI__ptr", "Reader__u8", "Reader__u16", "Reader__u32", "Reader__ptr", "Reader__i8", "Reader__i16", "Reader__i32", "Reader__f32", "Reader__f64", "Reader__i64", "Reader__u64", "Reader__intptr", "Crypto__getRandomValues", "Crypto__randomUUID", "Crypto__timingSafeEqual", "Zig__GlobalObject", "Bun__Path", "ArrayBufferSink", "HTTPSResponseSink", "HTTPResponseSink", "FileSink"}; -const size_t aligns[38] = {alignof(JSC::JSObject), alignof(WebCore::DOMURL), alignof(WebCore::FetchHeaders), alignof(SystemError), alignof(JSC::JSCell), alignof(JSC::JSString), alignof(JSC::JSModuleLoader), alignof(JSC::JSPromise), alignof(JSC::JSInternalPromise), alignof(JSC::JSFunction), alignof(JSC::JSGlobalObject), alignof(JSC::JSValue), alignof(JSC::Exception), alignof(JSC::VM), alignof(JSC::ThrowScope), alignof(JSC::CatchScope), alignof(FFI__ptr), alignof(Reader__u8), alignof(Reader__u16), alignof(Reader__u32), alignof(Reader__ptr), alignof(Reader__i8), alignof(Reader__i16), alignof(Reader__i32), alignof(Reader__f32), alignof(Reader__f64), alignof(Reader__i64), alignof(Reader__u64), alignof(Reader__intptr), alignof(Crypto__getRandomValues), alignof(Crypto__randomUUID), alignof(Crypto__timingSafeEqual), alignof(Zig::GlobalObject), alignof(Bun__Path), alignof(ArrayBufferSink), alignof(HTTPSResponseSink), alignof(HTTPResponseSink), alignof(FileSink)}; +const size_t aligns[39] = {alignof(JSC::JSObject), alignof(WebCore::DOMURL), alignof(WebCore::DOMFormData), alignof(WebCore::FetchHeaders), alignof(SystemError), alignof(JSC::JSCell), alignof(JSC::JSString), alignof(JSC::JSModuleLoader), alignof(JSC::JSPromise), alignof(JSC::JSInternalPromise), alignof(JSC::JSFunction), alignof(JSC::JSGlobalObject), alignof(JSC::JSValue), alignof(JSC::Exception), alignof(JSC::VM), alignof(JSC::ThrowScope), alignof(JSC::CatchScope), alignof(FFI__ptr), alignof(Reader__u8), alignof(Reader__u16), alignof(Reader__u32), alignof(Reader__ptr), alignof(Reader__i8), alignof(Reader__i16), alignof(Reader__i32), alignof(Reader__f32), alignof(Reader__f64), alignof(Reader__i64), alignof(Reader__u64), alignof(Reader__intptr), alignof(Crypto__getRandomValues), alignof(Crypto__randomUUID), alignof(Crypto__timingSafeEqual), alignof(Zig::GlobalObject), alignof(Bun__Path), alignof(ArrayBufferSink), alignof(HTTPSResponseSink), alignof(HTTPResponseSink), alignof(FileSink)}; diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 2d0f567d8..159a7acbd 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1674546420 +//-- AUTOGENERATED FILE -- 1676266700 #pragma once #include <stddef.h> @@ -55,8 +55,8 @@ typedef void* JSClassRef; typedef bJSC__JSObject JSC__JSObject; // JSC::JSObject typedef WebSocketClient WebSocketClient; typedef WebSocketHTTPSClient WebSocketHTTPSClient; - typedef bJSC__VM JSC__VM; // JSC::VM typedef JSClassRef JSClassRef; + typedef bJSC__VM JSC__VM; // JSC::VM typedef Bun__ArrayBuffer Bun__ArrayBuffer; typedef Uint8Array_alias Uint8Array_alias; typedef WebSocketClientTLS WebSocketClientTLS; @@ -72,6 +72,7 @@ typedef void* JSClassRef; typedef bJSC__JSInternalPromise JSC__JSInternalPromise; // JSC::JSInternalPromise typedef bJSC__Exception JSC__Exception; // JSC::Exception typedef bJSC__JSString JSC__JSString; // JSC::JSString + typedef struct WebCore__DOMFormData WebCore__DOMFormData; // WebCore::DOMFormData typedef struct JSC__CallFrame JSC__CallFrame; // JSC::CallFrame typedef struct WebCore__FetchHeaders WebCore__FetchHeaders; // WebCore::FetchHeaders @@ -92,6 +93,7 @@ typedef void* JSClassRef; class ThrowScope; } namespace WebCore { + class DOMFormData; class DOMURL; class FetchHeaders; } @@ -120,6 +122,7 @@ typedef void* JSClassRef; using JSC__VM = JSC::VM; using JSC__CallFrame = JSC::CallFrame; using JSC__ThrowScope = JSC::ThrowScope; + using WebCore__DOMFormData = WebCore::DOMFormData; using WebCore__DOMURL = WebCore::DOMURL; using WebCore__FetchHeaders = WebCore::FetchHeaders; @@ -146,6 +149,15 @@ CPP_DECL WebCore__DOMURL* WebCore__DOMURL__cast_(JSC__JSValue JSValue0, JSC__VM* CPP_DECL void WebCore__DOMURL__href_(WebCore__DOMURL* arg0, ZigString* arg1); CPP_DECL void WebCore__DOMURL__pathname_(WebCore__DOMURL* arg0, ZigString* arg1); +#pragma mark - WebCore::DOMFormData + +CPP_DECL void WebCore__DOMFormData__append(WebCore__DOMFormData* arg0, ZigString* arg1, ZigString* arg2); +CPP_DECL void WebCore__DOMFormData__appendBlob(WebCore__DOMFormData* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, void* arg3, ZigString* arg4); +CPP_DECL size_t WebCore__DOMFormData__count(WebCore__DOMFormData* arg0); +CPP_DECL JSC__JSValue WebCore__DOMFormData__create(JSC__JSGlobalObject* arg0); +CPP_DECL JSC__JSValue WebCore__DOMFormData__createFromURLQuery(JSC__JSGlobalObject* arg0, ZigString* arg1); +CPP_DECL WebCore__DOMFormData* WebCore__DOMFormData__fromJS(JSC__JSValue JSValue0); + #pragma mark - WebCore::FetchHeaders CPP_DECL void WebCore__FetchHeaders__append(WebCore__FetchHeaders* arg0, const ZigString* arg1, const ZigString* arg2); diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index f28a765fe..756a08bcc 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -97,6 +97,12 @@ pub extern fn ZigString__toValueGC(arg0: [*c]const ZigString, arg1: *bindings.JS pub extern fn WebCore__DOMURL__cast_(JSValue0: JSC__JSValue, arg1: *bindings.VM) ?*bindings.DOMURL; pub extern fn WebCore__DOMURL__href_(arg0: ?*bindings.DOMURL, arg1: [*c]ZigString) void; pub extern fn WebCore__DOMURL__pathname_(arg0: ?*bindings.DOMURL, arg1: [*c]ZigString) void; +pub extern fn WebCore__DOMFormData__append(arg0: ?*bindings.DOMFormData, arg1: [*c]ZigString, arg2: [*c]ZigString) void; +pub extern fn WebCore__DOMFormData__appendBlob(arg0: ?*bindings.DOMFormData, arg1: *bindings.JSGlobalObject, arg2: [*c]ZigString, arg3: ?*anyopaque, arg4: [*c]ZigString) void; +pub extern fn WebCore__DOMFormData__count(arg0: ?*bindings.DOMFormData) usize; +pub extern fn WebCore__DOMFormData__create(arg0: *bindings.JSGlobalObject) JSC__JSValue; +pub extern fn WebCore__DOMFormData__createFromURLQuery(arg0: *bindings.JSGlobalObject, arg1: [*c]ZigString) JSC__JSValue; +pub extern fn WebCore__DOMFormData__fromJS(JSValue0: JSC__JSValue) ?*bindings.DOMFormData; pub extern fn WebCore__FetchHeaders__append(arg0: ?*bindings.FetchHeaders, arg1: [*c]const ZigString, arg2: [*c]const ZigString) void; pub extern fn WebCore__FetchHeaders__cast_(JSValue0: JSC__JSValue, arg1: *bindings.VM) ?*bindings.FetchHeaders; pub extern fn WebCore__FetchHeaders__clone(arg0: ?*bindings.FetchHeaders, arg1: *bindings.JSGlobalObject) JSC__JSValue; diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 5b3605354..53b5b6994 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -35,8 +35,8 @@ public: /* --- bun --- */ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMException; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormData; - // std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormDataIterator; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormData; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMFormDataIterator; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDOMURL; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForURLSearchParams; diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 461a63ac6..089d12043 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -467,8 +467,8 @@ public: // std::unique_ptr<IsoSubspace> m_subspaceForFileList; // std::unique_ptr<IsoSubspace> m_subspaceForFileReader; // std::unique_ptr<IsoSubspace> m_subspaceForFileReaderSync; - // std::unique_ptr<IsoSubspace> m_subspaceForDOMFormData; - // std::unique_ptr<IsoSubspace> m_subspaceForDOMFormDataIterator; + std::unique_ptr<IsoSubspace> m_subspaceForDOMFormData; + std::unique_ptr<IsoSubspace> m_subspaceForDOMFormDataIterator; // std::unique_ptr<IsoSubspace> m_subspaceForDOMTokenList; // std::unique_ptr<IsoSubspace> m_subspaceForDOMTokenListIterator; // std::unique_ptr<IsoSubspace> m_subspaceForDOMURL; diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.cpp b/src/bun.js/bindings/webcore/JSDOMFormData.cpp new file mode 100644 index 000000000..44c28bd00 --- /dev/null +++ b/src/bun.js/bindings/webcore/JSDOMFormData.cpp @@ -0,0 +1,654 @@ +/* + 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 "JSDOMFormData.h" + +#include "ActiveDOMObject.h" +#include "DOMClientIsoSubspaces.h" +#include "DOMIsoSubspaces.h" +#include "IDLTypes.h" +#include "JSDOMBinding.h" +#include "JSDOMConstructor.h" +#include "JSDOMConvertBase.h" +#include "JSDOMConvertBoolean.h" +#include "JSDOMConvertInterface.h" +#include "JSDOMConvertNullable.h" +#include "JSDOMConvertSequences.h" +#include "JSDOMConvertStrings.h" +#include "JSDOMConvertUnion.h" +#include "JSDOMExceptionHandling.h" +#include "JSDOMGlobalObject.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 "ZigGeneratedClasses.h" + +namespace WebCore { +using namespace JSC; + +struct JSBlobWrapperConverter { + static RefPtr<Blob> toWrapped(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value) + { + auto* globalObject = JSC::jsDynamicCast<JSDOMGlobalObject*>(&lexicalGlobalObject); + if (!globalObject) + return nullptr; + + auto* readableStream = JSC::jsDynamicCast<JSBlob*>(value); + if (!readableStream) + return nullptr; + + return Blob::create(value); + } +}; + +template<> struct JSDOMWrapperConverterTraits<Blob> { + using WrapperClass = JSBlobWrapperConverter; + using ToWrappedReturnType = RefPtr<Blob>; + static constexpr bool needsState = true; +}; + +template<> struct Converter<IDLInterface<Blob>> : DefaultConverter<IDLInterface<Blob>> { + static RefPtr<Blob> convert(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue value, JSDOMGlobalObject& globalObject) + { + return JSBlobWrapperConverter::toWrapped(lexicalGlobalObject, value); + } +}; + +// Functions + +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_append); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_delete); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_get); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_getAll); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_has); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_set); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_entries); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_keys); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_values); +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_forEach); + +// Attributes + +static JSC_DECLARE_CUSTOM_GETTER(jsDOMFormDataConstructor); + +class JSDOMFormDataPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSDOMFormDataPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure) + { + JSDOMFormDataPrototype* ptr = new (NotNull, JSC::allocateCell<JSDOMFormDataPrototype>(vm)) JSDOMFormDataPrototype(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(JSDOMFormDataPrototype, 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: + JSDOMFormDataPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure) + : JSC::JSNonFinalObject(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDOMFormDataPrototype, JSDOMFormDataPrototype::Base); + +using JSDOMFormDataDOMConstructor = JSDOMConstructor<JSDOMFormData>; + +template<> EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSDOMFormDataDOMConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* castedThis = jsCast<JSDOMFormDataDOMConstructor*>(callFrame->jsCallee()); + ASSERT(castedThis); + auto* context = castedThis->scriptExecutionContext(); + if (UNLIKELY(!context)) + return throwConstructorScriptExecutionContextUnavailableError(*lexicalGlobalObject, throwScope, "FormData"); + auto object = DOMFormData::create(context); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + static_assert(TypeOrExceptionOrUnderlyingType<decltype(object)>::isRef); + auto jsValue = toJSNewlyCreated<IDLInterface<DOMFormData>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, WTFMove(object)); + if constexpr (IsExceptionOr<decltype(object)>) + RETURN_IF_EXCEPTION(throwScope, {}); + setSubclassStructureIfNeeded<DOMFormData>(lexicalGlobalObject, callFrame, asObject(jsValue)); + RETURN_IF_EXCEPTION(throwScope, {}); + return JSValue::encode(jsValue); +} +JSC_ANNOTATE_HOST_FUNCTION(JSDOMFormDataDOMConstructorConstruct, JSDOMFormDataDOMConstructor::construct); + +template<> const ClassInfo JSDOMFormDataDOMConstructor::s_info = { "FormData"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFormDataDOMConstructor) }; + +template<> JSValue JSDOMFormDataDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject) +{ + UNUSED_PARAM(vm); + return globalObject.functionPrototype(); +} + +template<> void JSDOMFormDataDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject) +{ + putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + JSString* nameString = jsNontrivialString(vm, "FormData"_s); + m_originalName.set(vm, this, nameString); + putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum); + putDirect(vm, vm.propertyNames->prototype, JSDOMFormData::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete); +} + +/* Hash table for prototype */ + +static const HashTableValue JSDOMFormDataPrototypeTableValues[] = { + { "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsDOMFormDataConstructor, 0 } }, + { "append"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_append, 2 } }, + { "delete"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_delete, 1 } }, + { "get"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_get, 1 } }, + { "getAll"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_getAll, 1 } }, + { "has"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_has, 1 } }, + { "set"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_set, 2 } }, + { "entries"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_entries, 0 } }, + { "keys"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_keys, 0 } }, + { "values"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_values, 0 } }, + { "forEach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_forEach, 1 } }, +}; + +const ClassInfo JSDOMFormDataPrototype::s_info = { "FormData"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFormDataPrototype) }; + +void JSDOMFormDataPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSDOMFormData::info(), JSDOMFormDataPrototypeTableValues, *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 JSDOMFormData::s_info = { "FormData"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFormData) }; + +JSDOMFormData::JSDOMFormData(Structure* structure, JSDOMGlobalObject& globalObject, Ref<DOMFormData>&& impl) + : JSDOMWrapper<DOMFormData>(structure, globalObject, WTFMove(impl)) +{ +} + +void JSDOMFormData::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + // static_assert(!std::is_base_of<ActiveDOMObject, DOMFormData>::value, "Interface is not marked as [ActiveDOMObject] even though implementation class subclasses ActiveDOMObject."); +} + +JSObject* JSDOMFormData::createPrototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return JSDOMFormDataPrototype::create(vm, &globalObject, JSDOMFormDataPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype())); +} + +JSObject* JSDOMFormData::prototype(VM& vm, JSDOMGlobalObject& globalObject) +{ + return getDOMPrototype<JSDOMFormData>(vm, globalObject); +} + +JSValue JSDOMFormData::getConstructor(VM& vm, const JSGlobalObject* globalObject) +{ + return getDOMConstructor<JSDOMFormDataDOMConstructor, DOMConstructorID::DOMFormData>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject)); +} + +void JSDOMFormData::destroy(JSC::JSCell* cell) +{ + JSDOMFormData* thisObject = static_cast<JSDOMFormData*>(cell); + thisObject->JSDOMFormData::~JSDOMFormData(); +} + +JSC_DEFINE_CUSTOM_GETTER(jsDOMFormDataConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + auto* prototype = jsDynamicCast<JSDOMFormDataPrototype*>(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(lexicalGlobalObject, throwScope); + return JSValue::encode(JSDOMFormData::getConstructor(JSC::getVM(lexicalGlobalObject), prototype->globalObject())); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_append1Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + auto value = convert<IDLUSVString>(*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)); }))); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_append2Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + + EnsureStillAliveScope argument2 = callFrame->argument(2); + auto filename = argument2.value().isUndefined() ? String() : convert<IDLUSVString>(*lexicalGlobalObject, argument2.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + + RefPtr<Blob> blobValue = nullptr; + if (argument1.value().inherits<JSBlob>()) { + blobValue = Blob::create(argument1.value()); + } + + if (!blobValue) { + throwTypeError(lexicalGlobalObject, throwScope, "Expected argument to be a Blob."_s); + } + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.append(WTFMove(name), WTFMove(blobValue), WTFMove(filename)); }))); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_appendOverloadDispatcher(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + size_t argsCount = std::min<size_t>(3, callFrame->argumentCount()); + if (argsCount == 2) { + JSValue distinguishingArg = callFrame->uncheckedArgument(1); + if (distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JSBlob>()) + RELEASE_AND_RETURN(throwScope, (jsDOMFormDataPrototypeFunction_append2Body(lexicalGlobalObject, callFrame, castedThis))); + RELEASE_AND_RETURN(throwScope, (jsDOMFormDataPrototypeFunction_append1Body(lexicalGlobalObject, callFrame, castedThis))); + } + if (argsCount == 3) { + RELEASE_AND_RETURN(throwScope, (jsDOMFormDataPrototypeFunction_append2Body(lexicalGlobalObject, callFrame, castedThis))); + } + return argsCount < 2 ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_append, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_appendOverloadDispatcher>(*lexicalGlobalObject, *callFrame, "append"); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_deleteBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::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<IDLUSVString>(*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(jsDOMFormDataPrototypeFunction_delete, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_deleteBody>(*lexicalGlobalObject, *callFrame, "delete"); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_getBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::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<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLNullable<IDLUnion<IDLUSVString, IDLInterface<Blob>>>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, impl.get(WTFMove(name))))); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_get, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_getBody>(*lexicalGlobalObject, *callFrame, "get"); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_getAllBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::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<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + auto entries = impl.getAll(WTFMove(name)); + JSC::JSArray* result = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); + for (auto entry : entries) { + if (auto string = std::get_if<String>(&entry)) { + result->push(lexicalGlobalObject, jsString(vm, *string)); + } else { + auto blob = std::get<RefPtr<Blob>>(entry); + result->push(lexicalGlobalObject, toJS(lexicalGlobalObject, castedThis->globalObject(), blob.get())); + } + } + + RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_getAll, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_getAllBody>(*lexicalGlobalObject, *callFrame, "getAll"); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_hasBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::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<IDLUSVString>(*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(jsDOMFormDataPrototypeFunction_has, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_hasBody>(*lexicalGlobalObject, *callFrame, "has"); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_set1Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + auto value = convert<IDLUSVString>(*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)); }))); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_set2Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + auto& impl = castedThis->wrapped(); + EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0); + auto name = convert<IDLUSVString>(*lexicalGlobalObject, argument0.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + EnsureStillAliveScope argument1 = callFrame->uncheckedArgument(1); + + EnsureStillAliveScope argument2 = callFrame->argument(2); + auto filename = argument2.value().isUndefined() ? String() : convert<IDLUSVString>(*lexicalGlobalObject, argument2.value()); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + + RefPtr<Blob> blobValue = nullptr; + if (argument1.value().inherits<JSBlob>()) { + blobValue = Blob::create(argument1.value()); + } + + if (!blobValue) { + throwTypeError(lexicalGlobalObject, throwScope, "Expected argument to be a Blob."_s); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + } + + RELEASE_AND_RETURN(throwScope, JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.set(WTFMove(name), WTFMove(blobValue), WTFMove(filename)); }))); +} + +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_setOverloadDispatcher(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + UNUSED_PARAM(throwScope); + UNUSED_PARAM(callFrame); + size_t argsCount = std::min<size_t>(3, callFrame->argumentCount()); + if (argsCount == 2) { + JSValue distinguishingArg = callFrame->uncheckedArgument(1); + if (distinguishingArg.isObject() && asObject(distinguishingArg)->inherits<JSBlob>()) + RELEASE_AND_RETURN(throwScope, (jsDOMFormDataPrototypeFunction_set2Body(lexicalGlobalObject, callFrame, castedThis))); + RELEASE_AND_RETURN(throwScope, (jsDOMFormDataPrototypeFunction_set1Body(lexicalGlobalObject, callFrame, castedThis))); + } + if (argsCount == 3) { + RELEASE_AND_RETURN(throwScope, (jsDOMFormDataPrototypeFunction_set2Body(lexicalGlobalObject, callFrame, castedThis))); + } + return argsCount < 2 ? throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)) : throwVMTypeError(lexicalGlobalObject, throwScope); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_set, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_setOverloadDispatcher>(*lexicalGlobalObject, *callFrame, "set"); +} + +struct DOMFormDataIteratorTraits { + static constexpr JSDOMIteratorType type = JSDOMIteratorType::Map; + using KeyType = IDLUSVString; + using ValueType = IDLUnion<IDLUSVString, IDLInterface<Blob>>; +}; + +using DOMFormDataIteratorBase = JSDOMIteratorBase<JSDOMFormData, DOMFormDataIteratorTraits>; +class DOMFormDataIterator final : public DOMFormDataIteratorBase { +public: + using Base = DOMFormDataIteratorBase; + 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<DOMFormDataIterator, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForDOMFormDataIterator.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForDOMFormDataIterator = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForDOMFormDataIterator.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForDOMFormDataIterator = std::forward<decltype(space)>(space); }); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static DOMFormDataIterator* create(JSC::VM& vm, JSC::Structure* structure, JSDOMFormData& iteratedObject, IterationKind kind) + { + auto* instance = new (NotNull, JSC::allocateCell<DOMFormDataIterator>(vm)) DOMFormDataIterator(structure, iteratedObject, kind); + instance->finishCreation(vm); + return instance; + } + +private: + DOMFormDataIterator(JSC::Structure* structure, JSDOMFormData& iteratedObject, IterationKind kind) + : Base(structure, iteratedObject, kind) + { + } +}; + +using DOMFormDataIteratorPrototype = JSDOMIteratorPrototype<JSDOMFormData, DOMFormDataIteratorTraits>; +JSC_ANNOTATE_HOST_FUNCTION(DOMFormDataIteratorPrototypeNext, DOMFormDataIteratorPrototype::next); + +template<> +const JSC::ClassInfo DOMFormDataIteratorBase::s_info = { "FormDataBase Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMFormDataIteratorBase) }; +const JSC::ClassInfo DOMFormDataIterator::s_info = { "FormData Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMFormDataIterator) }; + +template<> +const JSC::ClassInfo DOMFormDataIteratorPrototype::s_info = { "FormData Iterator"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMFormDataIteratorPrototype) }; + +static inline EncodedJSValue jsDOMFormDataPrototypeFunction_entriesCaller(JSGlobalObject*, CallFrame*, JSDOMFormData* thisObject) +{ + return JSValue::encode(iteratorCreate<DOMFormDataIterator>(*thisObject, IterationKind::Entries)); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_entries, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_entriesCaller>(*lexicalGlobalObject, *callFrame, "entries"); +} + +static inline EncodedJSValue jsDOMFormDataPrototypeFunction_keysCaller(JSGlobalObject*, CallFrame*, JSDOMFormData* thisObject) +{ + return JSValue::encode(iteratorCreate<DOMFormDataIterator>(*thisObject, IterationKind::Keys)); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_keys, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_keysCaller>(*lexicalGlobalObject, *callFrame, "keys"); +} + +static inline EncodedJSValue jsDOMFormDataPrototypeFunction_valuesCaller(JSGlobalObject*, CallFrame*, JSDOMFormData* thisObject) +{ + return JSValue::encode(iteratorCreate<DOMFormDataIterator>(*thisObject, IterationKind::Values)); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_values, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_valuesCaller>(*lexicalGlobalObject, *callFrame, "values"); +} + +static inline EncodedJSValue jsDOMFormDataPrototypeFunction_forEachCaller(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame, JSDOMFormData* thisObject) +{ + return JSValue::encode(iteratorForEach<DOMFormDataIterator>(*lexicalGlobalObject, *callFrame, *thisObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_forEach, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_forEachCaller>(*lexicalGlobalObject, *callFrame, "forEach"); +} + +JSC::GCClient::IsoSubspace* JSDOMFormData::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSDOMFormData, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForDOMFormData.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForDOMFormData = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForDOMFormData.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForDOMFormData = std::forward<decltype(space)>(space); }); +} + +void JSDOMFormData::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast<JSDOMFormData*>(cell); + analyzer.setWrappedObjectForCell(cell, &thisObject->wrapped()); + if (thisObject->scriptExecutionContext()) + analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string()); + Base::analyzeHeap(cell, analyzer); +} + +bool JSDOMFormDataOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, AbstractSlotVisitor& visitor, const char** reason) +{ + UNUSED_PARAM(handle); + UNUSED_PARAM(visitor); + UNUSED_PARAM(reason); + return false; +} + +void JSDOMFormDataOwner::finalize(JSC::Handle<JSC::Unknown> handle, void* context) +{ + auto* jsDOMFormData = static_cast<JSDOMFormData*>(handle.slot()->asCell()); + auto& world = *static_cast<DOMWrapperWorld*>(context); + uncacheWrapper(world, &jsDOMFormData->wrapped(), jsDOMFormData); +} + +#if ENABLE(BINDING_INTEGRITY) +#if PLATFORM(WIN) +#pragma warning(disable : 4483) +extern "C" { +extern void (*const __identifier("??_7DOMFormData@WebCore@@6B@")[])(); +} +#else +extern "C" { +extern void* _ZTVN7WebCore11DOMFormDataE[]; +} +#endif +#endif + +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<DOMFormData>&& impl) +{ + + if constexpr (std::is_polymorphic_v<DOMFormData>) { +#if ENABLE(BINDING_INTEGRITY) + const void* actualVTablePointer = getVTablePointer(impl.ptr()); +#if PLATFORM(WIN) + void* expectedVTablePointer = __identifier("??_7DOMFormData@WebCore@@6B@"); +#else + void* expectedVTablePointer = &_ZTVN7WebCore11DOMFormDataE[2]; +#endif + + // If you hit this assertion you either have a use after free bug, or + // DOMFormData has subclasses. If DOMFormData has subclasses that get passed + // to toJS() we currently require DOMFormData you to opt out of binding hardening + // by adding the SkipVTableValidation attribute to the interface IDL definition + RELEASE_ASSERT(actualVTablePointer == expectedVTablePointer); +#endif + } + return createWrapper<DOMFormData>(globalObject, WTFMove(impl)); +} + +JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, DOMFormData& impl) +{ + return wrap(lexicalGlobalObject, globalObject, impl); +} + +DOMFormData* JSDOMFormData::toWrapped(JSC::VM&, JSC::JSValue value) +{ + if (auto* wrapper = jsDynamicCast<JSDOMFormData*>(value)) + return &wrapper->wrapped(); + return nullptr; +} +} diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.dep b/src/bun.js/bindings/webcore/JSDOMFormData.dep new file mode 100644 index 000000000..5757aa3ae --- /dev/null +++ b/src/bun.js/bindings/webcore/JSDOMFormData.dep @@ -0,0 +1 @@ +JSDOMFormData.h : diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.h b/src/bun.js/bindings/webcore/JSDOMFormData.h new file mode 100644 index 000000000..085c42c5d --- /dev/null +++ b/src/bun.js/bindings/webcore/JSDOMFormData.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 "DOMFormData.h" +#include "JSDOMWrapper.h" +#include <wtf/NeverDestroyed.h> + +namespace WebCore { + +class JSDOMFormData : public JSDOMWrapper<DOMFormData> { +public: + using Base = JSDOMWrapper<DOMFormData>; + static JSDOMFormData* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, Ref<DOMFormData>&& impl) + { + JSDOMFormData* ptr = new (NotNull, JSC::allocateCell<JSDOMFormData>(globalObject->vm())) JSDOMFormData(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 DOMFormData* 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: + JSDOMFormData(JSC::Structure*, JSDOMGlobalObject&, Ref<DOMFormData>&&); + + void finishCreation(JSC::VM&); +}; + +class JSDOMFormDataOwner 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&, DOMFormData*) +{ + static NeverDestroyed<JSDOMFormDataOwner> owner; + return &owner.get(); +} + +inline void* wrapperKey(DOMFormData* wrappableObject) +{ + return wrappableObject; +} + +JSC::JSValue toJS(JSC::JSGlobalObject*, JSDOMGlobalObject*, DOMFormData&); +inline JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, DOMFormData* impl) { return impl ? toJS(lexicalGlobalObject, globalObject, *impl) : JSC::jsNull(); } +JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject*, JSDOMGlobalObject*, Ref<DOMFormData>&&); +inline JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, RefPtr<DOMFormData>&& impl) { return impl ? toJSNewlyCreated(lexicalGlobalObject, globalObject, impl.releaseNonNull()) : JSC::jsNull(); } + +template<> struct JSDOMWrapperConverterTraits<DOMFormData> { + using WrapperClass = JSDOMFormData; + using ToWrappedReturnType = DOMFormData*; +}; + +} // namespace WebCore diff --git a/src/bun.js/node/node_fs.zig b/src/bun.js/node/node_fs.zig index 5b7d5a3a9..f09e9b4fd 100644 --- a/src/bun.js/node/node_fs.zig +++ b/src/bun.js/node/node_fs.zig @@ -1453,6 +1453,9 @@ const Arguments = struct { path: PathOrFileDescriptor, encoding: Encoding = Encoding.utf8, + offset: JSC.WebCore.Blob.SizeType = 0, + max_size: ?JSC.WebCore.Blob.SizeType = null, + flag: FileSystemFlags = FileSystemFlags.r, pub fn fromJS(ctx: JSC.C.JSContextRef, arguments: *ArgumentsSlice, exception: JSC.C.ExceptionRef) ?ReadFile { @@ -3308,8 +3311,28 @@ pub const NodeFS = struct { .result => |stat_| stat_, }; + // Only used in DOMFormData + if (args.offset > 0) { + std.os.lseek_SET(fd, args.offset) catch {}; + } + // For certain files, the size might be 0 but the file might still have contents. - const size = @intCast(u64, @max(stat_.size, 0)); + const size = @intCast( + u64, + @max( + @min( + stat_.size, + @intCast( + @TypeOf(stat_.size), + // Only used in DOMFormData + args.max_size orelse std.math.maxInt( + JSC.WebCore.Blob.SizeType, + ), + ), + ), + 0, + ), + ); var buf = std.ArrayList(u8).init(bun.default_allocator); buf.ensureTotalCapacityPrecise(size + 16) catch unreachable; diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index de92aa0f4..a1b848397 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -97,6 +97,146 @@ pub const Blob = struct { pub const SizeType = u52; pub const max_size = std.math.maxInt(SizeType); + pub fn getFormDataEncoding(this: *Blob) ?*bun.FormData.AsyncFormData { + var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; + defer content_type_slice.deinit(); + const encoding = bun.FormData.Encoding.get(content_type_slice.slice()) orelse return null; + return bun.FormData.AsyncFormData.init(this.allocator orelse bun.default_allocator, encoding) catch unreachable; + } + + const FormDataContext = struct { + allocator: std.mem.Allocator, + joiner: StringJoiner, + boundary: []const u8, + failed: bool = false, + globalThis: *JSC.JSGlobalObject, + + pub fn onEntry(this: *FormDataContext, name: ZigString, entry: JSC.DOMFormData.FormDataEntry) void { + if (this.failed) return; + var globalThis = this.globalThis; + + const allocator = this.allocator; + const joiner = &this.joiner; + const boundary = this.boundary; + + joiner.append("--", 0, null); + joiner.append(boundary, 0, null); + joiner.append("\r\n", 0, null); + + joiner.append("Content-Disposition: form-data; name=\"", 0, null); + const name_slice = name.toSlice(allocator); + joiner.append(name_slice.slice(), 0, name_slice.allocator.get()); + name_slice.deinit(); + + switch (entry) { + .string => |value| { + joiner.append("\"\r\n\r\n", 0, null); + const value_slice = value.toSlice(allocator); + joiner.append(value_slice.slice(), 0, value_slice.allocator.get()); + }, + .file => |value| { + joiner.append("\"; filename=\"", 0, null); + const filename_slice = value.filename.toSlice(allocator); + joiner.append(filename_slice.slice(), 0, filename_slice.allocator.get()); + filename_slice.deinit(); + joiner.append("\"\r\n", 0, null); + + const blob = value.blob; + const content_type = if (blob.content_type.len > 0) blob.content_type else "application/octet-stream"; + joiner.append("Content-Type: ", 0, null); + joiner.append(content_type, 0, null); + joiner.append("\r\n\r\n", 0, null); + + if (blob.store) |store| { + blob.resolveSize(); + + switch (store.data) { + .file => |file| { + + // TODO: make this async + lazy + const res = JSC.Node.NodeFS.readFile( + globalThis.bunVM().nodeFS(), + .{ + .encoding = .buffer, + .path = file.pathlike, + .offset = blob.offset, + .max_size = blob.size, + }, + .sync, + ); + + switch (res) { + .err => |err| { + globalThis.throwValue(err.toJSC(globalThis)); + this.failed = true; + }, + .result => |result| { + joiner.append(result.slice(), 0, result.buffer.allocator); + }, + } + }, + .bytes => |_| { + joiner.append(blob.sharedView(), 0, null); + }, + } + } + }, + } + + joiner.append("\r\n", 0, null); + } + }; + + pub fn getContentType( + this: *Blob, + ) ?ZigString.Slice { + if (this.content_type.len > 0) + return ZigString.Slice.fromUTF8NeverFree(this.content_type); + + return null; + } + + pub fn fromDOMFormData( + globalThis: *JSC.JSGlobalObject, + allocator: std.mem.Allocator, + form_data: *JSC.DOMFormData, + ) Blob { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + var stack_allocator = std.heap.stackFallback(1024, arena.allocator()); + var stack_mem_all = stack_allocator.get(); + + var hex_buf: [70]u8 = undefined; + const boundary = brk: { + var random = globalThis.bunVM().rareData().nextUUID(); + var formatter = std.fmt.fmtSliceHexLower(&random); + break :brk std.fmt.bufPrint(&hex_buf, "-WebkitFormBoundary{any}", .{formatter}) catch unreachable; + }; + + var context = FormDataContext{ + .allocator = allocator, + .joiner = StringJoiner{ .use_pool = false, .node_allocator = stack_mem_all }, + .boundary = boundary, + .globalThis = globalThis, + }; + + form_data.forEach(FormDataContext, &context, FormDataContext.onEntry); + if (context.failed) { + return Blob.initEmpty(globalThis); + } + + context.joiner.append("--", 0, null); + context.joiner.append(boundary, 0, null); + context.joiner.append("--\r\n", 0, null); + + var store = Blob.Store.init(context.joiner.done(allocator) catch unreachable, allocator) catch unreachable; + var blob = Blob.initWithStore(store, globalThis); + blob.content_type = std.fmt.allocPrint(allocator, "multipart/form-data; boundary=\"{s}\"", .{boundary}) catch unreachable; + blob.content_type_allocated = true; + + return blob; + } + pub fn contentType(this: *const Blob) string { return this.content_type; } @@ -105,6 +245,29 @@ pub const Blob = struct { return this.store == null; } + export fn Blob__dupeFromJS(value: JSC.JSValue) ?*Blob { + var this = Blob.fromJS(value) orelse return null; + return Blob__dupe(this); + } + + export fn Blob__dupe(ptr: *anyopaque) *Blob { + var this = bun.cast(*Blob, ptr); + var new = bun.default_allocator.create(Blob) catch unreachable; + new.* = this.dupe(); + new.allocator = bun.default_allocator; + return new; + } + + export fn Blob__destroy(this: *Blob) void { + this.finalize(); + } + + comptime { + _ = Blob__dupeFromJS; + _ = Blob__destroy; + _ = Blob__dupe; + } + pub fn writeFormatForSize(size: usize, writer: anytype, comptime enable_ansi_colors: bool) !void { try writer.writeAll(comptime Output.prettyFmt("<r>Blob<r>", enable_ansi_colors)); try writer.print( @@ -2107,6 +2270,14 @@ pub const Blob = struct { return promisified(this.toArrayBuffer(globalThis, .clone), globalThis); } + pub fn getFormData( + this: *Blob, + globalThis: *JSC.JSGlobalObject, + _: *JSC.CallFrame, + ) callconv(.C) JSValue { + return promisified(this.toFormData(globalThis, .temporary), globalThis); + } + pub fn getWriter( this: *Blob, globalThis: *JSC.JSGlobalObject, @@ -2389,21 +2560,20 @@ pub const Blob = struct { }; if (args.len > 1) { - var options = args[0]; - if (options.isCell()) { + const options = args[1]; + if (options.isObject()) { // type, the ASCII-encoded string in lower case // representing the media type of the Blob. // Normative conditions for this member are provided // in the § 3.1 Constructors. if (options.get(globalThis, "type")) |content_type| { if (content_type.isString()) { - var content_type_str = content_type.getZigString(globalThis); - if (!content_type_str.is16Bit()) { - var slice = content_type_str.trimmedSlice(); - var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; - blob.content_type = strings.copyLowercase(slice, content_type_buf); - blob.content_type_allocated = true; - } + var content_type_str = content_type.toSlice(globalThis, bun.default_allocator); + defer content_type_str.deinit(); + var slice = content_type_str.slice(); + var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable; + blob.content_type = strings.copyLowercase(slice, content_type_buf); + blob.content_type_allocated = true; } } } @@ -2764,6 +2934,16 @@ pub const Blob = struct { } } + pub fn toFormDataWithBytes(this: *Blob, global: *JSGlobalObject, buf: []u8, comptime _: Lifetime) JSValue { + var encoder = this.getFormDataEncoding() orelse return { + return ZigString.init("Invalid encoding").toErrorInstance(global); + }; + defer encoder.deinit(); + + return bun.FormData.toJS(global, buf, encoder.encoding) catch |err| + global.createErrorInstance("FormData encoding failed: {s}", .{@errorName(err)}); + } + pub fn toArrayBufferWithBytes(this: *Blob, global: *JSGlobalObject, buf: []u8, comptime lifetime: Lifetime) JSValue { switch (comptime lifetime) { .clone => { @@ -2810,6 +2990,19 @@ pub const Blob = struct { return toArrayBufferWithBytes(this, global, bun.constStrToU8(view_), lifetime); } + pub fn toFormData(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { + if (this.needsToReadFile()) { + return this.doReadFile(toFormDataWithBytes, global); + } + + var view_ = this.sharedView(); + + if (view_.len == 0) + return JSC.DOMFormData.create(global); + + return toFormDataWithBytes(this, global, bun.constStrToU8(view_), lifetime); + } + pub inline fn get( global: *JSGlobalObject, arg: JSValue, diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index 05616c7df..b96686265 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -201,11 +201,10 @@ pub const Body = struct { /// conditionally runs when requesting data /// used in HTTP server to ignore request bodies unless asked for it onStartBuffering: ?*const fn (ctx: *anyopaque) void = null, - onStartStreaming: ?*const fn (ctx: *anyopaque) JSC.WebCore.DrainResult = null, deinit: bool = false, - action: Action = Action.none, + action: Action = Action{ .none = void{} }, pub fn toAnyBlob(this: *PendingValue) ?AnyBlob { if (this.promise != null) @@ -253,6 +252,9 @@ pub const Body = struct { return value.promise.?; }, + // TODO: + .getFormData => {}, + .none => {}, } } @@ -270,12 +272,13 @@ pub const Body = struct { } } - pub const Action = enum { - none, - getText, - getJSON, - getArrayBuffer, - getBlob, + pub const Action = union(enum) { + none: void, + getText: void, + getJSON: void, + getArrayBuffer: void, + getBlob: void, + getFormData: ?*bun.FormData.AsyncFormData, }; }; @@ -430,7 +433,10 @@ pub const Body = struct { } } - pub fn fromJS(globalThis: *JSGlobalObject, value: JSValue) ?Value { + pub fn fromJS( + globalThis: *JSGlobalObject, + value: JSValue, + ) ?Value { value.ensureStillAlive(); if (value.isEmptyOrUndefinedOrNull()) { @@ -546,6 +552,12 @@ pub const Body = struct { } } + if (value.as(JSC.DOMFormData)) |form_data| { + return Body.Value{ + .Blob = Blob.fromDOMFormData(globalThis, globalThis.allocator(), form_data), + }; + } + if (js_type == .DOMWrapper) { if (value.as(Blob)) |blob| { return Body.Value{ @@ -653,6 +665,16 @@ pub const Body = struct { var blob = new.useAsAnyBlob(); promise.resolve(global, blob.toArrayBuffer(global, .transfer)); }, + .getFormData => inner: { + var blob = new.useAsAnyBlob(); + defer blob.detach(); + var async_form_data = locked.action.getFormData orelse { + promise.reject(global, ZigString.init("Internal error: task for FormData must not be null").toErrorInstance(global)); + break :inner; + }; + defer async_form_data.deinit(); + async_form_data.toJS(global, blob.slice(), promise); + }, else => { var ptr = bun.default_allocator.create(Blob) catch unreachable; ptr.* = new.use(); @@ -931,7 +953,7 @@ pub fn BodyMixin(comptime Type: type) type { } if (value.* == .Locked) { - return value.Locked.setPromise(globalThis, .getText); + return value.Locked.setPromise(globalThis, .{ .getText = void{} }); } var blob = value.useAsAnyBlob(); @@ -970,7 +992,7 @@ pub fn BodyMixin(comptime Type: type) type { } if (value.* == .Locked) { - return value.Locked.setPromise(globalObject, .getJSON); + return value.Locked.setPromise(globalObject, .{ .getJSON = void{} }); } var blob = value.useAsAnyBlob(); @@ -996,13 +1018,59 @@ pub fn BodyMixin(comptime Type: type) type { } if (value.* == .Locked) { - return value.Locked.setPromise(globalObject, .getArrayBuffer); + return value.Locked.setPromise(globalObject, .{ .getArrayBuffer = void{} }); } var blob: AnyBlob = value.useAsAnyBlob(); return JSC.JSPromise.wrap(globalObject, blob.toArrayBuffer(globalObject, .transfer)); } + pub fn getFormData( + this: *Type, + globalObject: *JSC.JSGlobalObject, + _: *JSC.CallFrame, + ) callconv(.C) JSC.JSValue { + var value: *Body.Value = this.getBodyValue(); + + if (value.* == .Used) { + return handleBodyAlreadyUsed(globalObject); + } + + var encoder = this.getFormDataEncoding() orelse { + globalObject.throw("Invalid MIME type", .{}); + return .zero; + }; + + if (value.* == .Locked) { + return value.Locked.setPromise(globalObject, .{ .getFormData = encoder }); + } + + var blob: AnyBlob = value.useAsAnyBlob(); + defer blob.detach(); + defer encoder.deinit(); + + const js_value = bun.FormData.toJS( + globalObject, + blob.slice(), + encoder.encoding, + ) catch |err| { + return JSC.JSPromise.rejectedPromiseValue( + globalObject, + globalObject.createErrorInstance( + "FormData parse error {s}", + .{ + @errorName(err), + }, + ), + ); + }; + + return JSC.JSPromise.wrap( + globalObject, + js_value, + ); + } + pub fn getBlob( this: *Type, globalObject: *JSC.JSGlobalObject, @@ -1015,7 +1083,7 @@ pub fn BodyMixin(comptime Type: type) type { } if (value.* == .Locked) { - return value.Locked.setPromise(globalObject, .getBlob); + return value.Locked.setPromise(globalObject, .{ .getBlob = void{} }); } var blob = value.use(); diff --git a/src/bun.js/webcore/request.zig b/src/bun.js/webcore/request.zig index ff6816351..1248767c3 100644 --- a/src/bun.js/webcore/request.zig +++ b/src/bun.js/webcore/request.zig @@ -75,6 +75,37 @@ pub const Request = struct { pub const getJSON = RequestMixin.getJSON; pub const getArrayBuffer = RequestMixin.getArrayBuffer; pub const getBlob = RequestMixin.getBlob; + pub const getFormData = RequestMixin.getFormData; + + pub fn getContentType( + this: *Request, + ) ?ZigString.Slice { + if (this.uws_request) |req| { + if (req.header("content-type")) |value| { + return ZigString.Slice.fromUTF8NeverFree(value); + } + } + + if (this.headers) |headers| { + if (headers.fastGet(.ContentType)) |value| { + return value.toSlice(bun.default_allocator); + } + } + + if (this.body == .Blob) { + if (this.body.Blob.content_type.len > 0) + return ZigString.Slice.fromUTF8NeverFree(this.body.Blob.content_type); + } + + return null; + } + + pub fn getFormDataEncoding(this: *Request) ?*bun.FormData.AsyncFormData { + var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; + defer content_type_slice.deinit(); + const encoding = bun.FormData.Encoding.get(content_type_slice.slice()) orelse return null; + return bun.FormData.AsyncFormData.init(bun.default_allocator, encoding) catch unreachable; + } pub fn estimatedSize(this: *Request) callconv(.C) usize { return this.reported_estimated_size orelse brk: { @@ -341,19 +372,23 @@ pub const Request = struct { }).slice(); request.url_was_allocated = request.url.len > 0; } else { + if (Body.Init.init(getAllocator(globalThis), globalThis, arguments[0], url_or_object_type) catch null) |req_init| { + request.headers = req_init.headers; + request.method = req_init.method; + } + if (urlOrObject.fastGet(globalThis, .body)) |body_| { if (Body.Value.fromJS(globalThis, body_)) |body| { request.body = body; } else { + if (request.headers) |head| { + head.deref(); + } + return null; } } - if (Body.Init.init(getAllocator(globalThis), globalThis, arguments[0], url_or_object_type) catch null) |req_init| { - request.headers = req_init.headers; - request.method = req_init.method; - } - if (urlOrObject.fastGet(globalThis, .url)) |url| { request.url = (url.toSlice(globalThis, bun.default_allocator).cloneIfNeeded(bun.default_allocator) catch { return null; @@ -363,19 +398,23 @@ pub const Request = struct { } }, else => { + if (Body.Init.init(getAllocator(globalThis), globalThis, arguments[1], arguments[1].jsType()) catch null) |req_init| { + request.headers = req_init.headers; + request.method = req_init.method; + } + if (arguments[1].fastGet(globalThis, .body)) |body_| { if (Body.Value.fromJS(globalThis, body_)) |body| { request.body = body; } else { + if (request.headers) |head| { + head.deref(); + } + return null; } } - if (Body.Init.init(getAllocator(globalThis), globalThis, arguments[1], arguments[1].jsType()) catch null) |req_init| { - request.headers = req_init.headers; - request.method = req_init.method; - } - request.url = (arguments[0].toSlice(globalThis, bun.default_allocator).cloneIfNeeded(bun.default_allocator) catch { return null; }).slice(); diff --git a/src/bun.js/webcore/response.classes.ts b/src/bun.js/webcore/response.classes.ts index 3e8c84c82..7ebe4774b 100644 --- a/src/bun.js/webcore/response.classes.ts +++ b/src/bun.js/webcore/response.classes.ts @@ -14,6 +14,7 @@ export default [ json: { fn: "getJSON" }, body: { getter: "getBody", cache: true }, arrayBuffer: { fn: "getArrayBuffer" }, + formData: { fn: "getFormData" }, blob: { fn: "getBlob" }, clone: { fn: "doClone", length: 1 }, cache: { @@ -86,6 +87,7 @@ export default [ arrayBuffer: { fn: "getArrayBuffer" }, blob: { fn: "getBlob" }, clone: { fn: "doClone", length: 1 }, + formData: { fn: "getFormData" }, type: { getter: "getResponseType", @@ -125,6 +127,7 @@ export default [ arrayBuffer: { fn: "getArrayBuffer" }, slice: { fn: "getSlice", length: 2 }, stream: { fn: "getStream", length: 1 }, + formData: { fn: "getFormData" }, type: { getter: "getType", diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 22f9a4d15..53e1f3fb6 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -71,6 +71,14 @@ pub const Response = struct { pub const getJSON = ResponseMixin.getJSON; pub const getArrayBuffer = ResponseMixin.getArrayBuffer; pub const getBlob = ResponseMixin.getBlob; + pub const getFormData = ResponseMixin.getFormData; + + pub fn getFormDataEncoding(this: *Response) ?*bun.FormData.AsyncFormData { + var content_type_slice: ZigString.Slice = this.getContentType() orelse return null; + defer content_type_slice.deinit(); + const encoding = bun.FormData.Encoding.get(content_type_slice.slice()) orelse return null; + return bun.FormData.AsyncFormData.init(this.allocator, encoding) catch unreachable; + } pub fn estimatedSize(this: *Response) callconv(.C) usize { return this.reported_estimated_size orelse brk: { @@ -319,6 +327,23 @@ pub const Response = struct { } } + pub fn getContentType( + this: *Response, + ) ?ZigString.Slice { + if (this.body.init.headers) |headers| { + if (headers.fastGet(.ContentType)) |value| { + return value.toSlice(bun.default_allocator); + } + } + + if (this.body.value == .Blob) { + if (this.body.value.Blob.content_type.len > 0) + return ZigString.Slice.fromUTF8NeverFree(this.body.value.Blob.content_type); + } + + return null; + } + pub fn constructJSON( globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, diff --git a/src/bun.zig b/src/bun.zig index c70e7655e..7659dd3fb 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -774,6 +774,7 @@ pub fn zero(comptime Type: type) Type { } pub const c_ares = @import("./deps/c_ares.zig"); pub const URL = @import("./url.zig").URL; +pub const FormData = @import("./url.zig").FormData; var needs_proc_self_workaround: bool = false; @@ -944,3 +945,5 @@ pub fn cstring(input: []const u8) [:0]const u8 { } return @ptrCast([*:0]const u8, input.ptr)[0..input.len :0]; } + +pub const Semver = @import("./install/semver.zig"); diff --git a/src/io/io_darwin.zig b/src/io/io_darwin.zig index 06c338a28..fd991bbf5 100644 --- a/src/io/io_darwin.zig +++ b/src/io/io_darwin.zig @@ -259,7 +259,7 @@ const fd_t = os.fd_t; const mem = std.mem; const assert = std.debug.assert; const c = std.c; -const bun = @import("bun"); +const bun = @import("root").bun; pub const darwin = struct { pub usingnamespace os.darwin; pub extern "c" fn @"recvfrom$NOCANCEL"(sockfd: c.fd_t, noalias buf: *anyopaque, len: usize, flags: u32, noalias src_addr: ?*c.sockaddr, noalias addrlen: ?*c.socklen_t) isize; @@ -508,6 +508,8 @@ pub const Waker = struct { const zeroed = std.mem.zeroes([16]Kevent64); pub fn wake(this: *Waker) !void { + bun.JSC.markBinding(@src()); + if (io_darwin_schedule_wakeup(this.machport)) { this.has_pending_wake = false; return; @@ -516,6 +518,7 @@ pub const Waker = struct { } pub fn wait(this: Waker) !usize { + bun.JSC.markBinding(@src()); var events = zeroed; const count = std.os.system.kevent64( @@ -551,6 +554,7 @@ pub const Waker = struct { } pub fn initWithFileDescriptor(allocator: std.mem.Allocator, kq: i32) !Waker { + bun.JSC.markBinding(@src()); assert(kq > -1); var machport_buf = try allocator.alloc(u8, 1024); const machport = io_darwin_create_machport( @@ -573,6 +577,7 @@ pub const UserFilterWaker = struct { ident: u64 = undefined, pub fn wake(this: UserFilterWaker) !void { + bun.JSC.markBinding(@src()); var events = zeroed; events[0].ident = this.ident; events[0].filter = c.EVFILT_USER; diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 079817362..9d36d0c78 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -178,6 +178,56 @@ pub inline fn indexOf(self: string, str: string) ?usize { return @ptrToInt(start) - @ptrToInt(self_ptr); } +pub fn split(self: string, delimiter: string) SplitIterator { + return SplitIterator{ + .buffer = self, + .index = 0, + .delimiter = delimiter, + }; +} + +pub const SplitIterator = struct { + buffer: []const u8, + index: ?usize, + delimiter: []const u8, + + const Self = @This(); + + /// Returns a slice of the first field. This never fails. + /// Call this only to get the first field and then use `next` to get all subsequent fields. + pub fn first(self: *Self) []const u8 { + std.debug.assert(self.index.? == 0); + return self.next().?; + } + + /// Returns a slice of the next field, or null if splitting is complete. + pub fn next(self: *Self) ?[]const u8 { + const start = self.index orelse return null; + const end = if (indexOf(self.buffer[start..], self.delimiter)) |delim_start| blk: { + const del = delim_start + start; + self.index = del + self.delimiter.len; + break :blk delim_start + start; + } else blk: { + self.index = null; + break :blk self.buffer.len; + }; + + return self.buffer[start..end]; + } + + /// Returns a slice of the remaining bytes. Does not affect iterator state. + pub fn rest(self: Self) []const u8 { + const end = self.buffer.len; + const start = self.index orelse end; + return self.buffer[start..end]; + } + + /// Resets the iterator to the initial slice. + pub fn reset(self: *Self) void { + self.index = 0; + } +}; + // -- // This is faster when the string is found, by about 2x for a 8 MB file. // It is slower when the string is NOT found diff --git a/src/url.zig b/src/url.zig index 64de553f9..eec4b5769 100644 --- a/src/url.zig +++ b/src/url.zig @@ -821,6 +821,309 @@ pub const PercentEncoding = struct { } }; +pub const FormData = struct { + fields: Map, + buffer: []const u8, + + pub const Map = std.ArrayHashMapUnmanaged( + bun.Semver.String, + Field.Entry, + bun.Semver.String.ArrayHashContext, + false, + ); + + pub const Encoding = union(enum) { + URLEncoded: void, + Multipart: []const u8, // boundary + + pub fn get(content_type: []const u8) ?Encoding { + if (strings.indexOf(content_type, "application/x-www-form-urlencoded") != null) + return Encoding{ .URLEncoded = void{} }; + + if (strings.indexOf(content_type, "multipart/form-data") == null) return null; + + const boundary = getBoundary(content_type) orelse return null; + return .{ + .Multipart = boundary, + }; + } + }; + + pub const AsyncFormData = struct { + encoding: Encoding, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator, encoding: Encoding) !*AsyncFormData { + var this = try allocator.create(AsyncFormData); + this.* = AsyncFormData{ + .encoding = switch (encoding) { + .Multipart => .{ + .Multipart = try allocator.dupe(u8, encoding.Multipart), + }, + else => encoding, + }, + .allocator = allocator, + }; + return this; + } + + pub fn deinit(this: *AsyncFormData) void { + if (this.encoding == .Multipart) + this.allocator.free(this.encoding.Multipart); + this.allocator.destroy(this); + } + + pub fn toJS(this: *AsyncFormData, global: *bun.JSC.JSGlobalObject, data: []const u8, promise: bun.JSC.AnyPromise) void { + if (this.encoding == .Multipart and this.encoding.Multipart.len == 0) { + promise.reject(global, bun.JSC.ZigString.init("FormData missing boundary").toErrorInstance(global)); + return; + } + + const js_value = bun.FormData.toJS( + global, + data, + this.encoding, + ) catch |err| { + promise.reject(global, global.createErrorInstance("FormData {s}", .{@errorName(err)})); + return; + }; + + promise.resolve(global, js_value); + } + }; + + pub fn getBoundary(content_type: []const u8) ?[]const u8 { + const boundary_index = strings.indexOf(content_type, "boundary=") orelse return null; + const boundary_start = boundary_index + "boundary=".len; + const begin = content_type[boundary_start..]; + if (begin.len == 0) + return null; + + var boundary_end = strings.indexOfChar(begin, ';') orelse @truncate(u32, begin.len); + if (begin[0] == '"' and boundary_end > 0 and begin[boundary_end -| 1] == '"') { + boundary_end -|= 1; + return begin[1..boundary_end]; + } + + return begin[0..boundary_end]; + } + + pub const Field = struct { + value: bun.Semver.String = .{}, + filename: bun.Semver.String = .{}, + content_type: bun.Semver.String = .{}, + is_file: bool = false, + zero_count: u8 = 0, + + pub const Entry = union(enum) { + field: Field, + list: bun.BabyList(Field), + }; + + pub const External = extern struct { + name: bun.JSC.ZigString, + value: bun.JSC.ZigString, + blob: ?*bun.JSC.WebCore.Blob = null, + }; + }; + + pub fn toJS(globalThis: *bun.JSC.JSGlobalObject, input: []const u8, encoding: Encoding) !bun.JSC.JSValue { + switch (encoding) { + .URLEncoded => { + var str = bun.JSC.ZigString.fromUTF8(input); + return bun.JSC.DOMFormData.createFromURLQuery(globalThis, &str); + }, + .Multipart => |boundary| return toJSFromMultipartData(globalThis, input, boundary), + } + } + + pub fn toJSFromMultipartData( + globalThis: *bun.JSC.JSGlobalObject, + input: []const u8, + boundary: []const u8, + ) !bun.JSC.JSValue { + const form_data_value = bun.JSC.DOMFormData.create(globalThis); + form_data_value.ensureStillAlive(); + var form = bun.JSC.DOMFormData.fromJS(form_data_value).?; + const Wrapper = struct { + globalThis: *bun.JSC.JSGlobalObject, + form: *bun.JSC.DOMFormData, + + pub fn onEntry(wrap: *@This(), name: bun.Semver.String, field: Field, buf: []const u8) void { + var value_str = field.value.slice(buf); + var key = bun.JSC.ZigString.initUTF8(name.slice(buf)); + + if (field.is_file) { + var filename_str = field.filename.slice(buf); + + var blob = bun.JSC.WebCore.Blob.create(value_str, bun.default_allocator, wrap.globalThis, false); + defer blob.detach(); + var filename = bun.JSC.ZigString.initUTF8(filename_str); + const content_type: []const u8 = brk: { + if (filename_str.len > 0) { + if (bun.HTTP.MimeType.byExtensionNoDefault(std.fs.path.extension(filename_str))) |mime| { + break :brk mime.value; + } + } + + if (bun.HTTP.MimeType.sniff(value_str)) |mime| { + break :brk mime.value; + } + + break :brk ""; + }; + + if (content_type.len > 0) { + blob.content_type = content_type; + blob.content_type_allocated = false; + } + + wrap.form.appendBlob(wrap.globalThis, &key, &blob, &filename); + } else { + var value = bun.JSC.ZigString.initUTF8(value_str); + wrap.form.append(&key, &value); + } + } + }; + + { + var wrap = Wrapper{ + .globalThis = globalThis, + .form = form, + }; + + try forEachMultipartEntry(input, boundary, *Wrapper, &wrap, Wrapper.onEntry); + } + + return form_data_value; + } + + pub fn forEachMultipartEntry( + input: []const u8, + boundary: []const u8, + comptime Ctx: type, + ctx: Ctx, + comptime iterator: fn ( + Ctx, + bun.Semver.String, + Field, + string, + ) void, + ) !void { + var slice = input; + var subslicer = bun.Semver.SlicedString.init(input, input); + + var buf: [76]u8 = undefined; + { + const final_boundary = std.fmt.bufPrint(&buf, "--{s}--", .{boundary}) catch |err| { + if (err == error.NoSpaceLeft) { + return error.@"boundary is too long"; + } + + return err; + }; + const final_boundary_index = strings.lastIndexOf(input, final_boundary); + if (final_boundary_index == null) { + return error.@"missing final boundary"; + } + slice = slice[0..final_boundary_index.?]; + } + + const separator = try std.fmt.bufPrint(&buf, "--{s}\r\n", .{boundary}); + var splitter = strings.split(slice, separator); + _ = splitter.next(); // skip first boundary + + while (splitter.next()) |chunk| { + var remain = chunk; + const header_end = strings.indexOf(remain, "\r\n\r\n") orelse return error.@"is missing header end"; + const header = remain[0 .. header_end + 2]; + remain = remain[header_end + 4 ..]; + + var field = Field{}; + var name: bun.Semver.String = .{}; + var filename: ?bun.Semver.String = null; + var header_chunk = header; + var is_file = false; + while (header_chunk.len > 0 and (filename == null or name.len() == 0)) { + const line_end = strings.indexOf(header_chunk, "\r\n") orelse return error.@"is missing header line end"; + const line = header_chunk[0..line_end]; + header_chunk = header_chunk[line_end + 2 ..]; + const colon = strings.indexOf(line, ":") orelse return error.@"is missing header colon separator"; + + const key = line[0..colon]; + var value = if (line.len > colon + 1) line[colon + 1 ..] else ""; + if (strings.eqlCaseInsensitiveASCII(key, "content-disposition", true)) { + value = strings.trim(value, " "); + if (strings.hasPrefixComptime(value, "form-data;")) { + value = value["form-data;".len..]; + value = strings.trim(value, " "); + } + + while (strings.indexOf(value, "=")) |eql_start| { + const eql_key = strings.trim(value[0..eql_start], " ;"); + value = value[eql_start + 1 ..]; + if (strings.hasPrefixComptime(value, "\"")) { + value = value[1..]; + } + + var field_value = value; + { + var i: usize = 0; + while (i < field_value.len) : (i += 1) { + switch (field_value[i]) { + '"' => { + field_value = field_value[0..i]; + break; + }, + '\\' => { + i += @boolToInt(field_value.len > i + 1 and field_value[i + 1] == '"'); + }, + // the spec requires a end quote, but some browsers don't send it + else => {}, + } + } + value = value[@min(i + 1, value.len)..]; + } + + if (strings.eqlCaseInsensitiveASCII(eql_key, "name", true)) { + name = subslicer.sub(field_value).value(); + } else if (strings.eqlCaseInsensitiveASCII(eql_key, "filename", true)) { + filename = subslicer.sub(field_value).value(); + is_file = true; + } + + if (!name.isEmpty() and filename != null) { + break; + } + + if (strings.indexOfChar(value, ';')) |semi_start| { + value = value[semi_start + 1 ..]; + } else { + break; + } + } + } else if (value.len > 0 and field.content_type.isEmpty() and strings.eqlCaseInsensitiveASCII(key, "content-type", true)) { + field.content_type = subslicer.sub(strings.trim(value, "; \t")).value(); + } + } + + if (name.len() + @as(usize, field.zero_count) == 0) { + continue; + } + + var body = remain; + if (strings.endsWithComptime(body, "\r\n")) { + body = body[0 .. body.len - 2]; + } + field.value = subslicer.sub(body).value(); + field.filename = filename orelse .{}; + field.is_file = is_file; + + iterator(ctx, name, field, input); + } + } +}; + const ParamsList = @import("./router.zig").Param.List; pub const CombinedScanner = struct { query: Scanner, |