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 >
2024-05-19 12:29:26 +01:00
* Copyright ( c ) 2024 , Jamie Mansfield < jmansfield @ cadixdev . org >
2022-09-25 19:30:24 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2024-12-24 19:11:33 -08:00
# include <AK/GenericLexer.h>
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>
2024-05-19 12:29:26 +01:00
# include <LibJS/Runtime/TypedArray.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>
2022-09-25 19:30:24 +01:00
# include <LibWeb/Bindings/MainThreadVM.h>
2024-07-22 13:49:07 +02:00
# include <LibWeb/DOMURL/URLSearchParams.h>
2022-09-25 19:30:24 +01:00
# include <LibWeb/Fetch/Body.h>
2024-12-24 19:11:33 -08:00
# include <LibWeb/Fetch/Infrastructure/HTTP.h>
2022-09-25 19:30:24 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
# include <LibWeb/FileAPI/Blob.h>
2024-12-24 19:11:33 -08:00
# include <LibWeb/FileAPI/File.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>
2024-12-24 19:11:33 -08:00
# include <LibWeb/Infra/Strings.h>
2022-09-25 19:30:24 +01:00
# include <LibWeb/MimeSniff/MimeType.h>
# include <LibWeb/Streams/ReadableStream.h>
# include <LibWeb/WebIDL/Promise.h>
2024-07-22 13:49:07 +02:00
# include <LibWeb/XHR/FormData.h>
2022-09-25 19:30:24 +01:00
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
2024-11-15 04:01:23 +13:00
GC : : Ptr < Streams : : ReadableStream > BodyMixin : : body ( ) const
2022-09-25 19:30:24 +01:00
{
// 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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : 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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : 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 ) ;
}
2024-05-19 12:29:26 +01:00
// https://fetch.spec.whatwg.org/#dom-body-bytes
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : Promise > > BodyMixin : : bytes ( ) const
2024-05-19 12:29:26 +01:00
{
auto & vm = Bindings : : main_thread_vm ( ) ;
auto & realm = * vm . current_realm ( ) ;
// The bytes() method steps are to return the result of running consume body with this and Uint8Array.
return consume_body ( realm , * this , PackageDataType : : Uint8Array ) ;
}
2022-09-25 19:30:24 +01:00
// https://fetch.spec.whatwg.org/#dom-body-formdata
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : 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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : 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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : 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.
2024-10-14 11:06:43 +02:00
auto mime_type_string = mime_type . has_value ( ) ? 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
}
2024-05-19 12:29:26 +01:00
case PackageDataType : : Uint8Array : {
// Return the result of creating a Uint8Array from bytes in this’ s relevant realm.
auto bytes_length = bytes . size ( ) ;
auto array_buffer = JS : : ArrayBuffer : : create ( realm , move ( bytes ) ) ;
return JS : : Uint8Array : : create ( realm , bytes_length , * array_buffer ) ;
}
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 ) {
2024-12-24 19:11:33 -08:00
// 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]
auto error_or_entry_list = parse_multipart_form_data ( realm , bytes , mime_type . value ( ) ) ;
// 2. If that fails for some reason, then throw a TypeError.
if ( error_or_entry_list . is_error ( ) )
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : TypeError , MUST ( String : : formatted ( " Failed to parse multipart form data: {} " , error_or_entry_list . release_error ( ) . message ) ) } ;
// 3. Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list.
return TRY ( XHR : : FormData : : create ( realm , error_or_entry_list . release_value ( ) ) ) ;
2022-09-25 19:30:24 +01:00
}
// 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 ) {
2024-07-22 13:49:07 +02:00
// 1. Let entries be the result of parsing bytes.
auto entries = DOMURL : : url_decode ( StringView { bytes } ) ;
2025-01-16 12:36:19 +13:00
// 2. Return a new FormData object whose entry list is entries.
2024-08-11 00:24:54 +12:00
return TRY ( XHR : : FormData : : create ( realm , entries ) ) ;
2022-09-25 19:30:24 +01:00
}
// 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 ( ) ) ;
2024-04-27 10:15:01 -04:00
auto utf8_text = MUST ( TextCodec : : convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark ( * decoder , bytes ) ) ;
2023-05-21 02:20:13 +02:00
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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < WebIDL : : 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.
2024-11-15 04:01:23 +13:00
// NOTE: `promise` and `realm` is protected by GC::HeapFunction.
auto error_steps = GC : : create_function ( realm . heap ( ) , [ promise , & realm ] ( JS : : Value error ) {
2023-07-06 07:43:23 -04:00
// AD-HOC: An execution context is required for Promise's reject function.
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { 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.
2024-11-15 04:01:23 +13:00
// NOTE: `promise`, `realm` and `object` is protected by GC::HeapFunction.
2023-02-28 18:12:44 +00:00
// FIXME: Refactor this to the new version of the spec introduced with https://github.com/whatwg/fetch/commit/464326e8eb6a602122c030cd40042480a3c0e265
2024-11-15 04:01:23 +13:00
auto success_steps = GC : : create_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.
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm } ;
2023-02-28 18:12:44 +00:00
auto value_or_error = Bindings : : throw_dom_exception_if_needed ( vm , [ & ] ( ) - > WebIDL : : ExceptionOr < JS : : Value > {
2024-04-27 10:15:01 -04:00
return package_data ( realm , data , type , object . mime_type_impl ( ) ) ;
2023-02-28 18:12:44 +00:00
} ) ;
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-11-15 04:01:23 +13:00
body - > fully_read ( realm , success_steps , error_steps , GC : : Ref { HTML : : relevant_global_object ( object . as_platform_object ( ) ) } ) ;
2023-02-28 18:12:44 +00:00
}
// 7. Return promise.
2024-10-25 12:38:19 -06:00
return promise ;
2022-09-25 19:30:24 +01:00
}
2024-12-24 19:11:33 -08:00
// https://andreubotella.github.io/multipart-form-data/#parse-a-multipart-form-data-name
static MultipartParsingErrorOr < String > parse_multipart_form_data_name ( GenericLexer & lexer )
{
// 1. Assert: The byte at (position - 1) is 0x22 (").
VERIFY ( lexer . peek ( - 1 ) = = ' " ' ) ;
// 2. Let name be the result of collecting a sequence of bytes that are not 0x0A (LF), 0x0D (CR) or 0x22 ("), given position.
auto name = lexer . consume_until ( is_any_of ( " \n \r \" " sv ) ) ;
// 3. If the byte at position is not 0x22 ("), return failure. Otherwise, advance position by 1.
if ( ! lexer . consume_specific ( ' " ' ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected \" at position {} " , lexer . tell ( ) ) ) } ;
// 4. Replace any occurrence of the following subsequences in name with the given byte:
// - "%0A" with 0x0A (LF)
// - "%0D" with 0x0D (CR)
// - "%22" with 0x22 (")
StringBuilder builder ;
for ( size_t i = 0 ; i < name . length ( ) ; + + i ) {
// Check for subsequences starting with '%'
if ( name [ i ] = = ' % ' & & i + 2 < name . length ( ) ) {
auto subsequence = name . substring_view ( i , 3 ) ;
if ( subsequence = = " %0A " sv ) {
builder . append ( 0x0A ) ; // Append LF
i + = 2 ; // Skip the next two characters
continue ;
}
if ( subsequence = = " %0D " sv ) {
builder . append ( 0x0D ) ; // Append CR
i + = 2 ; // Skip the next two characters
continue ;
}
if ( subsequence = = " %22 " sv ) {
builder . append ( 0x22 ) ; // Append "
i + = 2 ; // Skip the next two characters
continue ;
}
}
// Append the current character if no substitution was made
builder . append ( name [ i ] ) ;
}
return builder . to_string_without_validation ( ) ;
}
// https://andreubotella.github.io/multipart-form-data/#parse-multipart-form-data-headers
static MultipartParsingErrorOr < MultiPartFormDataHeader > parse_multipart_form_data_header ( GenericLexer & lexer )
{
// 1. Let name, filename and contentType be null.
MultiPartFormDataHeader header ;
// 2. While true:
while ( true ) {
// 1. If position points to a sequence of bytes starting with 0x0D 0x0A (CR LF):
if ( lexer . next_is ( " \r \n " sv ) ) {
// 1. If name is null, return failure.
if ( ! header . name . has_value ( ) )
return MultipartParsingError { " Missing name parameter in Content-Disposition header " _string } ;
// 2. Return name, filename and contentType.
return header ;
}
// 2. Let header name be the result of collecting a sequence of bytes that are not 0x0A (LF), 0x0D (CR) or 0x3A (:), given position.
auto header_name = lexer . consume_until ( is_any_of ( " \n \r : " sv ) ) ;
// 3. Remove any HTTP tab or space bytes from the start or end of header name.
header_name = header_name . trim ( Infrastructure : : HTTP_TAB_OR_SPACE , TrimMode : : Both ) ;
// 4. If header name does not match the field-name token production, return failure.
if ( ! Infrastructure : : is_header_name ( header_name . bytes ( ) ) )
return MultipartParsingError { MUST ( String : : formatted ( " Invalid header name {} " , header_name ) ) } ;
// 5. If the byte at position is not 0x3A (:), return failure.
// 6. Advance position by 1.
if ( ! lexer . consume_specific ( ' : ' ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected : at position {} " , lexer . tell ( ) ) ) } ;
// 7. Collect a sequence of bytes that are HTTP tab or space bytes given position. (Do nothing with those bytes.)
lexer . ignore_while ( Infrastructure : : is_http_tab_or_space ) ;
// 8. Byte-lowercase header name and switch on the result:
// -> `content-disposition`
if ( header_name . equals_ignoring_ascii_case ( " content-disposition " sv ) ) {
// 1. Set name and filename to null.
header . name . clear ( ) ;
header . filename . clear ( ) ;
// 2. If position does not point to a sequence of bytes starting with `form-data; name="`, return failure.
// 3. Advance position so it points at the byte after the next 0x22 (") byte (the one in the sequence of bytes matched above).
if ( ! lexer . consume_specific ( " form-data; name= \" " sv ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected `form-data; name= \" ` at position {} " , lexer . tell ( ) ) ) } ;
// 4. Set name to the result of parsing a multipart/form-data name given input and position, if the result is not failure. Otherwise, return failure.
auto maybe_name = parse_multipart_form_data_name ( lexer ) ;
if ( maybe_name . is_error ( ) )
return maybe_name . release_error ( ) ;
header . name = maybe_name . release_value ( ) ;
// 5. If position points to a sequence of bytes starting with `; filename="`:
// 1. Advance position so it points at the byte after the next 0x22 (") byte (the one in the sequence of bytes matched above).
if ( lexer . consume_specific ( " ; filename= \" " sv ) ) {
// 2. Set filename to the result of parsing a multipart/form-data name given input and position, if the result is not failure. Otherwise, return failure.
auto maybe_filename = parse_multipart_form_data_name ( lexer ) ;
if ( maybe_filename . is_error ( ) )
return maybe_filename . release_error ( ) ;
header . filename = maybe_filename . release_value ( ) ;
}
}
// -> `content-type`
else if ( header_name . equals_ignoring_ascii_case ( " content-type " sv ) ) {
// 1. Let header value be the result of collecting a sequence of bytes that are not 0x0A (LF) or 0x0D (CR), given position.
auto header_value = lexer . consume_until ( Infrastructure : : is_http_newline ) ;
// 2. Remove any HTTP tab or space bytes from the end of header value.
header_value = header_value . trim ( Infrastructure : : HTTP_TAB_OR_SPACE , TrimMode : : Right ) ;
// 3. Set contentType to the isomorphic decoding of header value.
header . content_type = Infra : : isomorphic_decode ( header_value . bytes ( ) ) ;
}
// -> Otherwise
else {
// 1. Collect a sequence of bytes that are not 0x0A (LF) or 0x0D (CR), given position. (Do nothing with those bytes.)
lexer . ignore_until ( Infrastructure : : is_http_newline ) ;
}
// 9. If position does not point to a sequence of bytes starting with 0x0D 0x0A (CR LF), return failure. Otherwise, advance position by 2 (past the newline).
if ( ! lexer . consume_specific ( " \r \n " sv ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected CRLF at position {} " , lexer . tell ( ) ) ) } ;
}
return header ;
}
// https://andreubotella.github.io/multipart-form-data/#multipart-form-data-parser
MultipartParsingErrorOr < Vector < XHR : : FormDataEntry > > parse_multipart_form_data ( JS : : Realm & realm , StringView input , MimeSniff : : MimeType const & mime_type )
{
// 1. Assert: mimeType’ s essence is "multipart/form-data".
VERIFY ( mime_type . essence ( ) = = " multipart/form-data " sv ) ;
// 2. If mimeType’ s parameters["boundary"] does not exist, return failure. Otherwise, let boundary be the result of UTF-8 decoding mimeType’ s parameters["boundary"].
auto maybe_boundary = mime_type . parameters ( ) . get ( " boundary " sv ) ;
if ( ! maybe_boundary . has_value ( ) )
return MultipartParsingError { " Missing boundary parameter in Content-Type header " _string } ;
auto boundary = maybe_boundary . release_value ( ) ;
// 3. Let entry list be an empty entry list.
Vector < XHR : : FormDataEntry > entry_list ;
// 4. Let position be a pointer to a byte in input, initially pointing at the first byte.
GenericLexer lexer ( input ) ;
auto boundary_with_dashes = MUST ( String : : formatted ( " --{} " , boundary ) ) ;
// 5. While true:
while ( true ) {
// 1. If position points to a sequence of bytes starting with 0x2D 0x2D (`--`) followed by boundary, advance position by 2 + the length of boundary. Otherwise, return failure.
if ( ! lexer . consume_specific ( boundary_with_dashes ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected `--` followed by boundary at position {} " , lexer . tell ( ) ) ) } ;
// 2. If position points to the sequence of bytes 0x2D 0x2D 0x0D 0x0A (`--` followed by CR LF) followed by the end of input, return entry list.
if ( lexer . next_is ( " -- \r \n " sv ) )
return entry_list ;
// 3. If position does not point to a sequence of bytes starting with 0x0D 0x0A (CR LF), return failure.
// 4. Advance position by 2. (This skips past the newline.)
if ( ! lexer . consume_specific ( " \r \n " sv ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected CRLF at position {} " , lexer . tell ( ) ) ) } ;
// 5. Let name, filename and contentType be the result of parsing multipart/form-data headers on input and position, if the result is not failure. Otherwise, return failure.
auto header = TRY ( parse_multipart_form_data_header ( lexer ) ) ;
// 6. Advance position by 2. (This skips past the empty line that marks the end of the headers.)
lexer . ignore ( 2 ) ;
// 7. Let body be the empty byte sequence.
// 8. Body loop: While position is not past the end of input:
// 1. Append the code point at position to body.
// 2. If body ends with boundary:
// 1. Remove the last 4 + (length of boundary) bytes from body.
// 2. Decrease position by 4 + (length of boundary).
// 3. Break out of body loop.
auto body = lexer . consume_until ( boundary_with_dashes . bytes_as_string_view ( ) ) ;
if ( lexer . next_is ( boundary_with_dashes . bytes_as_string_view ( ) ) ) {
constexpr size_t trailing_crlf_length = 2 ;
if ( body . length ( ) > = trailing_crlf_length ) {
body = body . substring_view ( 0 , body . length ( ) - trailing_crlf_length ) ;
lexer . retreat ( trailing_crlf_length ) ;
}
}
// 9. If position does not point to a sequence of bytes starting with 0x0D 0x0A (CR LF), return failure. Otherwise, advance position by 2.
if ( ! lexer . consume_specific ( " \r \n " sv ) )
return MultipartParsingError { MUST ( String : : formatted ( " Expected CRLF at position {} " , lexer . tell ( ) ) ) } ;
// 10. If filename is not null:
Optional < XHR : : FormDataEntryValue > value ;
if ( header . filename . has_value ( ) ) {
// 1. If contentType is null, set contentType to "text/plain".
if ( ! header . content_type . has_value ( ) )
header . content_type = " text/plain " _string ;
// 2. If contentType is not an ASCII string, set contentType to the empty string.
if ( ! all_of ( header . content_type - > code_points ( ) , is_ascii ) ) {
header . content_type = " " _string ;
}
// 3. Let value be a new File object with name filename, type contentType, and body body.
auto blob = FileAPI : : Blob : : create ( realm , MUST ( ByteBuffer : : copy ( body . bytes ( ) ) ) , header . content_type . release_value ( ) ) ;
FileAPI : : FilePropertyBag options { } ;
options . type = blob - > type ( ) ;
auto file = MUST ( FileAPI : : File : : create ( realm , { GC : : make_root ( blob ) } , header . filename . release_value ( ) , move ( options ) ) ) ;
value = GC : : make_root ( file ) ;
}
// 11. Otherwise:
else {
// 1. Let value be the UTF-8 decoding without BOM of body.
value = String : : from_utf8_with_replacement_character ( body , String : : WithBOMHandling : : No ) ;
}
// 12. Assert: name is a scalar value string and value is either a scalar value string or a File object.
VERIFY ( header . name . has_value ( ) & & value . has_value ( ) ) ;
// 13. Create an entry with name and value, and append it to entry list.
entry_list . empend ( header . name . release_value ( ) , value . release_value ( ) ) ;
}
}
2022-09-25 19:30:24 +01:00
}