Serenity Operating System
at master 365 lines 15 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/Array.h> 8#include <LibJS/Heap/Heap.h> 9#include <LibJS/Runtime/Realm.h> 10#include <LibWeb/Fetch/Fetching/PendingResponse.h> 11#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h> 12#include <LibWeb/URL/URL.h> 13 14namespace Web::Fetch::Infrastructure { 15 16Request::Request(JS::NonnullGCPtr<HeaderList> header_list) 17 : m_header_list(header_list) 18{ 19} 20 21void Request::visit_edges(JS::Cell::Visitor& visitor) 22{ 23 Base::visit_edges(visitor); 24 visitor.visit(m_header_list); 25 for (auto pending_response : m_pending_responses) 26 visitor.visit(pending_response); 27} 28 29JS::NonnullGCPtr<Request> Request::create(JS::VM& vm) 30{ 31 return vm.heap().allocate_without_realm<Request>(HeaderList::create(vm)); 32} 33 34// https://fetch.spec.whatwg.org/#concept-request-url 35AK::URL& Request::url() 36{ 37 // A request has an associated URL (a URL). 38 // NOTE: Implementations are encouraged to make this a pointer to the first URL in request’s URL list. It is provided as a distinct field solely for the convenience of other standards hooking into Fetch. 39 VERIFY(!m_url_list.is_empty()); 40 return m_url_list.first(); 41} 42 43// https://fetch.spec.whatwg.org/#concept-request-url 44AK::URL const& Request::url() const 45{ 46 return const_cast<Request&>(*this).url(); 47} 48 49// https://fetch.spec.whatwg.org/#concept-request-current-url 50AK::URL& Request::current_url() 51{ 52 // A request has an associated current URL. It is a pointer to the last URL in request’s URL list. 53 VERIFY(!m_url_list.is_empty()); 54 return m_url_list.last(); 55} 56 57// https://fetch.spec.whatwg.org/#concept-request-current-url 58AK::URL const& Request::current_url() const 59{ 60 return const_cast<Request&>(*this).current_url(); 61} 62 63void Request::set_url(AK::URL url) 64{ 65 // Sometimes setting the URL and URL list are done as two distinct steps in the spec, 66 // but since we know the URL is always the URL list's first item and doesn't change later 67 // on, we can combine them. 68 if (!m_url_list.is_empty()) 69 m_url_list.clear(); 70 m_url_list.append(move(url)); 71} 72 73// https://fetch.spec.whatwg.org/#request-destination-script-like 74bool Request::destination_is_script_like() const 75{ 76 // A request’s destination is script-like if it is "audioworklet", "paintworklet", "script", "serviceworker", "sharedworker", or "worker". 77 static constexpr Array script_like_destinations = { 78 Destination::AudioWorklet, 79 Destination::PaintWorklet, 80 Destination::Script, 81 Destination::ServiceWorker, 82 Destination::SharedWorker, 83 Destination::Worker, 84 }; 85 return any_of(script_like_destinations, [this](auto destination) { 86 return m_destination == destination; 87 }); 88} 89 90// https://fetch.spec.whatwg.org/#subresource-request 91bool Request::is_subresource_request() const 92{ 93 // A subresource request is a request whose destination is "audio", "audioworklet", "font", "image", "manifest", "paintworklet", "script", "style", "track", "video", "xslt", or the empty string. 94 static constexpr Array subresource_request_destinations = { 95 Destination::Audio, 96 Destination::AudioWorklet, 97 Destination::Font, 98 Destination::Image, 99 Destination::Manifest, 100 Destination::PaintWorklet, 101 Destination::Script, 102 Destination::Style, 103 Destination::Track, 104 Destination::Video, 105 Destination::XSLT, 106 }; 107 return any_of(subresource_request_destinations, [this](auto destination) { 108 return m_destination == destination; 109 }) || !m_destination.has_value(); 110} 111 112// https://fetch.spec.whatwg.org/#non-subresource-request 113bool Request::is_non_subresource_request() const 114{ 115 // A non-subresource request is a request whose destination is "document", "embed", "frame", "iframe", "object", "report", "serviceworker", "sharedworker", or "worker". 116 static constexpr Array non_subresource_request_destinations = { 117 Destination::Document, 118 Destination::Embed, 119 Destination::Frame, 120 Destination::IFrame, 121 Destination::Object, 122 Destination::Report, 123 Destination::ServiceWorker, 124 Destination::SharedWorker, 125 Destination::Worker, 126 }; 127 return any_of(non_subresource_request_destinations, [this](auto destination) { 128 return m_destination == destination; 129 }); 130} 131 132// https://fetch.spec.whatwg.org/#navigation-request 133bool Request::is_navigation_request() const 134{ 135 // A navigation request is a request whose destination is "document", "embed", "frame", "iframe", or "object". 136 static constexpr Array navigation_request_destinations = { 137 Destination::Document, 138 Destination::Embed, 139 Destination::Frame, 140 Destination::IFrame, 141 Destination::Object, 142 }; 143 return any_of(navigation_request_destinations, [this](auto destination) { 144 return m_destination == destination; 145 }); 146} 147 148// https://fetch.spec.whatwg.org/#concept-request-tainted-origin 149bool Request::has_redirect_tainted_origin() const 150{ 151 // A request request has a redirect-tainted origin if these steps return true: 152 153 // 1. Let lastURL be null. 154 Optional<AK::URL const&> last_url; 155 156 // 2. For each url of request’s URL list: 157 for (auto const& url : m_url_list) { 158 // 1. If lastURL is null, then set lastURL to url and continue. 159 if (!last_url.has_value()) { 160 last_url = url; 161 continue; 162 } 163 164 // 2. If url’s origin is not same origin with lastURL’s origin and request’s origin is not same origin with lastURL’s origin, then return true. 165 auto const* request_origin = m_origin.get_pointer<HTML::Origin>(); 166 if (!URL::url_origin(url).is_same_origin(URL::url_origin(*last_url)) 167 && (request_origin == nullptr || !request_origin->is_same_origin(URL::url_origin(*last_url)))) { 168 return true; 169 } 170 171 // 3. Set lastURL to url. 172 last_url = url; 173 } 174 175 // 3. Return false. 176 return false; 177} 178 179// https://fetch.spec.whatwg.org/#serializing-a-request-origin 180ErrorOr<String> Request::serialize_origin() const 181{ 182 // 1. If request has a redirect-tainted origin, then return "null". 183 if (has_redirect_tainted_origin()) 184 return "null"_string; 185 186 // 2. Return request’s origin, serialized. 187 return String::from_deprecated_string(m_origin.get<HTML::Origin>().serialize()); 188} 189 190// https://fetch.spec.whatwg.org/#byte-serializing-a-request-origin 191ErrorOr<ByteBuffer> Request::byte_serialize_origin() const 192{ 193 // Byte-serializing a request origin, given a request request, is to return the result of serializing a request origin with request, isomorphic encoded. 194 return ByteBuffer::copy(TRY(serialize_origin()).bytes()); 195} 196 197// https://fetch.spec.whatwg.org/#concept-request-clone 198WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::clone(JS::Realm& realm) const 199{ 200 // To clone a request request, run these steps: 201 auto& vm = realm.vm(); 202 203 // 1. Let newRequest be a copy of request, except for its body. 204 auto new_request = Infrastructure::Request::create(vm); 205 new_request->set_method(m_method); 206 new_request->set_local_urls_only(m_local_urls_only); 207 for (auto const& header : *m_header_list) 208 MUST(new_request->header_list()->append(header)); 209 new_request->set_unsafe_request(m_unsafe_request); 210 new_request->set_client(m_client); 211 new_request->set_reserved_client(m_reserved_client); 212 new_request->set_replaces_client_id(m_replaces_client_id); 213 new_request->set_window(m_window); 214 new_request->set_keepalive(m_keepalive); 215 new_request->set_initiator_type(m_initiator_type); 216 new_request->set_service_workers_mode(m_service_workers_mode); 217 new_request->set_initiator(m_initiator); 218 new_request->set_destination(m_destination); 219 new_request->set_priority(m_priority); 220 new_request->set_origin(m_origin); 221 new_request->set_policy_container(m_policy_container); 222 new_request->set_referrer(m_referrer); 223 new_request->set_referrer_policy(m_referrer_policy); 224 new_request->set_mode(m_mode); 225 new_request->set_use_cors_preflight(m_use_cors_preflight); 226 new_request->set_credentials_mode(m_credentials_mode); 227 new_request->set_use_url_credentials(m_use_url_credentials); 228 new_request->set_cache_mode(m_cache_mode); 229 new_request->set_redirect_mode(m_redirect_mode); 230 new_request->set_integrity_metadata(m_integrity_metadata); 231 new_request->set_cryptographic_nonce_metadata(m_cryptographic_nonce_metadata); 232 new_request->set_parser_metadata(m_parser_metadata); 233 new_request->set_reload_navigation(m_reload_navigation); 234 new_request->set_history_navigation(m_history_navigation); 235 new_request->set_user_activation(m_user_activation); 236 new_request->set_render_blocking(m_render_blocking); 237 new_request->set_url_list(m_url_list); 238 new_request->set_redirect_count(m_redirect_count); 239 new_request->set_response_tainting(m_response_tainting); 240 new_request->set_prevent_no_cache_cache_control_header_modification(m_prevent_no_cache_cache_control_header_modification); 241 new_request->set_done(m_done); 242 new_request->set_timing_allow_failed(m_timing_allow_failed); 243 244 // 2. If request’s body is non-null, set newRequest’s body to the result of cloning request’s body. 245 if (auto const* body = m_body.get_pointer<Body>()) 246 new_request->set_body(TRY(body->clone(realm))); 247 248 // 3. Return newRequest. 249 return new_request; 250} 251 252// https://fetch.spec.whatwg.org/#concept-request-add-range-header 253ErrorOr<void> Request::add_range_header(u64 first, Optional<u64> const& last) 254{ 255 // To add a range header to a request request, with an integer first, and an optional integer last, run these steps: 256 257 // 1. Assert: last is not given, or first is less than or equal to last. 258 VERIFY(!last.has_value() || first <= last.value()); 259 260 // 2. Let rangeValue be `bytes=`. 261 auto range_value = MUST(ByteBuffer::copy("bytes"sv.bytes())); 262 263 // 3. Serialize and isomorphic encode first, and append the result to rangeValue. 264 TRY(range_value.try_append(TRY(String::number(first)).bytes())); 265 266 // 4. Append 0x2D (-) to rangeValue. 267 TRY(range_value.try_append('-')); 268 269 // 5. If last is given, then serialize and isomorphic encode it, and append the result to rangeValue. 270 if (last.has_value()) 271 TRY(range_value.try_append(TRY(String::number(*last)).bytes())); 272 273 // 6. Append (`Range`, rangeValue) to request’s header list. 274 auto header = Header { 275 .name = MUST(ByteBuffer::copy("Range"sv.bytes())), 276 .value = move(range_value), 277 }; 278 TRY(m_header_list->append(move(header))); 279 280 return {}; 281} 282 283// https://fetch.spec.whatwg.org/#append-a-request-origin-header 284ErrorOr<void> Request::add_origin_header() 285{ 286 // 1. Let serializedOrigin be the result of byte-serializing a request origin with request. 287 auto serialized_origin = TRY(byte_serialize_origin()); 288 289 // 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list. 290 if (m_response_tainting == ResponseTainting::CORS || m_mode == Mode::WebSocket) { 291 auto header = Header { 292 .name = MUST(ByteBuffer::copy("Origin"sv.bytes())), 293 .value = move(serialized_origin), 294 }; 295 TRY(m_header_list->append(move(header))); 296 } 297 // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then: 298 else if (!StringView { m_method }.is_one_of("GET"sv, "HEAD"sv)) { 299 // 1. If request’s mode is not "cors", then switch on request’s referrer policy: 300 if (m_mode != Mode::CORS && m_referrer_policy.has_value()) { 301 switch (*m_referrer_policy) { 302 // -> "no-referrer" 303 case ReferrerPolicy::ReferrerPolicy::NoReferrer: 304 // Set serializedOrigin to `null`. 305 serialized_origin = MUST(ByteBuffer::copy("null"sv.bytes())); 306 break; 307 // -> "no-referrer-when-downgrade" 308 // -> "strict-origin" 309 // -> "strict-origin-when-cross-origin" 310 case ReferrerPolicy::ReferrerPolicy::NoReferrerWhenDowngrade: 311 case ReferrerPolicy::ReferrerPolicy::StrictOrigin: 312 case ReferrerPolicy::ReferrerPolicy::StrictOriginWhenCrossOrigin: 313 // If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is 314 // not "https", then set serializedOrigin to `null`. 315 if (m_origin.has<HTML::Origin>() && m_origin.get<HTML::Origin>().scheme() == "https"sv && current_url().scheme() != "https"sv) 316 serialized_origin = MUST(ByteBuffer::copy("null"sv.bytes())); 317 break; 318 // -> "same-origin" 319 case ReferrerPolicy::ReferrerPolicy::SameOrigin: 320 // If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin 321 // to `null`. 322 if (m_origin.has<HTML::Origin>() && !m_origin.get<HTML::Origin>().is_same_origin(URL::url_origin(current_url()))) 323 serialized_origin = MUST(ByteBuffer::copy("null"sv.bytes())); 324 break; 325 // -> Otherwise 326 default: 327 // Do nothing. 328 break; 329 } 330 } 331 332 // 2. Append (`Origin`, serializedOrigin) to request’s header list. 333 auto header = Header { 334 .name = MUST(ByteBuffer::copy("Origin"sv.bytes())), 335 .value = move(serialized_origin), 336 }; 337 TRY(m_header_list->append(move(header))); 338 } 339 340 return {}; 341} 342 343// https://fetch.spec.whatwg.org/#cross-origin-embedder-policy-allows-credentials 344bool Request::cross_origin_embedder_policy_allows_credentials() const 345{ 346 // 1. If request’s mode is not "no-cors", then return true. 347 if (m_mode != Mode::NoCORS) 348 return true; 349 350 // 2. If request’s client is null, then return true. 351 if (m_client == nullptr) 352 return true; 353 354 // FIXME: 3. If request’s client’s policy container’s embedder policy’s value is not "credentialless", then return true. 355 356 // 4. If request’s origin is same origin with request’s current URL’s origin and request does not have a redirect-tainted origin, then return true. 357 // FIXME: Actually use the given origins once we have https://url.spec.whatwg.org/#concept-url-origin. 358 if (HTML::Origin().is_same_origin(HTML::Origin()) && !has_redirect_tainted_origin()) 359 return true; 360 361 // 5. Return false. 362 return false; 363} 364 365}