Serenity Operating System
at master 302 lines 14 kB view raw
1/* 2 * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/URLParser.h> 8#include <LibJS/Runtime/Completion.h> 9#include <LibWeb/Bindings/Intrinsics.h> 10#include <LibWeb/Bindings/MainThreadVM.h> 11#include <LibWeb/Fetch/Enums.h> 12#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h> 13#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h> 14#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h> 15#include <LibWeb/Fetch/Response.h> 16#include <LibWeb/HTML/Scripting/Environments.h> 17#include <LibWeb/Infra/JSON.h> 18 19namespace Web::Fetch { 20 21Response::Response(JS::Realm& realm, JS::NonnullGCPtr<Infrastructure::Response> response) 22 : PlatformObject(realm) 23 , m_response(response) 24{ 25} 26 27Response::~Response() = default; 28 29JS::ThrowCompletionOr<void> Response::initialize(JS::Realm& realm) 30{ 31 MUST_OR_THROW_OOM(Base::initialize(realm)); 32 set_prototype(&Bindings::ensure_web_prototype<Bindings::ResponsePrototype>(realm, "Response")); 33 34 return {}; 35} 36 37void Response::visit_edges(Cell::Visitor& visitor) 38{ 39 Base::visit_edges(visitor); 40 visitor.visit(m_response); 41 visitor.visit(m_headers); 42} 43 44// https://fetch.spec.whatwg.org/#concept-body-mime-type 45// https://fetch.spec.whatwg.org/#ref-for-concept-header-extract-mime-type%E2%91%A7 46ErrorOr<Optional<MimeSniff::MimeType>> Response::mime_type_impl() const 47{ 48 // Objects including the Body interface mixin need to define an associated MIME type algorithm which takes no arguments and returns failure or a MIME type. 49 // A Response object’s MIME type is to return the result of extracting a MIME type from its response’s header list. 50 return m_response->header_list()->extract_mime_type(); 51} 52 53// https://fetch.spec.whatwg.org/#concept-body-body 54// https://fetch.spec.whatwg.org/#ref-for-concept-body-body%E2%91%A8 55Optional<Infrastructure::Body const&> Response::body_impl() const 56{ 57 // Objects including the Body interface mixin have an associated body (null or a body). 58 // A Response object’s body is its response’s body. 59 return m_response->body().has_value() 60 ? m_response->body().value() 61 : Optional<Infrastructure::Body const&> {}; 62} 63 64// https://fetch.spec.whatwg.org/#concept-body-body 65// https://fetch.spec.whatwg.org/#ref-for-concept-body-body%E2%91%A8 66Optional<Infrastructure::Body&> Response::body_impl() 67{ 68 // Objects including the Body interface mixin have an associated body (null or a body). 69 // A Response object’s body is its response’s body. 70 return m_response->body().has_value() 71 ? m_response->body().value() 72 : Optional<Infrastructure::Body&> {}; 73} 74 75// https://fetch.spec.whatwg.org/#response-create 76WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::create(JS::Realm& realm, JS::NonnullGCPtr<Infrastructure::Response> response, Headers::Guard guard) 77{ 78 // 1. Let responseObject be a new Response object with realm. 79 // 2. Set responseObject’s response to response. 80 auto response_object = MUST_OR_THROW_OOM(realm.heap().allocate<Response>(realm, realm, response)); 81 82 // 3. Set responseObject’s headers to a new Headers object with realm, whose headers list is response’s headers list and guard is guard. 83 response_object->m_headers = MUST_OR_THROW_OOM(realm.heap().allocate<Headers>(realm, realm, response->header_list())); 84 response_object->m_headers->set_guard(guard); 85 86 // 4. Return responseObject. 87 return response_object; 88} 89 90// https://fetch.spec.whatwg.org/#initialize-a-response 91WebIDL::ExceptionOr<void> Response::initialize_response(ResponseInit const& init, Optional<Infrastructure::BodyWithType> const& body) 92{ 93 auto& vm = this->vm(); 94 95 // 1. If init["status"] is not in the range 200 to 599, inclusive, then throw a RangeError. 96 if (init.status < 200 || init.status > 599) 97 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Status must be in range 200-599"sv }; 98 99 // FIXME: 2. If init["statusText"] does not match the reason-phrase token production, then throw a TypeError. 100 101 // 3. Set response’s response’s status to init["status"]. 102 m_response->set_status(init.status); 103 104 // 4. Set response’s response’s status message to init["statusText"]. 105 m_response->set_status_message(TRY_OR_THROW_OOM(vm, ByteBuffer::copy(init.status_text.bytes()))); 106 107 // 5. If init["headers"] exists, then fill response’s headers with init["headers"]. 108 if (init.headers.has_value()) 109 TRY(m_headers->fill(*init.headers)); 110 111 // 6. If body was given, then: 112 if (body.has_value()) { 113 // 1. If response’s status is a null body status, then throw a TypeError. 114 if (Infrastructure::is_null_body_status(m_response->status())) 115 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Response with null body status cannot have a body"sv }; 116 117 // 2. Set response’s body to body’s body. 118 m_response->set_body(body->body); 119 120 // 3. If body’s type is non-null and response’s header list does not contain `Content-Type`, then append (`Content-Type`, body’s type) to response’s header list. 121 if (body->type.has_value() && m_response->header_list()->contains("Content-Type"sv.bytes())) { 122 auto header = Infrastructure::Header { 123 .name = MUST(ByteBuffer::copy("Content-Type"sv.bytes())), 124 .value = TRY_OR_THROW_OOM(vm, ByteBuffer::copy(body->type->span())), 125 }; 126 TRY_OR_THROW_OOM(vm, m_response->header_list()->append(move(header))); 127 } 128 } 129 130 return {}; 131} 132 133// https://fetch.spec.whatwg.org/#dom-response 134WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::construct_impl(JS::Realm& realm, Optional<BodyInit> const& body, ResponseInit const& init) 135{ 136 auto& vm = realm.vm(); 137 138 // Referred to as 'this' in the spec. 139 auto response_object = MUST_OR_THROW_OOM(realm.heap().allocate<Response>(realm, realm, Infrastructure::Response::create(vm))); 140 141 // 1. Set this’s response to a new response. 142 // NOTE: This is done at the beginning as the 'this' value Response object 143 // cannot exist with a null Infrastructure::Response. 144 145 // 2. Set this’s headers to a new Headers object with this’s relevant Realm, whose header list is this’s response’s header list and guard is "response". 146 response_object->m_headers = MUST_OR_THROW_OOM(realm.heap().allocate<Headers>(realm, realm, response_object->response()->header_list())); 147 response_object->m_headers->set_guard(Headers::Guard::Response); 148 149 // 3. Let bodyWithType be null. 150 Optional<Infrastructure::BodyWithType> body_with_type; 151 152 // 4. If body is non-null, then set bodyWithType to the result of extracting body. 153 if (body.has_value()) 154 body_with_type = TRY(extract_body(realm, *body)); 155 156 // 5. Perform initialize a response given this, init, and bodyWithType. 157 TRY(response_object->initialize_response(init, body_with_type)); 158 159 return response_object; 160} 161 162// https://fetch.spec.whatwg.org/#dom-response-error 163WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::error(JS::VM& vm) 164{ 165 // The static error() method steps are to return the result of creating a Response object, given a new network error, "immutable", and this’s relevant Realm. 166 // FIXME: How can we reliably get 'this', i.e. the object the function was called on, in IDL-defined functions? 167 return Response::create(*vm.current_realm(), Infrastructure::Response::network_error(vm, "Response created via `Response.error()`"sv), Headers::Guard::Immutable); 168} 169 170// https://fetch.spec.whatwg.org/#dom-response-redirect 171WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::redirect(JS::VM& vm, String const& url, u16 status) 172{ 173 auto& realm = *vm.current_realm(); 174 175 // 1. Let parsedURL be the result of parsing url with current settings object’s API base URL. 176 auto api_base_url = HTML::current_settings_object().api_base_url(); 177 auto parsed_url = URLParser::parse(url, &api_base_url); 178 179 // 2. If parsedURL is failure, then throw a TypeError. 180 if (!parsed_url.is_valid()) 181 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Redirect URL is not valid"sv }; 182 183 // 3. If status is not a redirect status, then throw a RangeError. 184 if (!Infrastructure::is_redirect_status(status)) 185 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Status must be one of 301, 302, 303, 307, or 308"sv }; 186 187 // 4. Let responseObject be the result of creating a Response object, given a new response, "immutable", and this’s relevant Realm. 188 // FIXME: How can we reliably get 'this', i.e. the object the function was called on, in IDL-defined functions? 189 auto response_object = TRY(Response::create(realm, Infrastructure::Response::create(vm), Headers::Guard::Immutable)); 190 191 // 5. Set responseObject’s response’s status to status. 192 response_object->response()->set_status(status); 193 194 // 6. Let value be parsedURL, serialized and isomorphic encoded. 195 auto value = parsed_url.serialize(); 196 197 // 7. Append (`Location`, value) to responseObject’s response’s header list. 198 auto header = TRY_OR_THROW_OOM(vm, Infrastructure::Header::from_string_pair("Location"sv, value)); 199 TRY_OR_THROW_OOM(vm, response_object->response()->header_list()->append(move(header))); 200 201 // 8. Return responseObject. 202 return response_object; 203} 204 205// https://fetch.spec.whatwg.org/#dom-response-json 206WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::json(JS::VM& vm, JS::Value data, ResponseInit const& init) 207{ 208 auto& realm = *vm.current_realm(); 209 210 // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data. 211 auto bytes = TRY(Infra::serialize_javascript_value_to_json_bytes(vm, data)); 212 213 // 2. Let body be the result of extracting bytes. 214 auto [body, _] = TRY(extract_body(realm, { bytes.bytes() })); 215 216 // 3. Let responseObject be the result of creating a Response object, given a new response, "response", and this’s relevant Realm. 217 // FIXME: How can we reliably get 'this', i.e. the object the function was called on, in IDL-defined functions? 218 auto response_object = TRY(Response::create(realm, Infrastructure::Response::create(vm), Headers::Guard::Response)); 219 220 // 4. Perform initialize a response given responseObject, init, and (body, "application/json"). 221 auto body_with_type = Infrastructure::BodyWithType { 222 .body = move(body), 223 .type = TRY_OR_THROW_OOM(vm, ByteBuffer::copy("application/json"sv.bytes())) 224 }; 225 TRY(response_object->initialize_response(init, move(body_with_type))); 226 227 // 5. Return responseObject. 228 return response_object; 229} 230 231// https://fetch.spec.whatwg.org/#dom-response-type 232Bindings::ResponseType Response::type() const 233{ 234 // The type getter steps are to return this’s response’s type. 235 return to_bindings_enum(m_response->type()); 236} 237 238// https://fetch.spec.whatwg.org/#dom-response-url 239WebIDL::ExceptionOr<String> Response::url() const 240{ 241 auto& vm = this->vm(); 242 243 // The url getter steps are to return the empty string if this’s response’s URL is null; otherwise this’s response’s URL, serialized with exclude fragment set to true. 244 return !m_response->url().has_value() 245 ? String {} 246 : TRY_OR_THROW_OOM(vm, String::from_deprecated_string(m_response->url()->serialize(AK::URL::ExcludeFragment::Yes))); 247} 248 249// https://fetch.spec.whatwg.org/#dom-response-redirected 250bool Response::redirected() const 251{ 252 // The redirected getter steps are to return true if this’s response’s URL list has more than one item; otherwise false. 253 return m_response->url_list().size() > 1; 254} 255 256// https://fetch.spec.whatwg.org/#dom-response-status 257u16 Response::status() const 258{ 259 // The status getter steps are to return this’s response’s status. 260 return m_response->status(); 261} 262 263// https://fetch.spec.whatwg.org/#dom-response-ok 264bool Response::ok() const 265{ 266 // The ok getter steps are to return true if this’s response’s status is an ok status; otherwise false. 267 return Infrastructure::is_ok_status(m_response->status()); 268} 269 270// https://fetch.spec.whatwg.org/#dom-response-statustext 271WebIDL::ExceptionOr<String> Response::status_text() const 272{ 273 auto& vm = this->vm(); 274 275 // The statusText getter steps are to return this’s response’s status message. 276 return TRY_OR_THROW_OOM(vm, String::from_utf8(m_response->status_message())); 277} 278 279// https://fetch.spec.whatwg.org/#dom-response-headers 280JS::NonnullGCPtr<Headers> Response::headers() const 281{ 282 // The headers getter steps are to return this’s headers. 283 return *m_headers; 284} 285 286// https://fetch.spec.whatwg.org/#dom-response-clone 287WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::clone() const 288{ 289 auto& realm = this->realm(); 290 291 // 1. If this is unusable, then throw a TypeError. 292 if (is_unusable()) 293 return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Response is unusable"sv }; 294 295 // 2. Let clonedResponse be the result of cloning this’s response. 296 auto cloned_response = TRY(m_response->clone(realm)); 297 298 // 3. Return the result of creating a Response object, given clonedResponse, this’s headers’s guard, and this’s relevant Realm. 299 return TRY(Response::create(HTML::relevant_realm(*this), cloned_response, m_headers->guard())); 300} 301 302}