ladybird/Userland/Libraries/LibWeb/Fetch/Body.cpp
Linus Groh fc9d587e39 LibJS: Make PromiseCapability GC-allocated
A struct with three raw pointers to other GC'd types is a pretty big
liability, let's just turn this into a Cell itself.
This comes with the additional benefit of being able to capture it in
a lambda effortlessly, without having to create handles for individual
members.
2022-10-02 23:02:27 +01:00

173 lines
7.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Fetch/Body.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
#include <LibWeb/FileAPI/Blob.h>
#include <LibWeb/Infra/JSON.h>
#include <LibWeb/MimeSniff/MimeType.h>
#include <LibWeb/Streams/ReadableStream.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::Fetch {
BodyMixin::~BodyMixin() = default;
// https://fetch.spec.whatwg.org/#body-unusable
bool BodyMixin::is_unusable() const
{
// An object including the Body interface mixin is said to be unusable if its body is non-null and its bodys stream is disturbed or locked.
auto const& body = body_impl();
return body.has_value() && (body->stream()->is_disturbed() || body->stream()->is_locked());
}
// https://fetch.spec.whatwg.org/#dom-body-body
JS::GCPtr<Streams::ReadableStream> BodyMixin::body() const
{
// The body getter steps are to return null if thiss body is null; otherwise thiss bodys stream.
auto const& body = body_impl();
return body.has_value() ? body->stream().ptr() : nullptr;
}
// https://fetch.spec.whatwg.org/#dom-body-bodyused
bool BodyMixin::body_used() const
{
// The bodyUsed getter steps are to return true if thiss body is non-null and thiss bodys stream is disturbed; otherwise false.
auto const& body = body_impl();
return body.has_value() && body->stream()->is_disturbed();
}
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
JS::NonnullGCPtr<JS::Promise> BodyMixin::array_buffer() const
{
auto& vm = Bindings::main_thread_vm();
auto& realm = *vm.current_realm();
// The arrayBuffer() method steps are to return the result of running consume body with this and ArrayBuffer.
return consume_body(realm, *this, PackageDataType::ArrayBuffer);
}
// https://fetch.spec.whatwg.org/#dom-body-blob
JS::NonnullGCPtr<JS::Promise> BodyMixin::blob() const
{
auto& vm = Bindings::main_thread_vm();
auto& realm = *vm.current_realm();
// The blob() method steps are to return the result of running consume body with this and Blob.
return consume_body(realm, *this, PackageDataType::Blob);
}
// https://fetch.spec.whatwg.org/#dom-body-formdata
JS::NonnullGCPtr<JS::Promise> BodyMixin::form_data() const
{
auto& vm = Bindings::main_thread_vm();
auto& realm = *vm.current_realm();
// The formData() method steps are to return the result of running consume body with this and FormData.
return consume_body(realm, *this, PackageDataType::FormData);
}
// https://fetch.spec.whatwg.org/#dom-body-json
JS::NonnullGCPtr<JS::Promise> BodyMixin::json() const
{
auto& vm = Bindings::main_thread_vm();
auto& realm = *vm.current_realm();
// The json() method steps are to return the result of running consume body with this and JSON.
return consume_body(realm, *this, PackageDataType::JSON);
}
// https://fetch.spec.whatwg.org/#dom-body-text
JS::NonnullGCPtr<JS::Promise> BodyMixin::text() const
{
auto& vm = Bindings::main_thread_vm();
auto& realm = *vm.current_realm();
// The text() method steps are to return the result of running consume body with this and text.
return consume_body(realm, *this, PackageDataType::Text);
}
// https://fetch.spec.whatwg.org/#concept-body-package-data
WebIDL::ExceptionOr<JS::Value> package_data(JS::Realm& realm, ByteBuffer bytes, PackageDataType type, Optional<MimeSniff::MimeType> const& mime_type)
{
auto& vm = realm.vm();
switch (type) {
case PackageDataType::ArrayBuffer:
// Return a new ArrayBuffer whose contents are bytes.
return JS::ArrayBuffer::create(realm, move(bytes));
case PackageDataType::Blob: {
// Return a Blob whose contents are bytes and type attribute is mimeType.
// NOTE: If extracting the mime type returns failure, other browsers set it to an empty string - not sure if that's spec'd.
auto mime_type_string = mime_type.has_value() ? mime_type->serialized() : String::empty();
return FileAPI::Blob::create(realm, move(bytes), move(mime_type_string));
}
case PackageDataType::FormData:
// If mimeTypes essence is "multipart/form-data", then:
if (mime_type.has_value() && mime_type->essence() == "multipart/form-data"sv) {
// FIXME: 1. Parse bytes, using the value of the `boundary` parameter from mimeType, per the rules set forth in Returning Values from Forms: multipart/form-data. [RFC7578]
// FIXME: 2. If that fails for some reason, then throw a TypeError.
// FIXME: 3. Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list.
return JS::js_null();
}
// Otherwise, if mimeTypes essence is "application/x-www-form-urlencoded", then:
else if (mime_type.has_value() && mime_type->essence() == "application/x-www-form-urlencoded"sv) {
// FIXME: 1. Let entries be the result of parsing bytes.
// FIXME: 2. If entries is failure, then throw a TypeError.
// FIXME: 3. Return a new FormData object whose entry list is entries.
return JS::js_null();
}
// Otherwise, throw a TypeError.
else {
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Mime type must be 'multipart/form-data' or 'application/x-www-form-urlencoded'"sv };
}
case PackageDataType::JSON:
// Return the result of running parse JSON from bytes on bytes.
return Infra::parse_json_bytes_to_javascript_value(vm, bytes);
case PackageDataType::Text:
// Return the result of running UTF-8 decode on bytes.
return JS::js_string(vm, String::copy(bytes));
default:
VERIFY_NOT_REACHED();
}
}
// https://fetch.spec.whatwg.org/#concept-body-consume-body
JS::NonnullGCPtr<JS::Promise> consume_body(JS::Realm& realm, BodyMixin const& object, PackageDataType type)
{
auto& vm = realm.vm();
// 1. If object is unusable, then return a promise rejected with a TypeError.
if (object.is_unusable()) {
auto promise_capability = WebIDL::create_rejected_promise(realm, JS::TypeError::create(realm, "Body is unusable"sv));
return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
}
// 2. Let promise be a promise resolved with an empty byte sequence.
auto promise = WebIDL::create_resolved_promise(realm, JS::js_string(vm, String::empty()));
// 3. If objects body is non-null, then set promise to the result of fully reading body as promise given objects body.
auto const& body = object.body_impl();
if (body.has_value())
promise = body->fully_read_as_promise();
// 4. Let steps be to return the result of package data with the first argument given, type, and objects MIME type.
auto steps = [&realm, &object, type](JS::Value value) -> WebIDL::ExceptionOr<JS::Value> {
VERIFY(value.is_string());
auto bytes = TRY_OR_RETURN_OOM(realm, ByteBuffer::copy(value.as_string().string().bytes()));
return package_data(realm, move(bytes), type, object.mime_type_impl());
};
// 5. Return the result of upon fulfillment of promise given steps.
return WebIDL::upon_fulfillment(promise, move(steps));
}
}