Body.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. * Copyright (c) 2022, 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/MainThreadVM.h>
  12. #include <LibWeb/Fetch/Body.h>
  13. #include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
  14. #include <LibWeb/FileAPI/Blob.h>
  15. #include <LibWeb/Infra/JSON.h>
  16. #include <LibWeb/MimeSniff/MimeType.h>
  17. #include <LibWeb/Streams/ReadableStream.h>
  18. #include <LibWeb/WebIDL/Promise.h>
  19. namespace Web::Fetch {
  20. BodyMixin::~BodyMixin() = default;
  21. // https://fetch.spec.whatwg.org/#body-unusable
  22. bool BodyMixin::is_unusable() const
  23. {
  24. // 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.
  25. auto const& body = body_impl();
  26. return body.has_value() && (body->stream()->is_disturbed() || body->stream()->is_locked());
  27. }
  28. // https://fetch.spec.whatwg.org/#dom-body-body
  29. JS::GCPtr<Streams::ReadableStream> BodyMixin::body() const
  30. {
  31. // The body getter steps are to return null if this’s body is null; otherwise this’s body’s stream.
  32. auto const& body = body_impl();
  33. return body.has_value() ? body->stream().ptr() : nullptr;
  34. }
  35. // https://fetch.spec.whatwg.org/#dom-body-bodyused
  36. bool BodyMixin::body_used() const
  37. {
  38. // 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.
  39. auto const& body = body_impl();
  40. return body.has_value() && body->stream()->is_disturbed();
  41. }
  42. // https://fetch.spec.whatwg.org/#dom-body-arraybuffer
  43. JS::NonnullGCPtr<JS::Promise> BodyMixin::array_buffer() const
  44. {
  45. auto& vm = Bindings::main_thread_vm();
  46. auto& realm = *vm.current_realm();
  47. // The arrayBuffer() method steps are to return the result of running consume body with this and ArrayBuffer.
  48. return consume_body(realm, *this, PackageDataType::ArrayBuffer);
  49. }
  50. // https://fetch.spec.whatwg.org/#dom-body-blob
  51. JS::NonnullGCPtr<JS::Promise> BodyMixin::blob() const
  52. {
  53. auto& vm = Bindings::main_thread_vm();
  54. auto& realm = *vm.current_realm();
  55. // The blob() method steps are to return the result of running consume body with this and Blob.
  56. return consume_body(realm, *this, PackageDataType::Blob);
  57. }
  58. // https://fetch.spec.whatwg.org/#dom-body-formdata
  59. JS::NonnullGCPtr<JS::Promise> BodyMixin::form_data() const
  60. {
  61. auto& vm = Bindings::main_thread_vm();
  62. auto& realm = *vm.current_realm();
  63. // The formData() method steps are to return the result of running consume body with this and FormData.
  64. return consume_body(realm, *this, PackageDataType::FormData);
  65. }
  66. // https://fetch.spec.whatwg.org/#dom-body-json
  67. JS::NonnullGCPtr<JS::Promise> BodyMixin::json() const
  68. {
  69. auto& vm = Bindings::main_thread_vm();
  70. auto& realm = *vm.current_realm();
  71. // The json() method steps are to return the result of running consume body with this and JSON.
  72. return consume_body(realm, *this, PackageDataType::JSON);
  73. }
  74. // https://fetch.spec.whatwg.org/#dom-body-text
  75. JS::NonnullGCPtr<JS::Promise> BodyMixin::text() const
  76. {
  77. auto& vm = Bindings::main_thread_vm();
  78. auto& realm = *vm.current_realm();
  79. // The text() method steps are to return the result of running consume body with this and text.
  80. return consume_body(realm, *this, PackageDataType::Text);
  81. }
  82. // https://fetch.spec.whatwg.org/#concept-body-package-data
  83. WebIDL::ExceptionOr<JS::Value> package_data(JS::Realm& realm, ByteBuffer bytes, PackageDataType type, Optional<MimeSniff::MimeType> const& mime_type)
  84. {
  85. auto& vm = realm.vm();
  86. switch (type) {
  87. case PackageDataType::ArrayBuffer:
  88. // Return a new ArrayBuffer whose contents are bytes.
  89. return JS::ArrayBuffer::create(realm, move(bytes));
  90. case PackageDataType::Blob: {
  91. // Return a Blob whose contents are bytes and type attribute is mimeType.
  92. // NOTE: If extracting the mime type returns failure, other browsers set it to an empty string - not sure if that's spec'd.
  93. auto mime_type_string = mime_type.has_value() ? mime_type->serialized() : DeprecatedString::empty();
  94. return TRY(FileAPI::Blob::create(realm, move(bytes), move(mime_type_string)));
  95. }
  96. case PackageDataType::FormData:
  97. // If mimeType’s essence is "multipart/form-data", then:
  98. if (mime_type.has_value() && mime_type->essence() == "multipart/form-data"sv) {
  99. // 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]
  100. // FIXME: 2. If that fails for some reason, then throw a TypeError.
  101. // FIXME: 3. Return a new FormData object, appending each entry, resulting from the parsing operation, to its entry list.
  102. return JS::js_null();
  103. }
  104. // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:
  105. else if (mime_type.has_value() && mime_type->essence() == "application/x-www-form-urlencoded"sv) {
  106. // FIXME: 1. Let entries be the result of parsing bytes.
  107. // FIXME: 2. If entries is failure, then throw a TypeError.
  108. // FIXME: 3. Return a new FormData object whose entry list is entries.
  109. return JS::js_null();
  110. }
  111. // Otherwise, throw a TypeError.
  112. else {
  113. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Mime type must be 'multipart/form-data' or 'application/x-www-form-urlencoded'"sv };
  114. }
  115. case PackageDataType::JSON:
  116. // Return the result of running parse JSON from bytes on bytes.
  117. return Infra::parse_json_bytes_to_javascript_value(vm, bytes);
  118. case PackageDataType::Text:
  119. // Return the result of running UTF-8 decode on bytes.
  120. return JS::PrimitiveString::create(vm, DeprecatedString::copy(bytes));
  121. default:
  122. VERIFY_NOT_REACHED();
  123. }
  124. }
  125. // https://fetch.spec.whatwg.org/#concept-body-consume-body
  126. JS::NonnullGCPtr<JS::Promise> consume_body(JS::Realm& realm, BodyMixin const& object, PackageDataType type)
  127. {
  128. auto& vm = realm.vm();
  129. // 1. If object is unusable, then return a promise rejected with a TypeError.
  130. if (object.is_unusable()) {
  131. auto promise_capability = WebIDL::create_rejected_promise(realm, JS::TypeError::create(realm, "Body is unusable"sv).release_allocated_value_but_fixme_should_propagate_errors());
  132. return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
  133. }
  134. // 2. Let promise be a promise resolved with an empty byte sequence.
  135. auto promise = WebIDL::create_resolved_promise(realm, JS::PrimitiveString::create(vm, String {}));
  136. // 3. If object’s body is non-null, then set promise to the result of fully reading body as promise given object’s body.
  137. auto const& body = object.body_impl();
  138. if (body.has_value())
  139. promise = body->fully_read_as_promise();
  140. // 4. Let steps be to return the result of package data with the first argument given, type, and object’s MIME type.
  141. auto steps = [&vm, &realm, &object, type](JS::Value value) -> WebIDL::ExceptionOr<JS::Value> {
  142. VERIFY(value.is_string());
  143. auto bytes = TRY_OR_THROW_OOM(vm, ByteBuffer::copy(TRY(value.as_string().deprecated_string()).bytes()));
  144. return package_data(realm, move(bytes), type, object.mime_type_impl());
  145. };
  146. // 5. Return the result of upon fulfillment of promise given steps.
  147. return WebIDL::upon_fulfillment(promise, move(steps));
  148. }
  149. }