Serenity Operating System
at master 217 lines 10 kB view raw
1/* 2 * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/TypeCasts.h> 8#include <LibJS/Runtime/ArrayBuffer.h> 9#include <LibJS/Runtime/Completion.h> 10#include <LibJS/Runtime/Error.h> 11#include <LibJS/Runtime/PromiseCapability.h> 12#include <LibWeb/Bindings/ExceptionOrUtils.h> 13#include <LibWeb/Bindings/HostDefined.h> 14#include <LibWeb/Bindings/MainThreadVM.h> 15#include <LibWeb/Fetch/Body.h> 16#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h> 17#include <LibWeb/FileAPI/Blob.h> 18#include <LibWeb/Infra/JSON.h> 19#include <LibWeb/MimeSniff/MimeType.h> 20#include <LibWeb/Streams/ReadableStream.h> 21#include <LibWeb/WebIDL/Promise.h> 22 23namespace Web::Fetch { 24 25BodyMixin::~BodyMixin() = default; 26 27// https://fetch.spec.whatwg.org/#body-unusable 28bool BodyMixin::is_unusable() const 29{ 30 // An object including the Body interface mixin is said to be unusable if its body is non-null and its body’s stream is disturbed or locked. 31 auto const& body = body_impl(); 32 return body.has_value() && (body->stream()->is_disturbed() || body->stream()->is_locked()); 33} 34 35// https://fetch.spec.whatwg.org/#dom-body-body 36JS::GCPtr<Streams::ReadableStream> BodyMixin::body() const 37{ 38 // The body getter steps are to return null if this’s body is null; otherwise this’s body’s stream. 39 auto const& body = body_impl(); 40 return body.has_value() ? body->stream().ptr() : nullptr; 41} 42 43// https://fetch.spec.whatwg.org/#dom-body-bodyused 44bool BodyMixin::body_used() const 45{ 46 // The bodyUsed getter steps are to return true if this’s body is non-null and this’s body’s stream is disturbed; otherwise false. 47 auto const& body = body_impl(); 48 return body.has_value() && body->stream()->is_disturbed(); 49} 50 51// https://fetch.spec.whatwg.org/#dom-body-arraybuffer 52WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> BodyMixin::array_buffer() const 53{ 54 auto& vm = Bindings::main_thread_vm(); 55 auto& realm = *vm.current_realm(); 56 57 // The arrayBuffer() method steps are to return the result of running consume body with this and ArrayBuffer. 58 return consume_body(realm, *this, PackageDataType::ArrayBuffer); 59} 60 61// https://fetch.spec.whatwg.org/#dom-body-blob 62WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> BodyMixin::blob() const 63{ 64 auto& vm = Bindings::main_thread_vm(); 65 auto& realm = *vm.current_realm(); 66 67 // The blob() method steps are to return the result of running consume body with this and Blob. 68 return consume_body(realm, *this, PackageDataType::Blob); 69} 70 71// https://fetch.spec.whatwg.org/#dom-body-formdata 72WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> BodyMixin::form_data() const 73{ 74 auto& vm = Bindings::main_thread_vm(); 75 auto& realm = *vm.current_realm(); 76 77 // The formData() method steps are to return the result of running consume body with this and FormData. 78 return consume_body(realm, *this, PackageDataType::FormData); 79} 80 81// https://fetch.spec.whatwg.org/#dom-body-json 82WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> BodyMixin::json() const 83{ 84 auto& vm = Bindings::main_thread_vm(); 85 auto& realm = *vm.current_realm(); 86 87 // The json() method steps are to return the result of running consume body with this and JSON. 88 return consume_body(realm, *this, PackageDataType::JSON); 89} 90 91// https://fetch.spec.whatwg.org/#dom-body-text 92WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> BodyMixin::text() const 93{ 94 auto& vm = Bindings::main_thread_vm(); 95 auto& realm = *vm.current_realm(); 96 97 // The text() method steps are to return the result of running consume body with this and text. 98 return consume_body(realm, *this, PackageDataType::Text); 99} 100 101// https://fetch.spec.whatwg.org/#concept-body-package-data 102WebIDL::ExceptionOr<JS::Value> package_data(JS::Realm& realm, ByteBuffer bytes, PackageDataType type, Optional<MimeSniff::MimeType> const& mime_type) 103{ 104 auto& vm = realm.vm(); 105 106 switch (type) { 107 case PackageDataType::ArrayBuffer: 108 // Return a new ArrayBuffer whose contents are bytes. 109 return JS::ArrayBuffer::create(realm, move(bytes)); 110 case PackageDataType::Blob: { 111 // Return a Blob whose contents are bytes and type attribute is mimeType. 112 // NOTE: If extracting the mime type returns failure, other browsers set it to an empty string - not sure if that's spec'd. 113 auto mime_type_string = mime_type.has_value() ? TRY_OR_THROW_OOM(vm, mime_type->serialized()) : String {}; 114 return TRY(FileAPI::Blob::create(realm, move(bytes), move(mime_type_string))); 115 } 116 case PackageDataType::FormData: 117 // If mimeType’s essence is "multipart/form-data", then: 118 if (mime_type.has_value() && mime_type->essence() == "multipart/form-data"sv) { 119 // 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] 120 // FIXME: 2. If that fails for some reason, then throw a TypeError. 121 // FIXME: 3. Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list. 122 return JS::js_null(); 123 } 124 // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then: 125 else if (mime_type.has_value() && mime_type->essence() == "application/x-www-form-urlencoded"sv) { 126 // FIXME: 1. Let entries be the result of parsing bytes. 127 // FIXME: 2. If entries is failure, then throw a TypeError. 128 // FIXME: 3. Return a new FormData object whose entry list is entries. 129 return JS::js_null(); 130 } 131 // Otherwise, throw a TypeError. 132 else { 133 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Mime type must be 'multipart/form-data' or 'application/x-www-form-urlencoded'"sv }; 134 } 135 case PackageDataType::JSON: 136 // Return the result of running parse JSON from bytes on bytes. 137 return Infra::parse_json_bytes_to_javascript_value(realm, bytes); 138 case PackageDataType::Text: 139 // Return the result of running UTF-8 decode on bytes. 140 return JS::PrimitiveString::create(vm, TRY_OR_THROW_OOM(vm, String::from_utf8(bytes))); 141 default: 142 VERIFY_NOT_REACHED(); 143 } 144} 145 146// https://fetch.spec.whatwg.org/#concept-body-consume-body 147WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Promise>> consume_body(JS::Realm& realm, BodyMixin const& object, PackageDataType type) 148{ 149 // 1. If object is unusable, then return a promise rejected with a TypeError. 150 if (object.is_unusable()) { 151 auto promise_capability = WebIDL::create_rejected_promise(realm, JS::TypeError::create(realm, "Body is unusable"sv).release_allocated_value_but_fixme_should_propagate_errors()); 152 return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise_capability->promise().ptr()) }; 153 } 154 155 // 2. Let promise be a new promise. 156 auto promise = WebIDL::create_promise(realm); 157 158 // 3. Let errorSteps given error be to reject promise with error. 159 // NOTE: `promise` and `realm` is protected by JS::SafeFunction. 160 auto error_steps = [promise, &realm](JS::Object& error) { 161 // NOTE: Not part of the spec, but we need to have an execution context on the stack to call native functions. 162 // (In this case, Promise's reject function) 163 auto& environment_settings_object = Bindings::host_defined_environment_settings_object(realm); 164 environment_settings_object.prepare_to_run_script(); 165 166 WebIDL::reject_promise(realm, promise, &error); 167 168 // See above NOTE. 169 environment_settings_object.clean_up_after_running_script(); 170 }; 171 172 // 4. Let successSteps given a byte sequence data be to resolve promise with the result of running convertBytesToJSValue 173 // with data. If that threw an exception, then run errorSteps with that exception. 174 // NOTE: `promise`, `realm` and `object` is protected by JS::SafeFunction. 175 // FIXME: Refactor this to the new version of the spec introduced with https://github.com/whatwg/fetch/commit/464326e8eb6a602122c030cd40042480a3c0e265 176 auto success_steps = [promise, &realm, &object, type](ByteBuffer const& data) { 177 auto& vm = realm.vm(); 178 179 // NOTE: Not part of the spec, but we need to have an execution context on the stack to call native functions. 180 // (In this case, Promise's reject function and JSON.parse) 181 auto& environment_settings_object = Bindings::host_defined_environment_settings_object(realm); 182 environment_settings_object.prepare_to_run_script(); 183 184 ScopeGuard guard = [&]() { 185 // See above NOTE. 186 environment_settings_object.clean_up_after_running_script(); 187 }; 188 189 auto value_or_error = Bindings::throw_dom_exception_if_needed(vm, [&]() -> WebIDL::ExceptionOr<JS::Value> { 190 return package_data(realm, data, type, TRY_OR_THROW_OOM(vm, object.mime_type_impl())); 191 }); 192 193 if (value_or_error.is_error()) { 194 // We can't call error_steps here without moving it into success_steps, causing a double move when we pause error_steps 195 // to fully_read, so just reject the promise like error_steps does. 196 WebIDL::reject_promise(realm, promise, value_or_error.release_error().value().value()); 197 return; 198 } 199 200 WebIDL::resolve_promise(realm, promise, value_or_error.release_value()); 201 }; 202 203 // 5. If object’s body is null, then run successSteps with an empty byte sequence. 204 auto const& body = object.body_impl(); 205 if (!body.has_value()) { 206 success_steps(ByteBuffer {}); 207 } 208 // 6. Otherwise, fully read object’s body given successSteps, errorSteps, and object’s relevant global object. 209 else { 210 TRY(body->fully_read(realm, move(success_steps), move(error_steps), JS::NonnullGCPtr { HTML::relevant_global_object(object.as_platform_object()) })); 211 } 212 213 // 7. Return promise. 214 return JS::NonnullGCPtr { verify_cast<JS::Promise>(*promise->promise().ptr()) }; 215} 216 217}