2022-09-25 19:30:24 +01:00
/*
2023-03-02 22:26:12 +00:00
* Copyright ( c ) 2022 - 2023 , Linus Groh < linusg @ serenityos . org >
2022-09-25 19:30:24 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-11-23 13:41:50 +01:00
# include <AK/TypeCasts.h>
2022-09-25 19:30:24 +01:00
# include <LibJS/Runtime/ArrayBuffer.h>
2023-01-07 12:14:54 -05:00
# include <LibJS/Runtime/Completion.h>
2022-09-25 19:30:24 +01:00
# include <LibJS/Runtime/Error.h>
2022-10-02 10:59:22 +01:00
# include <LibJS/Runtime/PromiseCapability.h>
2023-05-21 02:20:13 +02:00
# include <LibTextCodec/Decoder.h>
2023-02-28 18:12:44 +00:00
# include <LibWeb/Bindings/ExceptionOrUtils.h>
# include <LibWeb/Bindings/HostDefined.h>
2022-09-25 19:30:24 +01:00
# include <LibWeb/Bindings/MainThreadVM.h>
# include <LibWeb/Fetch/Body.h>
# include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
# include <LibWeb/FileAPI/Blob.h>
2023-07-06 07:43:23 -04:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2022-09-25 19:30:24 +01:00
# 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 body’ s stream is disturbed or locked.
auto const & body = body_impl ( ) ;
2023-08-18 19:38:13 +02:00
return body & & ( body - > stream ( ) - > is_disturbed ( ) | | body - > stream ( ) - > is_locked ( ) ) ;
2022-09-25 19:30:24 +01:00
}
// https://fetch.spec.whatwg.org/#dom-body-body
JS : : GCPtr < Streams : : ReadableStream > BodyMixin : : body ( ) const
{
// The body getter steps are to return null if this’ s body is null; otherwise this’ s body’ s stream.
auto const & body = body_impl ( ) ;
2023-08-18 19:38:13 +02:00
return body ? body - > stream ( ) . ptr ( ) : nullptr ;
2022-09-25 19:30:24 +01:00
}
// https://fetch.spec.whatwg.org/#dom-body-bodyused
bool BodyMixin : : body_used ( ) const
{
// 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.
auto const & body = body_impl ( ) ;
2023-08-18 19:38:13 +02:00
return body & & body - > stream ( ) - > is_disturbed ( ) ;
2022-09-25 19:30:24 +01:00
}
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
2023-02-28 18:12:44 +00:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < JS : : Promise > > BodyMixin : : array_buffer ( ) const
2022-09-25 19:30:24 +01:00
{
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
2023-02-28 18:12:44 +00:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < JS : : Promise > > BodyMixin : : blob ( ) const
2022-09-25 19:30:24 +01:00
{
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
2023-02-28 18:12:44 +00:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < JS : : Promise > > BodyMixin : : form_data ( ) const
2022-09-25 19:30:24 +01:00
{
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
2023-02-28 18:12:44 +00:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < JS : : Promise > > BodyMixin : : json ( ) const
2022-09-25 19:30:24 +01:00
{
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
2023-02-28 18:12:44 +00:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < JS : : Promise > > BodyMixin : : text ( ) const
2022-09-25 19:30:24 +01:00
{
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.
2023-03-03 09:27:51 +00:00
auto mime_type_string = mime_type . has_value ( ) ? TRY_OR_THROW_OOM ( vm , mime_type - > serialized ( ) ) : String { } ;
2023-08-13 13:05:26 +02:00
return FileAPI : : Blob : : create ( realm , move ( bytes ) , move ( mime_type_string ) ) ;
2022-09-25 19:30:24 +01:00
}
case PackageDataType : : FormData :
// If mimeType’ s 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 mimeType’ s 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.
2023-02-28 17:45:49 +00:00
return Infra : : parse_json_bytes_to_javascript_value ( realm , bytes ) ;
2023-05-21 02:20:13 +02:00
case PackageDataType : : Text : {
2022-09-25 19:30:24 +01:00
// Return the result of running UTF-8 decode on bytes.
2023-05-21 02:20:13 +02:00
auto decoder = TextCodec : : decoder_for ( " UTF-8 " sv ) ;
VERIFY ( decoder . has_value ( ) ) ;
auto utf8_text = TRY_OR_THROW_OOM ( vm , TextCodec : : convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark ( * decoder , bytes ) ) ;
return JS : : PrimitiveString : : create ( vm , move ( utf8_text ) ) ;
}
2022-09-25 19:30:24 +01:00
default :
VERIFY_NOT_REACHED ( ) ;
}
}
// https://fetch.spec.whatwg.org/#concept-body-consume-body
2023-02-28 18:12:44 +00:00
WebIDL : : ExceptionOr < JS : : NonnullGCPtr < JS : : Promise > > consume_body ( JS : : Realm & realm , BodyMixin const & object , PackageDataType type )
2022-09-25 19:30:24 +01:00
{
// 1. If object is unusable, then return a promise rejected with a TypeError.
if ( object . is_unusable ( ) ) {
2024-03-08 15:37:28 +00:00
WebIDL : : SimpleException exception { WebIDL : : SimpleExceptionType : : TypeError , " Body is unusable " sv } ;
return WebIDL : : create_rejected_promise_from_exception ( realm , move ( exception ) ) ;
2022-09-25 19:30:24 +01:00
}
2023-02-28 18:12:44 +00:00
// 2. Let promise be a new promise.
auto promise = WebIDL : : create_promise ( realm ) ;
2022-09-25 19:30:24 +01:00
2023-02-28 18:12:44 +00:00
// 3. Let errorSteps given error be to reject promise with error.
// NOTE: `promise` and `realm` is protected by JS::SafeFunction.
2024-04-23 10:25:20 +02:00
auto error_steps = JS : : create_heap_function ( realm . heap ( ) , [ promise , & realm ] ( JS : : GCPtr < WebIDL : : DOMException > error ) {
2023-07-06 07:43:23 -04:00
// AD-HOC: An execution context is required for Promise's reject function.
HTML : : TemporaryExecutionContext execution_context { Bindings : : host_defined_environment_settings_object ( realm ) } ;
2023-05-13 12:44:46 +01:00
WebIDL : : reject_promise ( realm , promise , error ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2022-09-25 19:30:24 +01:00
2023-02-28 18:12:44 +00:00
// 4. Let successSteps given a byte sequence data be to resolve promise with the result of running convertBytesToJSValue
// with data. If that threw an exception, then run errorSteps with that exception.
// NOTE: `promise`, `realm` and `object` is protected by JS::SafeFunction.
// FIXME: Refactor this to the new version of the spec introduced with https://github.com/whatwg/fetch/commit/464326e8eb6a602122c030cd40042480a3c0e265
2024-04-23 10:25:20 +02:00
auto success_steps = JS : : create_heap_function ( realm . heap ( ) , [ promise , & realm , & object , type ] ( ByteBuffer data ) {
2023-02-28 18:12:44 +00:00
auto & vm = realm . vm ( ) ;
2023-07-06 07:43:23 -04:00
// AD-HOC: An execution context is required for Promise's reject function and JSON.parse.
HTML : : TemporaryExecutionContext execution_context { Bindings : : host_defined_environment_settings_object ( realm ) } ;
2023-02-28 18:12:44 +00:00
auto value_or_error = Bindings : : throw_dom_exception_if_needed ( vm , [ & ] ( ) - > WebIDL : : ExceptionOr < JS : : Value > {
return package_data ( realm , data , type , TRY_OR_THROW_OOM ( vm , object . mime_type_impl ( ) ) ) ;
} ) ;
if ( value_or_error . is_error ( ) ) {
// We can't call error_steps here without moving it into success_steps, causing a double move when we pause error_steps
// to fully_read, so just reject the promise like error_steps does.
WebIDL : : reject_promise ( realm , promise , value_or_error . release_error ( ) . value ( ) . value ( ) ) ;
return ;
}
WebIDL : : resolve_promise ( realm , promise , value_or_error . release_value ( ) ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2023-02-28 18:12:44 +00:00
// 5. If object’ s body is null, then run successSteps with an empty byte sequence.
auto const & body = object . body_impl ( ) ;
2023-08-18 19:38:13 +02:00
if ( ! body ) {
2024-04-23 10:25:20 +02:00
success_steps - > function ( ) ( ByteBuffer { } ) ;
2023-02-28 18:12:44 +00:00
}
// 6. Otherwise, fully read object’ s body given successSteps, errorSteps, and object’ s relevant global object.
else {
2024-04-26 14:57:40 -04:00
body - > fully_read ( realm , success_steps , error_steps , JS : : NonnullGCPtr { HTML : : relevant_global_object ( object . as_platform_object ( ) ) } ) ;
2023-02-28 18:12:44 +00:00
}
// 7. Return promise.
return JS : : NonnullGCPtr { verify_cast < JS : : Promise > ( * promise - > promise ( ) . ptr ( ) ) } ;
2022-09-25 19:30:24 +01:00
}
}