Body.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
  3. * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  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 <LibJS/Runtime/TypedArray.h>
  13. #include <LibTextCodec/Decoder.h>
  14. #include <LibWeb/Bindings/ExceptionOrUtils.h>
  15. #include <LibWeb/Bindings/MainThreadVM.h>
  16. #include <LibWeb/DOMURL/URLSearchParams.h>
  17. #include <LibWeb/Fetch/Body.h>
  18. #include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
  19. #include <LibWeb/FileAPI/Blob.h>
  20. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  21. #include <LibWeb/Infra/JSON.h>
  22. #include <LibWeb/MimeSniff/MimeType.h>
  23. #include <LibWeb/Streams/ReadableStream.h>
  24. #include <LibWeb/WebIDL/Promise.h>
  25. #include <LibWeb/XHR/FormData.h>
  26. namespace Web::Fetch {
  27. BodyMixin::~BodyMixin() = default;
  28. // https://fetch.spec.whatwg.org/#body-unusable
  29. bool BodyMixin::is_unusable() const
  30. {
  31. // 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.
  32. auto const& body = body_impl();
  33. return body && (body->stream()->is_disturbed() || body->stream()->is_locked());
  34. }
  35. // https://fetch.spec.whatwg.org/#dom-body-body
  36. GC::Ptr<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 ? body->stream().ptr() : nullptr;
  41. }
  42. // https://fetch.spec.whatwg.org/#dom-body-bodyused
  43. bool BodyMixin::body_used() const
  44. {
  45. // 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.
  46. auto const& body = body_impl();
  47. return body && body->stream()->is_disturbed();
  48. }
  49. // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
  50. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> BodyMixin::array_buffer() const
  51. {
  52. auto& vm = Bindings::main_thread_vm();
  53. auto& realm = *vm.current_realm();
  54. // The arrayBuffer() method steps are to return the result of running consume body with this and ArrayBuffer.
  55. return consume_body(realm, *this, PackageDataType::ArrayBuffer);
  56. }
  57. // https://fetch.spec.whatwg.org/#dom-body-blob
  58. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> BodyMixin::blob() const
  59. {
  60. auto& vm = Bindings::main_thread_vm();
  61. auto& realm = *vm.current_realm();
  62. // The blob() method steps are to return the result of running consume body with this and Blob.
  63. return consume_body(realm, *this, PackageDataType::Blob);
  64. }
  65. // https://fetch.spec.whatwg.org/#dom-body-bytes
  66. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> BodyMixin::bytes() const
  67. {
  68. auto& vm = Bindings::main_thread_vm();
  69. auto& realm = *vm.current_realm();
  70. // The bytes() method steps are to return the result of running consume body with this and Uint8Array.
  71. return consume_body(realm, *this, PackageDataType::Uint8Array);
  72. }
  73. // https://fetch.spec.whatwg.org/#dom-body-formdata
  74. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> BodyMixin::form_data() const
  75. {
  76. auto& vm = Bindings::main_thread_vm();
  77. auto& realm = *vm.current_realm();
  78. // The formData() method steps are to return the result of running consume body with this and FormData.
  79. return consume_body(realm, *this, PackageDataType::FormData);
  80. }
  81. // https://fetch.spec.whatwg.org/#dom-body-json
  82. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> BodyMixin::json() const
  83. {
  84. auto& vm = Bindings::main_thread_vm();
  85. auto& realm = *vm.current_realm();
  86. // The json() method steps are to return the result of running consume body with this and JSON.
  87. return consume_body(realm, *this, PackageDataType::JSON);
  88. }
  89. // https://fetch.spec.whatwg.org/#dom-body-text
  90. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> BodyMixin::text() const
  91. {
  92. auto& vm = Bindings::main_thread_vm();
  93. auto& realm = *vm.current_realm();
  94. // The text() method steps are to return the result of running consume body with this and text.
  95. return consume_body(realm, *this, PackageDataType::Text);
  96. }
  97. // https://fetch.spec.whatwg.org/#concept-body-package-data
  98. WebIDL::ExceptionOr<JS::Value> package_data(JS::Realm& realm, ByteBuffer bytes, PackageDataType type, Optional<MimeSniff::MimeType> const& mime_type)
  99. {
  100. auto& vm = realm.vm();
  101. switch (type) {
  102. case PackageDataType::ArrayBuffer:
  103. // Return a new ArrayBuffer whose contents are bytes.
  104. return JS::ArrayBuffer::create(realm, move(bytes));
  105. case PackageDataType::Blob: {
  106. // Return a Blob whose contents are bytes and type attribute is mimeType.
  107. // NOTE: If extracting the mime type returns failure, other browsers set it to an empty string - not sure if that's spec'd.
  108. auto mime_type_string = mime_type.has_value() ? mime_type->serialized() : String {};
  109. return FileAPI::Blob::create(realm, move(bytes), move(mime_type_string));
  110. }
  111. case PackageDataType::Uint8Array: {
  112. // Return the result of creating a Uint8Array from bytes in this’s relevant realm.
  113. auto bytes_length = bytes.size();
  114. auto array_buffer = JS::ArrayBuffer::create(realm, move(bytes));
  115. return JS::Uint8Array::create(realm, bytes_length, *array_buffer);
  116. }
  117. case PackageDataType::FormData:
  118. // If mimeType’s essence is "multipart/form-data", then:
  119. if (mime_type.has_value() && mime_type->essence() == "multipart/form-data"sv) {
  120. // 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]
  121. // FIXME: 2. If that fails for some reason, then throw a TypeError.
  122. // FIXME: 3. Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list.
  123. return JS::js_null();
  124. }
  125. // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
  126. else if (mime_type.has_value() && mime_type->essence() == "application/x-www-form-urlencoded"sv) {
  127. // 1. Let entries be the result of parsing bytes.
  128. auto entries = DOMURL::url_decode(StringView { bytes });
  129. // 2. If entries is failure, then throw a TypeError.
  130. // FIXME: Spec bug? It doesn't seem possible to throw an error here.
  131. // 3. Return a new FormData object whose entry list is entries.
  132. return TRY(XHR::FormData::create(realm, entries));
  133. }
  134. // Otherwise, throw a TypeError.
  135. else {
  136. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Mime type must be 'multipart/form-data' or 'application/x-www-form-urlencoded'"sv };
  137. }
  138. case PackageDataType::JSON:
  139. // Return the result of running parse JSON from bytes on bytes.
  140. return Infra::parse_json_bytes_to_javascript_value(realm, bytes);
  141. case PackageDataType::Text: {
  142. // Return the result of running UTF-8 decode on bytes.
  143. auto decoder = TextCodec::decoder_for("UTF-8"sv);
  144. VERIFY(decoder.has_value());
  145. auto utf8_text = MUST(TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, bytes));
  146. return JS::PrimitiveString::create(vm, move(utf8_text));
  147. }
  148. default:
  149. VERIFY_NOT_REACHED();
  150. }
  151. }
  152. // https://fetch.spec.whatwg.org/#concept-body-consume-body
  153. WebIDL::ExceptionOr<GC::Ref<WebIDL::Promise>> consume_body(JS::Realm& realm, BodyMixin const& object, PackageDataType type)
  154. {
  155. // 1. If object is unusable, then return a promise rejected with a TypeError.
  156. if (object.is_unusable()) {
  157. WebIDL::SimpleException exception { WebIDL::SimpleExceptionType::TypeError, "Body is unusable"sv };
  158. return WebIDL::create_rejected_promise_from_exception(realm, move(exception));
  159. }
  160. // 2. Let promise be a new promise.
  161. auto promise = WebIDL::create_promise(realm);
  162. // 3. Let errorSteps given error be to reject promise with error.
  163. // NOTE: `promise` and `realm` is protected by GC::HeapFunction.
  164. auto error_steps = GC::create_function(realm.heap(), [promise, &realm](JS::Value error) {
  165. // AD-HOC: An execution context is required for Promise's reject function.
  166. HTML::TemporaryExecutionContext execution_context { realm };
  167. WebIDL::reject_promise(realm, promise, error);
  168. });
  169. // 4. Let successSteps given a byte sequence data be to resolve promise with the result of running convertBytesToJSValue
  170. // with data. If that threw an exception, then run errorSteps with that exception.
  171. // NOTE: `promise`, `realm` and `object` is protected by GC::HeapFunction.
  172. // FIXME: Refactor this to the new version of the spec introduced with https://github.com/whatwg/fetch/commit/464326e8eb6a602122c030cd40042480a3c0e265
  173. auto success_steps = GC::create_function(realm.heap(), [promise, &realm, &object, type](ByteBuffer data) {
  174. auto& vm = realm.vm();
  175. // AD-HOC: An execution context is required for Promise's reject function and JSON.parse.
  176. HTML::TemporaryExecutionContext execution_context { realm };
  177. auto value_or_error = Bindings::throw_dom_exception_if_needed(vm, [&]() -> WebIDL::ExceptionOr<JS::Value> {
  178. return package_data(realm, data, type, object.mime_type_impl());
  179. });
  180. if (value_or_error.is_error()) {
  181. // We can't call error_steps here without moving it into success_steps, causing a double move when we pause error_steps
  182. // to fully_read, so just reject the promise like error_steps does.
  183. WebIDL::reject_promise(realm, promise, value_or_error.release_error().value().value());
  184. return;
  185. }
  186. WebIDL::resolve_promise(realm, promise, value_or_error.release_value());
  187. });
  188. // 5. If object’s body is null, then run successSteps with an empty byte sequence.
  189. auto const& body = object.body_impl();
  190. if (!body) {
  191. success_steps->function()(ByteBuffer {});
  192. }
  193. // 6. Otherwise, fully read object’s body given successSteps, errorSteps, and object’s relevant global object.
  194. else {
  195. body->fully_read(realm, success_steps, error_steps, GC::Ref { HTML::relevant_global_object(object.as_platform_object()) });
  196. }
  197. // 7. Return promise.
  198. return promise;
  199. }
  200. }