Body.cpp 9.9 KB

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