/src/serenity/Userland/Libraries/LibWeb/Fetch/BodyInit.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org> |
3 | | * Copyright (c) 2022-2024, Kenneth Myhra <kennethmyhra@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <LibJS/Runtime/Completion.h> |
9 | | #include <LibJS/Runtime/TypedArray.h> |
10 | | #include <LibWeb/DOMURL/URLSearchParams.h> |
11 | | #include <LibWeb/Fetch/BodyInit.h> |
12 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h> |
13 | | #include <LibWeb/FileAPI/Blob.h> |
14 | | #include <LibWeb/HTML/FormControlInfrastructure.h> |
15 | | #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h> |
16 | | #include <LibWeb/Platform/EventLoopPlugin.h> |
17 | | #include <LibWeb/Streams/AbstractOperations.h> |
18 | | #include <LibWeb/WebIDL/AbstractOperations.h> |
19 | | #include <LibWeb/WebIDL/Buffers.h> |
20 | | #include <LibWeb/WebIDL/ExceptionOr.h> |
21 | | #include <LibWeb/XHR/FormData.h> |
22 | | |
23 | | namespace Web::Fetch { |
24 | | |
25 | | // https://fetch.spec.whatwg.org/#bodyinit-safely-extract |
26 | | WebIDL::ExceptionOr<Infrastructure::BodyWithType> safely_extract_body(JS::Realm& realm, BodyInitOrReadableBytes const& object) |
27 | 0 | { |
28 | | // 1. If object is a ReadableStream object, then: |
29 | 0 | if (auto const* stream = object.get_pointer<JS::Handle<Streams::ReadableStream>>()) { |
30 | | // 1. Assert: object is neither disturbed nor locked. |
31 | 0 | VERIFY(!((*stream)->is_disturbed() || (*stream)->is_locked())); |
32 | 0 | } |
33 | | |
34 | | // 2. Return the result of extracting object. |
35 | 0 | return extract_body(realm, object); |
36 | 0 | } |
37 | | |
38 | | // https://fetch.spec.whatwg.org/#concept-bodyinit-extract |
39 | | WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm, BodyInitOrReadableBytes const& object, bool keepalive) |
40 | 0 | { |
41 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; |
42 | |
|
43 | 0 | auto& vm = realm.vm(); |
44 | | |
45 | | // 1. Let stream be null. |
46 | 0 | JS::GCPtr<Streams::ReadableStream> stream; |
47 | | |
48 | | // 2. If object is a ReadableStream object, then set stream to object. |
49 | 0 | if (auto const* stream_handle = object.get_pointer<JS::Handle<Streams::ReadableStream>>()) { |
50 | 0 | stream = const_cast<Streams::ReadableStream*>(stream_handle->cell()); |
51 | 0 | } |
52 | | // 3. Otherwise, if object is a Blob object, set stream to the result of running object’s get stream. |
53 | 0 | else if (auto const* blob_handle = object.get_pointer<JS::Handle<FileAPI::Blob>>()) { |
54 | 0 | stream = blob_handle->cell()->get_stream(); |
55 | 0 | } |
56 | | // 4. Otherwise, set stream to a new ReadableStream object, and set up stream with byte reading support. |
57 | 0 | else { |
58 | 0 | stream = realm.heap().allocate<Streams::ReadableStream>(realm, realm); |
59 | 0 | Streams::set_up_readable_stream_controller_with_byte_reading_support(*stream); |
60 | 0 | } |
61 | | |
62 | | // 5. Assert: stream is a ReadableStream object. |
63 | 0 | VERIFY(stream); |
64 | | |
65 | | // 6. Let action be null. |
66 | 0 | Function<ByteBuffer()> action; |
67 | | |
68 | | // 7. Let source be null. |
69 | 0 | Infrastructure::Body::SourceType source {}; |
70 | | |
71 | | // 8. Let length be null. |
72 | 0 | Optional<u64> length {}; |
73 | | |
74 | | // 9. Let type be null. |
75 | 0 | Optional<ByteBuffer> type {}; |
76 | | |
77 | | // 10. Switch on object. |
78 | 0 | TRY(object.visit( |
79 | 0 | [&](JS::Handle<FileAPI::Blob> const& blob) -> WebIDL::ExceptionOr<void> { |
80 | | // Set source to object. |
81 | 0 | source = blob; |
82 | | // Set length to object’s size. |
83 | 0 | length = blob->size(); |
84 | | // If object’s type attribute is not the empty byte sequence, set type to its value. |
85 | 0 | if (!blob->type().is_empty()) |
86 | 0 | type = MUST(ByteBuffer::copy(blob->type().bytes())); |
87 | 0 | return {}; |
88 | 0 | }, |
89 | 0 | [&](ReadonlyBytes bytes) -> WebIDL::ExceptionOr<void> { |
90 | | // Set source to object. |
91 | 0 | source = MUST(ByteBuffer::copy(bytes)); |
92 | 0 | return {}; |
93 | 0 | }, |
94 | 0 | [&](JS::Handle<WebIDL::BufferSource> const& buffer_source) -> WebIDL::ExceptionOr<void> { |
95 | | // Set source to a copy of the bytes held by object. |
96 | 0 | source = MUST(WebIDL::get_buffer_source_copy(*buffer_source->raw_object())); |
97 | 0 | return {}; |
98 | 0 | }, |
99 | 0 | [&](JS::Handle<XHR::FormData> const& form_data) -> WebIDL::ExceptionOr<void> { |
100 | | // Set action to this step: run the multipart/form-data encoding algorithm, with object’s entry list and UTF-8. |
101 | 0 | auto serialized_form_data = MUST(HTML::serialize_to_multipart_form_data(form_data->entry_list())); |
102 | | // Set source to object. |
103 | 0 | source = serialized_form_data.serialized_data; |
104 | | // FIXME: Set length to unclear, see html/6424 for improving this. |
105 | | // Set type to `multipart/form-data; boundary=`, followed by the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm. |
106 | 0 | type = MUST(ByteBuffer::copy(MUST(String::formatted("multipart/form-data; boundary={}"sv, serialized_form_data.boundary)).bytes())); |
107 | 0 | return {}; |
108 | 0 | }, |
109 | 0 | [&](JS::Handle<DOMURL::URLSearchParams> const& url_search_params) -> WebIDL::ExceptionOr<void> { |
110 | | // Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list. |
111 | 0 | auto search_params_string = url_search_params->to_string(); |
112 | 0 | source = MUST(ByteBuffer::copy(search_params_string.bytes())); |
113 | | // Set type to `application/x-www-form-urlencoded;charset=UTF-8`. |
114 | 0 | type = MUST(ByteBuffer::copy("application/x-www-form-urlencoded;charset=UTF-8"sv.bytes())); |
115 | 0 | return {}; |
116 | 0 | }, |
117 | 0 | [&](String const& scalar_value_string) -> WebIDL::ExceptionOr<void> { |
118 | | // NOTE: AK::String is always UTF-8. |
119 | | // Set source to the UTF-8 encoding of object. |
120 | 0 | source = MUST(ByteBuffer::copy(scalar_value_string.bytes())); |
121 | | // Set type to `text/plain;charset=UTF-8`. |
122 | 0 | type = MUST(ByteBuffer::copy("text/plain;charset=UTF-8"sv.bytes())); |
123 | 0 | return {}; |
124 | 0 | }, |
125 | 0 | [&](JS::Handle<Streams::ReadableStream> const& stream) -> WebIDL::ExceptionOr<void> { |
126 | | // If keepalive is true, then throw a TypeError. |
127 | 0 | if (keepalive) |
128 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot extract body from stream when keepalive is set"sv }; |
129 | | |
130 | | // If object is disturbed or locked, then throw a TypeError. |
131 | 0 | if (stream->is_disturbed() || stream->is_locked()) |
132 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Cannot extract body from disturbed or locked stream"sv }; |
133 | |
|
134 | 0 | return {}; |
135 | 0 | })); |
136 | | |
137 | | // 11. If source is a byte sequence, then set action to a step that returns source and length to source’s length. |
138 | | // For now, do it synchronously. |
139 | 0 | if (source.has<ByteBuffer>()) { |
140 | 0 | action = [source = MUST(ByteBuffer::copy(source.get<ByteBuffer>()))]() mutable { |
141 | 0 | return move(source); |
142 | 0 | }; |
143 | 0 | length = source.get<ByteBuffer>().size(); |
144 | 0 | } |
145 | | |
146 | | // 12. If action is non-null, then run these steps in parallel: |
147 | 0 | if (action) { |
148 | 0 | Platform::EventLoopPlugin::the().deferred_invoke([&realm, stream, action = move(action)] { |
149 | 0 | HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; |
150 | | |
151 | | // 1. Run action. |
152 | 0 | auto bytes = action(); |
153 | | |
154 | | // Whenever one or more bytes are available and stream is not errored, enqueue the result of creating a |
155 | | // Uint8Array from the available bytes into stream. |
156 | 0 | if (!bytes.is_empty() && !stream->is_errored()) { |
157 | 0 | auto array_buffer = JS::ArrayBuffer::create(stream->realm(), move(bytes)); |
158 | 0 | auto chunk = JS::Uint8Array::create(stream->realm(), array_buffer->byte_length(), *array_buffer); |
159 | |
|
160 | 0 | Streams::readable_stream_enqueue(*stream->controller(), chunk).release_value_but_fixme_should_propagate_errors(); |
161 | 0 | } |
162 | | |
163 | | // When running action is done, close stream. |
164 | 0 | stream->close(); |
165 | 0 | }); |
166 | 0 | } |
167 | | |
168 | | // 13. Let body be a body whose stream is stream, source is source, and length is length. |
169 | 0 | auto body = Infrastructure::Body::create(vm, *stream, move(source), move(length)); |
170 | | |
171 | | // 14. Return (body, type). |
172 | 0 | return Infrastructure::BodyWithType { .body = move(body), .type = move(type) }; |
173 | 0 | } |
174 | | |
175 | | } |