/src/serenity/Userland/Libraries/LibWeb/Fetch/Request.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/Completion.h> |
8 | | #include <LibWeb/Bindings/Intrinsics.h> |
9 | | #include <LibWeb/Bindings/RequestPrototype.h> |
10 | | #include <LibWeb/DOM/AbortSignal.h> |
11 | | #include <LibWeb/DOMURL/DOMURL.h> |
12 | | #include <LibWeb/Fetch/Enums.h> |
13 | | #include <LibWeb/Fetch/Headers.h> |
14 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h> |
15 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h> |
16 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Methods.h> |
17 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h> |
18 | | #include <LibWeb/Fetch/Request.h> |
19 | | #include <LibWeb/HTML/Scripting/Environments.h> |
20 | | #include <LibWeb/ReferrerPolicy/ReferrerPolicy.h> |
21 | | |
22 | | namespace Web::Fetch { |
23 | | |
24 | | JS_DEFINE_ALLOCATOR(Request); |
25 | | |
26 | | Request::Request(JS::Realm& realm, JS::NonnullGCPtr<Infrastructure::Request> request) |
27 | 0 | : PlatformObject(realm) |
28 | 0 | , m_request(request) |
29 | 0 | { |
30 | 0 | } |
31 | | |
32 | 0 | Request::~Request() = default; |
33 | | |
34 | | void Request::initialize(JS::Realm& realm) |
35 | 0 | { |
36 | 0 | Base::initialize(realm); |
37 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(Request); |
38 | 0 | } |
39 | | |
40 | | void Request::visit_edges(Cell::Visitor& visitor) |
41 | 0 | { |
42 | 0 | Base::visit_edges(visitor); |
43 | 0 | visitor.visit(m_request); |
44 | 0 | visitor.visit(m_headers); |
45 | 0 | visitor.visit(m_signal); |
46 | 0 | } |
47 | | |
48 | | // https://fetch.spec.whatwg.org/#concept-body-mime-type |
49 | | // https://fetch.spec.whatwg.org/#ref-for-concept-body-mime-type%E2%91%A0 |
50 | | Optional<MimeSniff::MimeType> Request::mime_type_impl() const |
51 | 0 | { |
52 | | // 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. |
53 | | // A Request object’s MIME type is to return the result of extracting a MIME type from its request’s header list. |
54 | 0 | return m_request->header_list()->extract_mime_type(); |
55 | 0 | } |
56 | | |
57 | | // https://fetch.spec.whatwg.org/#concept-body-body |
58 | | // https://fetch.spec.whatwg.org/#ref-for-concept-body-body%E2%91%A7 |
59 | | JS::GCPtr<Infrastructure::Body const> Request::body_impl() const |
60 | 0 | { |
61 | | // Objects including the Body interface mixin have an associated body (null or a body). |
62 | | // A Request object’s body is its request’s body. |
63 | 0 | return m_request->body().visit( |
64 | 0 | [](JS::NonnullGCPtr<Infrastructure::Body> const& b) -> JS::GCPtr<Infrastructure::Body const> { return b; }, |
65 | 0 | [](Empty) -> JS::GCPtr<Infrastructure::Body const> { return nullptr; }, |
66 | | // A byte sequence will be safely extracted into a body early on in fetch. |
67 | 0 | [](ByteBuffer const&) -> JS::GCPtr<Infrastructure::Body const> { VERIFY_NOT_REACHED(); }); |
68 | 0 | } |
69 | | |
70 | | // https://fetch.spec.whatwg.org/#concept-body-body |
71 | | // https://fetch.spec.whatwg.org/#ref-for-concept-body-body%E2%91%A7 |
72 | | JS::GCPtr<Infrastructure::Body> Request::body_impl() |
73 | 0 | { |
74 | | // Objects including the Body interface mixin have an associated body (null or a body). |
75 | | // A Request object’s body is its request’s body. |
76 | 0 | return m_request->body().visit( |
77 | 0 | [](JS::NonnullGCPtr<Infrastructure::Body>& b) -> JS::GCPtr<Infrastructure::Body> { return b; }, |
78 | 0 | [](Empty) -> JS::GCPtr<Infrastructure::Body> { return {}; }, |
79 | | // A byte sequence will be safely extracted into a body early on in fetch. |
80 | 0 | [](ByteBuffer&) -> JS::GCPtr<Infrastructure::Body> { VERIFY_NOT_REACHED(); }); |
81 | 0 | } |
82 | | |
83 | | // https://fetch.spec.whatwg.org/#request-create |
84 | | JS::NonnullGCPtr<Request> Request::create(JS::Realm& realm, JS::NonnullGCPtr<Infrastructure::Request> request, Headers::Guard guard, JS::NonnullGCPtr<DOM::AbortSignal> signal) |
85 | 0 | { |
86 | | // 1. Let requestObject be a new Request object with realm. |
87 | | // 2. Set requestObject’s request to request. |
88 | 0 | auto request_object = realm.heap().allocate<Request>(realm, realm, request); |
89 | | |
90 | | // 3. Set requestObject’s headers to a new Headers object with realm, whose headers list is request’s headers list and guard is guard. |
91 | 0 | request_object->m_headers = realm.heap().allocate<Headers>(realm, realm, request->header_list()); |
92 | 0 | request_object->m_headers->set_guard(guard); |
93 | | |
94 | | // 4. Set requestObject’s signal to signal. |
95 | 0 | request_object->m_signal = signal; |
96 | | |
97 | | // 5. Return requestObject. |
98 | 0 | return request_object; |
99 | 0 | } |
100 | | |
101 | | // https://fetch.spec.whatwg.org/#dom-request |
102 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::construct_impl(JS::Realm& realm, RequestInfo const& input, RequestInit const& init) |
103 | 0 | { |
104 | 0 | auto& vm = realm.vm(); |
105 | | |
106 | | // Referred to as 'this' in the spec. |
107 | 0 | auto request_object = realm.heap().allocate<Request>(realm, realm, Infrastructure::Request::create(vm)); |
108 | | |
109 | | // 1. Let request be null. |
110 | 0 | JS::GCPtr<Infrastructure::Request> input_request; |
111 | | |
112 | | // 2. Let fallbackMode be null. |
113 | 0 | Optional<Infrastructure::Request::Mode> fallback_mode; |
114 | | |
115 | | // 3. Let baseURL be this’s relevant settings object’s API base URL. |
116 | 0 | auto base_url = HTML::relevant_settings_object(*request_object).api_base_url(); |
117 | | |
118 | | // 4. Let signal be null. |
119 | 0 | DOM::AbortSignal* input_signal = nullptr; |
120 | | |
121 | | // 5. If input is a string, then: |
122 | 0 | if (input.has<String>()) { |
123 | | // 1. Let parsedURL be the result of parsing input with baseURL. |
124 | 0 | auto parsed_url = DOMURL::parse(input.get<String>(), base_url); |
125 | | |
126 | | // 2. If parsedURL is failure, then throw a TypeError. |
127 | 0 | if (!parsed_url.is_valid()) |
128 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Input URL is not valid"sv }; |
129 | | |
130 | | // 3. If parsedURL includes credentials, then throw a TypeError. |
131 | 0 | if (parsed_url.includes_credentials()) |
132 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Input URL must not include credentials"sv }; |
133 | | |
134 | | // 4. Set request to a new request whose URL is parsedURL. |
135 | 0 | input_request = Infrastructure::Request::create(vm); |
136 | 0 | input_request->set_url(move(parsed_url)); |
137 | | |
138 | | // 5. Set fallbackMode to "cors". |
139 | 0 | fallback_mode = Infrastructure::Request::Mode::CORS; |
140 | 0 | } |
141 | | // 6. Otherwise: |
142 | 0 | else { |
143 | | // 1. Assert: input is a Request object. |
144 | 0 | VERIFY(input.has<JS::Handle<Request>>()); |
145 | | |
146 | | // 2. Set request to input’s request. |
147 | 0 | input_request = input.get<JS::Handle<Request>>()->request(); |
148 | | |
149 | | // 3. Set signal to input’s signal. |
150 | 0 | input_signal = input.get<JS::Handle<Request>>()->signal(); |
151 | 0 | } |
152 | | |
153 | | // 7. Let origin be this’s relevant settings object’s origin. |
154 | 0 | auto const& origin = HTML::relevant_settings_object(*request_object).origin(); |
155 | | |
156 | | // 8. Let window be "client". |
157 | 0 | auto window = Infrastructure::Request::WindowType { Infrastructure::Request::Window::Client }; |
158 | | |
159 | | // 9. If request’s window is an environment settings object and its origin is same origin with origin, then set window to request’s window. |
160 | 0 | if (input_request->window().has<JS::GCPtr<HTML::EnvironmentSettingsObject>>()) { |
161 | 0 | auto eso = input_request->window().get<JS::GCPtr<HTML::EnvironmentSettingsObject>>(); |
162 | 0 | if (eso->origin().is_same_origin(origin)) |
163 | 0 | window = input_request->window(); |
164 | 0 | } |
165 | | |
166 | | // 10. If init["window"] exists and is non-null, then throw a TypeError. |
167 | 0 | if (init.window.has_value() && !init.window->is_null()) |
168 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "The 'window' property must be omitted or null"sv }; |
169 | | |
170 | | // 11. If init["window"] exists, then set window to "no-window". |
171 | 0 | if (init.window.has_value()) |
172 | 0 | window = Infrastructure::Request::Window::NoWindow; |
173 | | |
174 | | // 12. Set request to a new request with the following properties: |
175 | | // NOTE: This is done at the beginning as the 'this' value Request object |
176 | | // cannot exist with a null Infrastructure::Request. |
177 | 0 | auto request = request_object->request(); |
178 | | |
179 | | // URL |
180 | | // request’s URL. |
181 | 0 | request->set_url(input_request->url()); |
182 | | |
183 | | // method |
184 | | // request’s method. |
185 | 0 | request->set_method(MUST(ByteBuffer::copy(input_request->method()))); |
186 | | |
187 | | // header list |
188 | | // A copy of request’s header list. |
189 | 0 | auto header_list_copy = Infrastructure::HeaderList::create(vm); |
190 | 0 | for (auto& header : *input_request->header_list()) |
191 | 0 | header_list_copy->append(header); |
192 | 0 | request->set_header_list(header_list_copy); |
193 | | |
194 | | // unsafe-request flag |
195 | | // Set. |
196 | 0 | request->set_unsafe_request(true); |
197 | | |
198 | | // client |
199 | | // This’s relevant settings object. |
200 | 0 | request->set_client(&HTML::relevant_settings_object(*request_object)); |
201 | | |
202 | | // window |
203 | | // window. |
204 | 0 | request->set_window(window); |
205 | | |
206 | | // priority |
207 | | // request’s priority. |
208 | 0 | request->set_priority(input_request->priority()); |
209 | | |
210 | | // origin |
211 | | // request’s origin. The propagation of the origin is only significant for navigation requests being handled by a service worker. In this scenario a request can have an origin that is different from the current client. |
212 | 0 | request->set_origin(input_request->origin()); |
213 | | |
214 | | // referrer |
215 | | // request’s referrer. |
216 | 0 | request->set_referrer(input_request->referrer()); |
217 | | |
218 | | // referrer policy |
219 | | // request’s referrer policy. |
220 | 0 | request->set_referrer_policy(input_request->referrer_policy()); |
221 | | |
222 | | // mode |
223 | | // request’s mode. |
224 | 0 | request->set_mode(input_request->mode()); |
225 | | |
226 | | // credentials mode |
227 | | // request’s credentials mode. |
228 | 0 | request->set_credentials_mode(input_request->credentials_mode()); |
229 | | |
230 | | // cache mode |
231 | | // request’s cache mode. |
232 | 0 | request->set_cache_mode(input_request->cache_mode()); |
233 | | |
234 | | // redirect mode |
235 | | // request’s redirect mode. |
236 | 0 | request->set_redirect_mode(input_request->redirect_mode()); |
237 | | |
238 | | // integrity metadata |
239 | | // request’s integrity metadata. |
240 | 0 | request->set_integrity_metadata(input_request->integrity_metadata()); |
241 | | |
242 | | // keepalive |
243 | | // request’s keepalive. |
244 | 0 | request->set_keepalive(input_request->keepalive()); |
245 | | |
246 | | // reload-navigation flag |
247 | | // request’s reload-navigation flag. |
248 | 0 | request->set_reload_navigation(input_request->reload_navigation()); |
249 | | |
250 | | // history-navigation flag |
251 | | // request’s history-navigation flag. |
252 | 0 | request->set_history_navigation(input_request->history_navigation()); |
253 | | |
254 | | // URL list |
255 | | // A clone of request’s URL list. |
256 | 0 | request->set_url_list(input_request->url_list()); |
257 | | |
258 | | // initiator type |
259 | | // "fetch". |
260 | 0 | request->set_initiator_type(Infrastructure::Request::InitiatorType::Fetch); |
261 | | |
262 | | // 13. If init is not empty, then: |
263 | 0 | if (!init.is_empty()) { |
264 | | // 1. If request’s mode is "navigate", then set it to "same-origin". |
265 | 0 | if (request->mode() == Infrastructure::Request::Mode::Navigate) |
266 | 0 | request->set_mode(Infrastructure::Request::Mode::SameOrigin); |
267 | | |
268 | | // 2. Unset request’s reload-navigation flag. |
269 | 0 | request->set_reload_navigation(false); |
270 | | |
271 | | // 3. Unset request’s history-navigation flag. |
272 | 0 | request->set_history_navigation(false); |
273 | | |
274 | | // 4. Set request’s origin to "client". |
275 | 0 | request->set_origin(Infrastructure::Request::Origin::Client); |
276 | | |
277 | | // 5. Set request’s referrer to "client". |
278 | 0 | request->set_referrer(Infrastructure::Request::Referrer::Client); |
279 | | |
280 | | // 6. Set request’s referrer policy to the empty string. |
281 | 0 | request->set_referrer_policy({}); |
282 | | |
283 | | // 7. Set request’s URL to request’s current URL. |
284 | 0 | request->set_url(request->current_url()); |
285 | | |
286 | | // 8. Set request’s URL list to « request’s URL ». |
287 | | // NOTE: This is done implicitly by assigning the initial URL above. |
288 | 0 | } |
289 | | |
290 | | // 14. If init["referrer"] exists, then: |
291 | 0 | if (init.referrer.has_value()) { |
292 | | // 1. Let referrer be init["referrer"]. |
293 | 0 | auto const& referrer = *init.referrer; |
294 | | |
295 | | // 2. If referrer is the empty string, then set request’s referrer to "no-referrer". |
296 | 0 | if (referrer.is_empty()) { |
297 | 0 | request->set_referrer(Infrastructure::Request::Referrer::NoReferrer); |
298 | 0 | } |
299 | | // 3. Otherwise: |
300 | 0 | else { |
301 | | // 1. Let parsedReferrer be the result of parsing referrer with baseURL. |
302 | 0 | auto parsed_referrer = DOMURL::parse(referrer, base_url); |
303 | | |
304 | | // 2. If parsedReferrer is failure, then throw a TypeError. |
305 | 0 | if (!parsed_referrer.is_valid()) |
306 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Referrer must be a valid URL"sv }; |
307 | | |
308 | | // 3. If one of the following is true |
309 | | // - parsedReferrer’s scheme is "about" and path is the string "client" |
310 | | // - parsedReferrer’s origin is not same origin with origin |
311 | | // then set request’s referrer to "client". |
312 | 0 | auto parsed_referrer_origin = parsed_referrer.origin(); |
313 | 0 | if ((parsed_referrer.scheme() == "about"sv && parsed_referrer.paths().size() == 1 && parsed_referrer.paths()[0] == "client"sv) |
314 | 0 | || !parsed_referrer_origin.is_same_origin(origin)) { |
315 | 0 | request->set_referrer(Infrastructure::Request::Referrer::Client); |
316 | 0 | } |
317 | | // 4. Otherwise, set request’s referrer to parsedReferrer. |
318 | 0 | else { |
319 | 0 | request->set_referrer(move(parsed_referrer)); |
320 | 0 | } |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | // 15. If init["referrerPolicy"] exists, then set request’s referrer policy to it. |
325 | 0 | if (init.referrer_policy.has_value()) |
326 | 0 | request->set_referrer_policy(from_bindings_enum(*init.referrer_policy)); |
327 | | |
328 | | // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise. |
329 | 0 | auto mode = init.mode.has_value() |
330 | 0 | ? from_bindings_enum(*init.mode) |
331 | 0 | : fallback_mode; |
332 | | |
333 | | // 17. If mode is "navigate", then throw a TypeError. |
334 | 0 | if (mode == Infrastructure::Request::Mode::Navigate) |
335 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Mode must not be 'navigate"sv }; |
336 | | |
337 | | // 18. If mode is non-null, set request’s mode to mode. |
338 | 0 | if (mode.has_value()) |
339 | 0 | request->set_mode(*mode); |
340 | | |
341 | | // 19. If init["credentials"] exists, then set request’s credentials mode to it. |
342 | 0 | if (init.credentials.has_value()) |
343 | 0 | request->set_credentials_mode(from_bindings_enum(*init.credentials)); |
344 | | |
345 | | // 20. If init["cache"] exists, then set request’s cache mode to it. |
346 | 0 | if (init.cache.has_value()) |
347 | 0 | request->set_cache_mode(from_bindings_enum(*init.cache)); |
348 | | |
349 | | // 21. If request’s cache mode is "only-if-cached" and request’s mode is not "same-origin", then throw a TypeError. |
350 | 0 | if (request->cache_mode() == Infrastructure::Request::CacheMode::OnlyIfCached && request->mode() != Infrastructure::Request::Mode::SameOrigin) |
351 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Mode must be 'same-origin' when cache mode is 'only-if-cached'"sv }; |
352 | | |
353 | | // 22. If init["redirect"] exists, then set request’s redirect mode to it. |
354 | 0 | if (init.redirect.has_value()) |
355 | 0 | request->set_redirect_mode(from_bindings_enum(*init.redirect)); |
356 | | |
357 | | // 23. If init["integrity"] exists, then set request’s integrity metadata to it. |
358 | 0 | if (init.integrity.has_value()) |
359 | 0 | request->set_integrity_metadata(*init.integrity); |
360 | | |
361 | | // 24. If init["keepalive"] exists, then set request’s keepalive to it. |
362 | 0 | if (init.keepalive.has_value()) |
363 | 0 | request->set_keepalive(*init.keepalive); |
364 | | |
365 | | // 25. If init["method"] exists, then: |
366 | 0 | if (init.method.has_value()) { |
367 | | // 1. Let method be init["method"]. |
368 | 0 | auto method = *init.method; |
369 | | |
370 | | // 2. If method is not a method or method is a forbidden method, then throw a TypeError. |
371 | 0 | if (!Infrastructure::is_method(method.bytes())) |
372 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method has invalid value"sv }; |
373 | 0 | if (Infrastructure::is_forbidden_method(method.bytes())) |
374 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method must not be one of CONNECT, TRACE, or TRACK"sv }; |
375 | | |
376 | | // 3. Normalize method. |
377 | 0 | method = MUST(String::from_utf8(Infrastructure::normalize_method(method.bytes()))); |
378 | | |
379 | | // 4. Set request’s method to method. |
380 | 0 | request->set_method(MUST(ByteBuffer::copy(method.bytes()))); |
381 | 0 | } |
382 | | |
383 | | // 26. If init["signal"] exists, then set signal to it. |
384 | 0 | if (init.signal.has_value()) |
385 | 0 | input_signal = *init.signal; |
386 | | |
387 | | // 27. If init["priority"] exists, then: |
388 | 0 | if (init.priority.has_value()) |
389 | 0 | request->set_priority(from_bindings_enum(*init.priority)); |
390 | | |
391 | | // 28. Set this’s request to request. |
392 | | // NOTE: This is done at the beginning as the 'this' value Request object |
393 | | // cannot exist with a null Infrastructure::Request. |
394 | | |
395 | | // 29. Let signals be « signal » if signal is non-null; otherwise « ». |
396 | 0 | auto& this_relevant_realm = HTML::relevant_realm(*request_object); |
397 | 0 | Vector<JS::Handle<DOM::AbortSignal>> signals; |
398 | 0 | if (input_signal != nullptr) |
399 | 0 | signals.append(*input_signal); |
400 | | |
401 | | // 30. Set this’s signal to the result of creating a dependent abort signal from signals, using AbortSignal and this’s relevant realm. |
402 | 0 | request_object->m_signal = TRY(DOM::AbortSignal::create_dependent_abort_signal(this_relevant_realm, signals)); |
403 | | |
404 | | // 31. Set this’s headers to a new Headers object with this’s relevant Realm, whose header list is request’s header list and guard is "request". |
405 | 0 | request_object->m_headers = realm.heap().allocate<Headers>(realm, realm, request->header_list()); |
406 | 0 | request_object->m_headers->set_guard(Headers::Guard::Request); |
407 | | |
408 | | // 32. If this’s request’s mode is "no-cors", then: |
409 | 0 | if (request_object->request()->mode() == Infrastructure::Request::Mode::NoCORS) { |
410 | | // 1. If this’s request’s method is not a CORS-safelisted method, then throw a TypeError. |
411 | 0 | if (!Infrastructure::is_cors_safelisted_method(request_object->request()->method())) |
412 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method must be one of GET, HEAD, or POST"sv }; |
413 | | |
414 | | // 2. Set this’s headers’s guard to "request-no-cors". |
415 | 0 | request_object->headers()->set_guard(Headers::Guard::RequestNoCORS); |
416 | 0 | } |
417 | | |
418 | | // 33. If init is not empty, then: |
419 | 0 | if (!init.is_empty()) { |
420 | | // 1. Let headers be a copy of this’s headers and its associated header list. |
421 | 0 | auto headers = Variant<HeadersInit, JS::NonnullGCPtr<Infrastructure::HeaderList>> { request_object->headers()->header_list() }; |
422 | | |
423 | | // 2. If init["headers"] exists, then set headers to init["headers"]. |
424 | 0 | if (init.headers.has_value()) |
425 | 0 | headers = *init.headers; |
426 | | |
427 | | // 3. Empty this’s headers’s header list. |
428 | 0 | request_object->headers()->header_list()->clear(); |
429 | | |
430 | | // 4. If headers is a Headers object, then for each header of its header list, append header to this’s headers. |
431 | 0 | if (auto* header_list = headers.get_pointer<JS::NonnullGCPtr<Infrastructure::HeaderList>>()) { |
432 | 0 | for (auto& header : *header_list->ptr()) |
433 | 0 | TRY(request_object->headers()->append(Infrastructure::Header::from_string_pair(header.name, header.value))); |
434 | 0 | } |
435 | | // 5. Otherwise, fill this’s headers with headers. |
436 | 0 | else { |
437 | 0 | TRY(request_object->headers()->fill(headers.get<HeadersInit>())); |
438 | 0 | } |
439 | 0 | } |
440 | | |
441 | | // 34. Let inputBody be input’s request’s body if input is a Request object; otherwise null. |
442 | 0 | Optional<Infrastructure::Request::BodyType const&> input_body; |
443 | 0 | if (input.has<JS::Handle<Request>>()) |
444 | 0 | input_body = input.get<JS::Handle<Request>>()->request()->body(); |
445 | | |
446 | | // 35. If either init["body"] exists and is non-null or inputBody is non-null, and request’s method is `GET` or `HEAD`, then throw a TypeError. |
447 | 0 | if (((init.body.has_value() && (*init.body).has_value()) || (input_body.has_value() && !input_body.value().has<Empty>())) && StringView { request->method() }.is_one_of("GET"sv, "HEAD"sv)) |
448 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method must not be GET or HEAD when body is provided"sv }; |
449 | | |
450 | | // 36. Let initBody be null. |
451 | 0 | JS::GCPtr<Infrastructure::Body> init_body; |
452 | | |
453 | | // 37. If init["body"] exists and is non-null, then: |
454 | 0 | if (init.body.has_value() && (*init.body).has_value()) { |
455 | | // 1. Let bodyWithType be the result of extracting init["body"], with keepalive set to request’s keepalive. |
456 | 0 | auto body_with_type = TRY(extract_body(realm, (*init.body).value(), request->keepalive())); |
457 | | |
458 | | // 2. Set initBody to bodyWithType’s body. |
459 | 0 | init_body = body_with_type.body; |
460 | | |
461 | | // 3. Let type be bodyWithType’s type. |
462 | 0 | auto const& type = body_with_type.type; |
463 | | |
464 | | // 4. If type is non-null and this’s headers’s header list does not contain `Content-Type`, then append (`Content-Type`, type) to this’s headers. |
465 | 0 | if (type.has_value() && !request_object->headers()->header_list()->contains("Content-Type"sv.bytes())) |
466 | 0 | TRY(request_object->headers()->append(Infrastructure::Header::from_string_pair("Content-Type"sv, type->span()))); |
467 | 0 | } |
468 | | |
469 | | // 38. Let inputOrInitBody be initBody if it is non-null; otherwise inputBody. |
470 | 0 | Optional<Infrastructure::Request::BodyType> input_or_init_body = init_body |
471 | 0 | ? Infrastructure::Request::BodyType { *init_body } |
472 | 0 | : input_body.copy(); |
473 | | |
474 | | // 39. If inputOrInitBody is non-null and inputOrInitBody’s source is null, then: |
475 | | // FIXME: The spec doesn't check if inputOrInitBody is a body before accessing source. |
476 | 0 | if (input_or_init_body.has_value() && input_or_init_body->has<JS::NonnullGCPtr<Infrastructure::Body>>() && input_or_init_body->get<JS::NonnullGCPtr<Infrastructure::Body>>()->source().has<Empty>()) { |
477 | | // 1. If initBody is non-null and init["duplex"] does not exist, then throw a TypeError. |
478 | 0 | if (init_body && !init.duplex.has_value()) |
479 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Body without source requires 'duplex' value to be set"sv }; |
480 | | |
481 | | // 2. If this’s request’s mode is neither "same-origin" nor "cors", then throw a TypeError. |
482 | 0 | if (request_object->request()->mode() != Infrastructure::Request::Mode::SameOrigin && request_object->request()->mode() != Infrastructure::Request::Mode::CORS) |
483 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Request mode must be 'same-origin' or 'cors'"sv }; |
484 | | |
485 | | // 3. Set this’s request’s use-CORS-preflight flag. |
486 | 0 | request_object->request()->set_use_cors_preflight(true); |
487 | 0 | } |
488 | | |
489 | | // 40. Let finalBody be inputOrInitBody. |
490 | 0 | auto const& final_body = input_or_init_body; |
491 | | |
492 | | // 41. If initBody is null and inputBody is non-null, then: |
493 | 0 | if (!init_body && input_body.has_value()) { |
494 | | // 2. If input is unusable, then throw a TypeError. |
495 | 0 | if (input.has<JS::Handle<Request>>() && input.get<JS::Handle<Request>>()->is_unusable()) |
496 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Request is unusable"sv }; |
497 | | |
498 | | // FIXME: 2. Set finalBody to the result of creating a proxy for inputBody. |
499 | 0 | } |
500 | | |
501 | | // 42. Set this’s request’s body to finalBody. |
502 | 0 | if (final_body.has_value()) |
503 | 0 | request_object->request()->set_body(*final_body); |
504 | |
|
505 | 0 | return JS::NonnullGCPtr { *request_object }; |
506 | 0 | } |
507 | | |
508 | | // https://fetch.spec.whatwg.org/#dom-request-method |
509 | | String Request::method() const |
510 | 0 | { |
511 | | // The method getter steps are to return this’s request’s method. |
512 | 0 | return MUST(String::from_utf8(m_request->method())); |
513 | 0 | } |
514 | | |
515 | | // https://fetch.spec.whatwg.org/#dom-request-url |
516 | | String Request::url() const |
517 | 0 | { |
518 | | // The url getter steps are to return this’s request’s URL, serialized. |
519 | 0 | return MUST(String::from_byte_string(m_request->url().serialize())); |
520 | 0 | } |
521 | | |
522 | | // https://fetch.spec.whatwg.org/#dom-request-headers |
523 | | JS::NonnullGCPtr<Headers> Request::headers() const |
524 | 0 | { |
525 | | // The headers getter steps are to return this’s headers. |
526 | 0 | return *m_headers; |
527 | 0 | } |
528 | | |
529 | | // https://fetch.spec.whatwg.org/#dom-request-destination |
530 | | Bindings::RequestDestination Request::destination() const |
531 | 0 | { |
532 | | // The destination getter are to return this’s request’s destination. |
533 | 0 | return to_bindings_enum(m_request->destination()); |
534 | 0 | } |
535 | | |
536 | | // https://fetch.spec.whatwg.org/#dom-request-referrer |
537 | | String Request::referrer() const |
538 | 0 | { |
539 | 0 | return m_request->referrer().visit( |
540 | 0 | [&](Infrastructure::Request::Referrer const& referrer) { |
541 | 0 | switch (referrer) { |
542 | | // 1. If this’s request’s referrer is "no-referrer", then return the empty string. |
543 | 0 | case Infrastructure::Request::Referrer::NoReferrer: |
544 | 0 | return String {}; |
545 | | // 2. If this’s request’s referrer is "client", then return "about:client". |
546 | 0 | case Infrastructure::Request::Referrer::Client: |
547 | 0 | return "about:client"_string; |
548 | 0 | default: |
549 | 0 | VERIFY_NOT_REACHED(); |
550 | 0 | } |
551 | 0 | }, |
552 | 0 | [&](URL::URL const& url) { |
553 | | // 3. Return this’s request’s referrer, serialized. |
554 | 0 | return MUST(String::from_byte_string(url.serialize())); |
555 | 0 | }); |
556 | 0 | } |
557 | | |
558 | | // https://fetch.spec.whatwg.org/#dom-request-referrerpolicy |
559 | | Bindings::ReferrerPolicy Request::referrer_policy() const |
560 | 0 | { |
561 | | // The referrerPolicy getter steps are to return this’s request’s referrer policy. |
562 | 0 | return to_bindings_enum(m_request->referrer_policy()); |
563 | 0 | } |
564 | | |
565 | | // https://fetch.spec.whatwg.org/#dom-request-mode |
566 | | Bindings::RequestMode Request::mode() const |
567 | 0 | { |
568 | | // The mode getter steps are to return this’s request’s mode. |
569 | 0 | return to_bindings_enum(m_request->mode()); |
570 | 0 | } |
571 | | |
572 | | // https://fetch.spec.whatwg.org/#dom-request-credentials |
573 | | Bindings::RequestCredentials Request::credentials() const |
574 | 0 | { |
575 | | // The credentials getter steps are to return this’s request’s credentials mode. |
576 | 0 | return to_bindings_enum(m_request->credentials_mode()); |
577 | 0 | } |
578 | | |
579 | | // https://fetch.spec.whatwg.org/#dom-request-cache |
580 | | Bindings::RequestCache Request::cache() const |
581 | 0 | { |
582 | | // The cache getter steps are to return this’s request’s cache mode. |
583 | 0 | return to_bindings_enum(m_request->cache_mode()); |
584 | 0 | } |
585 | | |
586 | | // https://fetch.spec.whatwg.org/#dom-request-redirect |
587 | | Bindings::RequestRedirect Request::redirect() const |
588 | 0 | { |
589 | | // The redirect getter steps are to return this’s request’s redirect mode. |
590 | 0 | return to_bindings_enum(m_request->redirect_mode()); |
591 | 0 | } |
592 | | |
593 | | // https://fetch.spec.whatwg.org/#dom-request-integrity |
594 | | String Request::integrity() const |
595 | 0 | { |
596 | | // The integrity getter steps are to return this’s request’s integrity metadata. |
597 | 0 | return m_request->integrity_metadata(); |
598 | 0 | } |
599 | | |
600 | | // https://fetch.spec.whatwg.org/#dom-request-keepalive |
601 | | bool Request::keepalive() const |
602 | 0 | { |
603 | | // The keepalive getter steps are to return this’s request’s keepalive. |
604 | 0 | return m_request->keepalive(); |
605 | 0 | } |
606 | | |
607 | | // https://fetch.spec.whatwg.org/#dom-request-isreloadnavigation |
608 | | bool Request::is_reload_navigation() const |
609 | 0 | { |
610 | | // The isReloadNavigation getter steps are to return true if this’s request’s reload-navigation flag is set; otherwise false. |
611 | 0 | return m_request->reload_navigation(); |
612 | 0 | } |
613 | | |
614 | | // https://fetch.spec.whatwg.org/#dom-request-ishistorynavigation |
615 | | bool Request::is_history_navigation() const |
616 | 0 | { |
617 | | // The isHistoryNavigation getter steps are to return true if this’s request’s history-navigation flag is set; otherwise false. |
618 | 0 | return m_request->history_navigation(); |
619 | 0 | } |
620 | | |
621 | | // https://fetch.spec.whatwg.org/#dom-request-signal |
622 | | JS::NonnullGCPtr<DOM::AbortSignal> Request::signal() const |
623 | 0 | { |
624 | | // The signal getter steps are to return this’s signal. |
625 | 0 | return *m_signal; |
626 | 0 | } |
627 | | |
628 | | // https://fetch.spec.whatwg.org/#dom-request-duplex |
629 | | Bindings::RequestDuplex Request::duplex() const |
630 | 0 | { |
631 | | // The duplex getter steps are to return "half". |
632 | 0 | return Bindings::RequestDuplex::Half; |
633 | 0 | } |
634 | | |
635 | | // https://fetch.spec.whatwg.org/#dom-request-clone |
636 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<Request>> Request::clone() const |
637 | 0 | { |
638 | 0 | auto& realm = this->realm(); |
639 | | |
640 | | // 1. If this is unusable, then throw a TypeError. |
641 | 0 | if (is_unusable()) |
642 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Request is unusable"sv }; |
643 | | |
644 | | // 2. Let clonedRequest be the result of cloning this’s request. |
645 | 0 | auto cloned_request = m_request->clone(realm); |
646 | | |
647 | | // 3. Assert: this’s signal is non-null. |
648 | 0 | VERIFY(m_signal); |
649 | | |
650 | | // 4. Let clonedSignal be the result of creating a dependent abort signal from « this’s signal », using AbortSignal and this’s relevant realm. |
651 | 0 | auto& relevant_realm = HTML::relevant_realm(*this); |
652 | 0 | auto cloned_signal = TRY(DOM::AbortSignal::create_dependent_abort_signal(relevant_realm, { m_signal })); |
653 | | |
654 | | // 5. Let clonedRequestObject be the result of creating a Request object, given clonedRequest, this’s headers’s guard, clonedSignal and this’s relevant realm. |
655 | 0 | auto cloned_request_object = Request::create(relevant_realm, cloned_request, m_headers->guard(), cloned_signal); |
656 | | |
657 | | // 6. Return clonedRequestObject. |
658 | 0 | return cloned_request_object; |
659 | 0 | } |
660 | | |
661 | | } |