Skip to main content

script/dom/fetch/
request.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Ref;
6use std::rc::Rc;
7use std::str::FromStr;
8
9use cssparser::match_ignore_ascii_case;
10use dom_struct::dom_struct;
11use http::Method as HttpMethod;
12use http::header::{CONTENT_TYPE, HeaderValue};
13use http::method::InvalidMethod;
14use js::context::JSContext;
15use js::rust::HandleObject;
16use net_traits::ReferrerPolicy as MsgReferrerPolicy;
17use net_traits::fetch::headers::is_forbidden_method;
18use net_traits::request::{
19    CacheMode, CredentialsMode, Destination, Origin, RedirectMode, Referrer,
20    Request as NetTraitsRequest, RequestBuilder, RequestMode as NetTraitsRequestMode,
21    TraversableForUserPrompts,
22};
23use script_bindings::cell::DomRefCell;
24use script_bindings::cformat;
25use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
26use servo_url::ServoUrl;
27
28use crate::conversions::Convert;
29use crate::dom::abortsignal::AbortSignal;
30use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
31use crate::dom::bindings::codegen::Bindings::RequestBinding::{
32    ReferrerPolicy, RequestCache, RequestCredentials, RequestDestination, RequestDuplex,
33    RequestInfo, RequestInit, RequestMethods, RequestMode, RequestRedirect,
34};
35use crate::dom::bindings::error::{Error, Fallible};
36use crate::dom::bindings::reflector::DomGlobal;
37use crate::dom::bindings::root::{DomRoot, MutNullableDom};
38use crate::dom::bindings::str::{ByteString, DOMString, USVString};
39use crate::dom::bindings::trace::RootedTraceableBox;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::headers::{Guard, Headers};
42use crate::dom::promise::Promise;
43use crate::dom::stream::readablestream::ReadableStream;
44use crate::fetch::body::{
45    BodyMixin, BodyType, Extractable, body_text_stream, clone_body_stream_for_dom_body,
46    consume_body,
47};
48use crate::fetch::fetch::RequestWithGlobalScope;
49use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
50
51#[dom_struct]
52pub(crate) struct Request {
53    reflector_: Reflector,
54    #[no_trace]
55    /// <https://fetch.spec.whatwg.org/#concept-request-request>
56    request: DomRefCell<NetTraitsRequest>,
57    /// <https://fetch.spec.whatwg.org/#concept-request-body>
58    body_stream: MutNullableDom<ReadableStream>,
59    /// <https://fetch.spec.whatwg.org/#request-headers>
60    headers: MutNullableDom<Headers>,
61    /// <https://fetch.spec.whatwg.org/#request-signal>
62    signal: MutNullableDom<AbortSignal>,
63}
64
65impl Request {
66    fn new_inherited(global: &GlobalScope, url: ServoUrl) -> Request {
67        Request {
68            reflector_: Reflector::new(),
69            request: DomRefCell::new(net_request_from_global(global, url)),
70            body_stream: MutNullableDom::new(None),
71            headers: Default::default(),
72            signal: MutNullableDom::new(None),
73        }
74    }
75
76    pub(crate) fn new(
77        cx: &mut JSContext,
78        global: &GlobalScope,
79        proto: Option<HandleObject>,
80        url: ServoUrl,
81    ) -> DomRoot<Request> {
82        reflect_dom_object_with_proto(
83            cx,
84            Box::new(Request::new_inherited(global, url)),
85            global,
86            proto,
87        )
88    }
89
90    fn from_net_request(
91        cx: &mut JSContext,
92        global: &GlobalScope,
93        proto: Option<HandleObject>,
94        net_request: NetTraitsRequest,
95    ) -> DomRoot<Request> {
96        let r = Request::new(cx, global, proto, net_request.current_url());
97        *r.request.borrow_mut() = net_request;
98        r
99    }
100
101    // https://fetch.spec.whatwg.org/#dom-request
102    pub(crate) fn constructor(
103        cx: &mut js::context::JSContext,
104        global: &GlobalScope,
105        proto: Option<HandleObject>,
106        mut input: RequestInfo,
107        init: &RequestInit,
108    ) -> Fallible<DomRoot<Request>> {
109        // Step 1. Let request be null.
110        let temporary_request: NetTraitsRequest;
111
112        // Step 2. Let fallbackMode be null.
113        let mut fallback_mode: Option<NetTraitsRequestMode> = None;
114
115        // Step 3. Let baseURL be this’s relevant settings object’s API base URL.
116        let base_url = global.api_base_url();
117
118        // Step 4. Let signal be null.
119        let mut signal: Option<DomRoot<AbortSignal>> = None;
120
121        // Required later for step 41.1
122        let mut input_body_is_unusable = false;
123
124        match input {
125            // Step 5. If input is a string, then:
126            RequestInfo::USVString(USVString(ref usv_string)) => {
127                // Step 5.1. Let parsedURL be the result of parsing input with baseURL.
128                let parsed_url = base_url.join(usv_string);
129                // Step 5.2. If parsedURL is failure, then throw a TypeError.
130                if parsed_url.is_err() {
131                    return Err(Error::Type(c"Url could not be parsed".to_owned()));
132                }
133                // Step 5.3. If parsedURL includes credentials, then throw a TypeError.
134                let url = parsed_url.unwrap();
135                if includes_credentials(&url) {
136                    return Err(Error::Type(c"Url includes credentials".to_owned()));
137                }
138                // Step 5.4. Set request to a new request whose URL is parsedURL.
139                temporary_request = net_request_from_global(global, url);
140                // Step 5.5. Set fallbackMode to "cors".
141                fallback_mode = Some(NetTraitsRequestMode::CorsMode);
142            },
143            // Step 6. Otherwise:
144            // Step 6.1. Assert: input is a Request object.
145            RequestInfo::Request(ref input_request) => {
146                // Preparation for step 41.1
147                input_body_is_unusable = input_request.is_unusable();
148                // Step 6.2. Set request to input’s request.
149                temporary_request = input_request.request.borrow().clone();
150                // Step 6.3. Set signal to input’s signal.
151                signal = Some(input_request.Signal());
152            },
153        }
154
155        // Step 7. Let origin be this’s relevant settings object’s origin.
156        let origin = global.origin();
157        let origin = origin.immutable();
158
159        // Step 8. Let traversableForUserPrompts be "client".
160        let mut traversable_for_user_prompts = TraversableForUserPrompts::Client;
161
162        // Step 9. If request’s traversable for user prompts is an environment settings object
163        // and its origin is same origin with origin, then set traversableForUserPrompts
164        // to request’s traversable for user prompts.
165        // TODO: `environment settings object` is not implemented in Servo yet.
166
167        // Step 10. If init["window"] exists and is non-null, then throw a TypeError.
168        if !init.window.handle().is_null_or_undefined() {
169            return Err(Error::Type(c"Window is present and is not null".to_owned()));
170        }
171
172        // Step 11. If init["window"] exists, then set traversableForUserPrompts to "no-traversable".
173        if !init.window.handle().is_undefined() {
174            traversable_for_user_prompts = TraversableForUserPrompts::NoTraversable;
175        }
176
177        // Step 12. Set request to a new request with the following properties:
178        let mut request: NetTraitsRequest;
179        request = net_request_from_global(global, temporary_request.current_url());
180        request.method = temporary_request.method;
181        request.headers = temporary_request.headers.clone();
182        request.unsafe_request = true;
183        request.traversable_for_user_prompts = traversable_for_user_prompts;
184        // TODO: `entry settings object` is not implemented in Servo yet.
185        request.origin = Origin::Client;
186        request.referrer = temporary_request.referrer;
187        request.referrer_policy = temporary_request.referrer_policy;
188        request.mode = temporary_request.mode;
189        request.credentials_mode = temporary_request.credentials_mode;
190        request.cache_mode = temporary_request.cache_mode;
191        request.redirect_mode = temporary_request.redirect_mode;
192        request.integrity_metadata = temporary_request.integrity_metadata;
193        // reload-navigation flag: request’s reload-navigation flag.
194        request.reload_navigation = temporary_request.reload_navigation;
195        // history-navigation flag: request’s history-navigation flag.
196        request.history_navigation = temporary_request.history_navigation;
197
198        // Step 13. If init is not empty, then:
199        if init.body.is_some() ||
200            init.cache.is_some() ||
201            init.credentials.is_some() ||
202            init.duplex.is_some() ||
203            init.integrity.is_some() ||
204            init.headers.is_some() ||
205            init.keepalive.is_some() ||
206            init.method.is_some() ||
207            init.mode.is_some() ||
208            init.redirect.is_some() ||
209            init.referrer.is_some() ||
210            init.referrerPolicy.is_some() ||
211            !init.window.handle().is_undefined()
212        {
213            // Step 13.1. If request’s mode is "navigate", then set it to "same-origin".
214            if request.mode == NetTraitsRequestMode::Navigate {
215                request.mode = NetTraitsRequestMode::SameOrigin;
216            }
217            // Step 13.2. Unset request’s reload-navigation flag.
218            request.reload_navigation = false;
219            // Step 13.3. Unset request’s history-navigation flag.
220            request.history_navigation = false;
221            // Step 13.4. Set request’s origin to "client".
222            // TODO
223            // Step 13.5. Set request’s referrer to "client".
224            request.referrer = global.get_referrer();
225            // Step 13.6. Set request’s referrer policy to the empty string.
226            request.referrer_policy = MsgReferrerPolicy::EmptyString;
227            // Step 13.7. Set request’s URL to request’s current URL.
228            // TODO
229            // Step 13.8. Set request’s URL list to « request’s URL ».
230            // TODO
231        }
232
233        // Step 14. If init["referrer"] exists, then:
234        if let Some(init_referrer) = init.referrer.as_ref() {
235            // Step 14.1. Let referrer be init["referrer"].
236            let referrer = &init_referrer.0;
237            // Step 14.2. If referrer is the empty string, then set request’s referrer to "no-referrer".
238            if referrer.is_empty() {
239                request.referrer = Referrer::NoReferrer;
240            // Step 14.3. Otherwise:
241            } else {
242                // Step 14.3.1. Let parsedReferrer be the result of parsing referrer with baseURL.
243                let parsed_referrer = base_url.join(referrer);
244                // Step 14.3.2. If parsedReferrer is failure, then throw a TypeError.
245                if parsed_referrer.is_err() {
246                    return Err(Error::Type(c"Failed to parse referrer url".to_owned()));
247                }
248                // Step 14.3.3. If one of the following is true
249                // parsedReferrer’s scheme is "about" and path is the string "client"
250                // parsedReferrer’s origin is not same origin with origin
251                if let Ok(parsed_referrer) = parsed_referrer {
252                    if (parsed_referrer.cannot_be_a_base() &&
253                        parsed_referrer.scheme() == "about" &&
254                        parsed_referrer.path() == "client") ||
255                        parsed_referrer.origin() != *origin
256                    {
257                        // then set request’s referrer to "client".
258                        request.referrer = global.get_referrer();
259                    } else {
260                        // Step 14.3.4. Otherwise, set request’s referrer to parsedReferrer.
261                        request.referrer = Referrer::ReferrerUrl(parsed_referrer);
262                    }
263                }
264            }
265        }
266
267        // Step 15. If init["referrerPolicy"] exists, then set request’s referrer policy to it.
268        if let Some(init_referrerpolicy) = init.referrerPolicy.as_ref() {
269            let init_referrer_policy = (*init_referrerpolicy).convert();
270            request.referrer_policy = init_referrer_policy;
271        }
272
273        // Step 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise.
274        let mode = init.mode.as_ref().map(|m| (*m).convert()).or(fallback_mode);
275
276        // Step 17. If mode is "navigate", then throw a TypeError.
277        if let Some(NetTraitsRequestMode::Navigate) = mode {
278            return Err(Error::Type(c"Request mode is Navigate".to_owned()));
279        }
280
281        // Step 18. If mode is non-null, set request’s mode to mode.
282        if let Some(m) = mode {
283            request.mode = m;
284        }
285
286        // Step 19. If init["credentials"] exists, then set request’s credentials mode to it.
287        if let Some(init_credentials) = init.credentials.as_ref() {
288            let credentials = (*init_credentials).convert();
289            request.credentials_mode = credentials;
290        }
291
292        // Step 20. If init["cache"] exists, then set request’s cache mode to it.
293        if let Some(init_cache) = init.cache.as_ref() {
294            let cache = (*init_cache).convert();
295            request.cache_mode = cache;
296        }
297
298        // Step 21. If request’s cache mode is "only-if-cached" and request’s mode
299        // is not "same-origin", then throw a TypeError.
300        if request.cache_mode == CacheMode::OnlyIfCached &&
301            request.mode != NetTraitsRequestMode::SameOrigin
302        {
303            return Err(Error::Type(
304                c"Cache is 'only-if-cached' and mode is not 'same-origin'".to_owned(),
305            ));
306        }
307
308        // Step 22. If init["redirect"] exists, then set request’s redirect mode to it.
309        if let Some(init_redirect) = init.redirect.as_ref() {
310            let redirect = (*init_redirect).convert();
311            request.redirect_mode = redirect;
312        }
313
314        // Step 23. If init["integrity"] exists, then set request’s integrity metadata to it.
315        if let Some(init_integrity) = init.integrity.as_ref() {
316            let integrity = init_integrity.to_string();
317            request.integrity_metadata = integrity;
318        }
319
320        // Step 24. If init["keepalive"] exists, then set request’s keepalive to it.
321        if let Some(init_keepalive) = init.keepalive {
322            request.keep_alive = init_keepalive;
323        }
324
325        // Step 25. If init["method"] exists, then:
326        // Step 25.1. Let method be init["method"].
327        if let Some(init_method) = init.method.as_ref() {
328            // Step 25.2. If method is not a method or method is a forbidden method, then throw a TypeError.
329            if !is_method(init_method) {
330                return Err(Error::Type(c"Method is not a method".to_owned()));
331            }
332            if is_forbidden_method(init_method) {
333                return Err(Error::Type(c"Method is forbidden".to_owned()));
334            }
335            // Step 25.3. Normalize method.
336            let method = match init_method.as_str() {
337                Some(s) => normalize_method(s)
338                    .map_err(|e| Error::Type(cformat!("Method is not valid: {:?}", e)))?,
339                None => return Err(Error::Type(c"Method is not a valid UTF8".to_owned())),
340            };
341            // Step 25.4. Set request’s method to method.
342            request.method = method;
343        }
344
345        // Step 26. If init["signal"] exists, then set signal to it.
346        if let Some(init_signal) = init.signal.as_ref() {
347            signal = init_signal.clone();
348        }
349        // Step 27. If init["priority"] exists, then:
350        // TODO
351        // Step 27.1. If request’s internal priority is not null,
352        // then update request’s internal priority in an implementation-defined manner.
353        // TODO
354        // Step 27.2. Otherwise, set request’s priority to init["priority"].
355        // TODO
356
357        // Step 28. Set this’s request to request.
358        let request = Request::from_net_request(cx, global, proto, request);
359
360        // Step 29. Let signals be « signal » if signal is non-null; otherwise « ».
361        let signals = signal.map_or(vec![], |s| vec![s]);
362        // Step 30. Set this’s signal to the result of creating a dependent
363        // abort signal from signals, using AbortSignal and this’s relevant realm.
364        request
365            .signal
366            .set(Some(&AbortSignal::create_dependent_abort_signal(
367                cx, signals, global,
368            )));
369
370        // Step 31. Set this’s headers to a new Headers object with this’s relevant realm,
371        // whose header list is request’s header list and guard is "request".
372        //
373        // "or_init" looks unclear here, but it always enters the block since r
374        // hasn't had any other way to initialize its headers
375        request
376            .headers
377            .or_init(|| Headers::for_request(cx, &request.global()));
378
379        // Step 33. If init is not empty, then:
380        //
381        // but spec says this should only be when non-empty init?
382        let headers_copy = init
383            .headers
384            .as_ref()
385            .map(|possible_header| match possible_header {
386                HeadersInit::ByteStringSequenceSequence(init_sequence) => {
387                    HeadersInit::ByteStringSequenceSequence(init_sequence.clone())
388                },
389                HeadersInit::ByteStringByteStringRecord(init_map) => {
390                    HeadersInit::ByteStringByteStringRecord(init_map.clone())
391                },
392            });
393
394        // Step 33.3
395        // We cannot empty `r.Headers().header_list` because
396        // we would undo the Step 25 above.  One alternative is to set
397        // `headers_copy` as a deep copy of `r.Headers()`. However,
398        // `r.Headers()` is a `DomRoot<T>`, and therefore it is difficult
399        // to obtain a mutable reference to `r.Headers()`. Without the
400        // mutable reference, we cannot mutate `r.Headers()` to be the
401        // deep copied headers in Step 25.
402
403        // Step 32. If this’s request’s mode is "no-cors", then:
404        if request.request.borrow().mode == NetTraitsRequestMode::NoCors {
405            let borrowed_request = request.request.borrow();
406            // Step 32.1. If this’s request’s method is not a CORS-safelisted method, then throw a TypeError.
407            if !is_cors_safelisted_method(&borrowed_request.method) {
408                return Err(Error::Type(
409                    c"The mode is 'no-cors' but the method is not a cors-safelisted method"
410                        .to_owned(),
411                ));
412            }
413            // Step 32.2. Set this’s headers’s guard to "request-no-cors".
414            request.Headers(cx).set_guard(Guard::RequestNoCors);
415        }
416
417        match headers_copy {
418            None => {
419                // Step 33.4. If headers is a Headers object, then for each header of its header list, append header to this’s headers.
420                //
421                // This is equivalent to the specification's concept of
422                // "associated headers list". If an init headers is not given,
423                // but an input with headers is given, set request's
424                // headers as the input's Headers.
425                if let RequestInfo::Request(ref input_request) = input {
426                    request
427                        .Headers(cx)
428                        .copy_from_headers(&input_request.Headers(cx))?;
429                }
430            },
431            // Step 33.5. Otherwise, fill this’s headers with headers.
432            Some(headers_copy) => request.Headers(cx).fill(Some(headers_copy))?,
433        }
434
435        // Step 33.5 depending on how we got here
436        // Copy the headers list onto the headers of net_traits::Request
437        request.request.borrow_mut().headers = request.Headers(cx).get_headers_list();
438
439        // Step 34. Let inputBody be input’s request’s body if input is a Request object; otherwise null.
440        let input_body = if let RequestInfo::Request(ref mut input_request) = input {
441            let mut input_request_request = input_request.request.borrow_mut();
442            request.body_stream.set(input_request.body().as_deref());
443            input_request_request.body.take()
444        } else {
445            None
446        };
447
448        // Step 35. If either init["body"] exists and is non-null or inputBody is non-null,
449        // and request’s method is `GET` or `HEAD`, then throw a TypeError.
450        if init.body.as_ref().is_some_and(|body| body.is_some()) || input_body.is_some() {
451            let req = request.request.borrow();
452            let req_method = &req.method;
453            match *req_method {
454                HttpMethod::GET => {
455                    return Err(Error::Type(
456                        c"Init's body is non-null, and request method is GET".to_owned(),
457                    ));
458                },
459                HttpMethod::HEAD => {
460                    return Err(Error::Type(
461                        c"Init's body is non-null, and request method is HEAD".to_owned(),
462                    ));
463                },
464                _ => {},
465            }
466        }
467
468        // Step 36. Let initBody be null.
469        let mut init_body = None;
470        // Step 37. If init["body"] exists and is non-null, then:
471        if let Some(Some(ref input_init_body)) = init.body {
472            // Step 37.1. Let bodyWithType be the result of extracting init["body"], with keepalive set to request’s keepalive.
473            let mut body_with_type =
474                input_init_body.extract(cx, global, request.request.borrow().keep_alive)?;
475
476            // Step 37.3. Let type be bodyWithType’s type.
477            if let Some(contents) = body_with_type.content_type.take() {
478                // Step 37.4. If type is non-null and this’s headers’s header list
479                // does not contain `Content-Type`, then append (`Content-Type`, type) to this’s headers.
480                let content_type_header_name = b"Content-Type";
481                if !request
482                    .Headers(cx)
483                    .Has(ByteString::new(content_type_header_name.to_vec()))
484                    .unwrap()
485                {
486                    let content_type_header_value = contents.as_bytes();
487                    request.Headers(cx).Append(
488                        ByteString::new(content_type_header_name.to_vec()),
489                        ByteString::new(content_type_header_value.to_vec()),
490                    )?;
491
492                    // In Servo request.Headers's header list isn't a pointer to the same
493                    // actual list as request.request's, and so we need to append to both lists
494                    // to keep them in sync.
495                    if let Ok(header_value) = HeaderValue::from_bytes(&content_type_header_value) {
496                        request
497                            .request
498                            .borrow_mut()
499                            .headers
500                            .insert(CONTENT_TYPE, header_value);
501                    }
502                }
503            }
504
505            // Step 37.2. Set initBody to bodyWithType’s body.
506            let (net_body, stream) = body_with_type.into_net_request_body(cx);
507            request.body_stream.set(Some(&*stream));
508            init_body = Some(net_body);
509        }
510
511        // Step 38. Let inputOrInitBody be initBody if it is non-null; otherwise inputBody.
512        // Step 40. Let finalBody be inputOrInitBody.
513        // Step 41.2. Set finalBody to the result of creating a proxy for inputBody.
514        //
515        // There are multiple reassignments to similar values. In the end, all end up as
516        // final_body. Therefore, final_body is equivalent to inputOrInitBody
517        let init_body_is_non_null = init_body.is_some();
518        let final_body = init_body.or(input_body);
519
520        // Step 39. If inputOrInitBody is non-null and inputOrInitBody’s source is null, then:
521        if final_body
522            .as_ref()
523            .is_some_and(|body| body.source_is_null())
524        {
525            // Step 39.1. “If initBody is non-null and init["duplex"] does not exist,
526            // then throw a TypeError.”
527            if init_body_is_non_null && init.duplex.is_none() {
528                return Err(Error::Type(
529                    c"Request with a ReadableStream body must specify duplex".to_owned(),
530                ));
531            }
532            // Step 39.2. If this’s request’s mode is neither "same-origin" nor "cors", then throw a TypeError.
533            let request_mode = &request.request.borrow().mode;
534            if *request_mode != NetTraitsRequestMode::CorsMode &&
535                *request_mode != NetTraitsRequestMode::SameOrigin
536            {
537                return Err(Error::Type(
538                    c"Request mode must be Cors or SameOrigin".to_owned(),
539                ));
540            }
541            // Step 39.3. Set this’s request’s use-CORS-preflight flag.
542            // TODO
543        }
544
545        // Step 41. If initBody is null and inputBody is non-null, then:
546        // Step 41.1. If inputBody is unusable, then throw a TypeError.
547        //
548        // We only perform this check on input_body. However, we already
549        // processed the input body. Therefore, we check it all the way
550        // above and throw the error at the last possible moment
551        if input_body_is_unusable {
552            return Err(Error::Type(c"Input body is unusable".to_owned()));
553        }
554
555        // Step 42. Set this’s request’s body to finalBody.
556        request.request.borrow_mut().body = final_body;
557
558        Ok(request)
559    }
560
561    /// <https://fetch.spec.whatwg.org/#concept-request-clone>
562    fn clone_from(cx: &mut js::context::JSContext, r: &Request) -> Fallible<DomRoot<Request>> {
563        let req = r.request.borrow();
564        let url = req.url();
565        let headers_guard = r.Headers(cx).get_guard();
566
567        // Step 1. Let newRequest be a copy of request, except for its body.
568        let mut new_req_inner = req.clone();
569        let body = new_req_inner.body.take();
570
571        let r_clone = Request::new(cx, &r.global(), None, url);
572        *r_clone.request.borrow_mut() = new_req_inner;
573
574        // Step 2. If request’s body is non-null, set newRequest’s body
575        // to the result of cloning request’s body.
576        if let Some(body) = body {
577            r_clone.request.borrow_mut().body = Some(body);
578        }
579
580        r_clone.Headers(cx).copy_from_headers(&r.Headers(cx))?;
581        r_clone.Headers(cx).set_guard(headers_guard);
582
583        clone_body_stream_for_dom_body(cx, &r.body_stream, &r_clone.body_stream)?;
584
585        // Step 3. Return newRequest.
586        Ok(r_clone)
587    }
588
589    pub(crate) fn request(&self) -> Ref<'_, NetTraitsRequest> {
590        self.request.borrow()
591    }
592}
593
594fn net_request_from_global(global: &GlobalScope, url: ServoUrl) -> NetTraitsRequest {
595    let url = ensure_blob_referenced_by_url_is_kept_alive(global, url);
596    RequestBuilder::new(global.webview_id(), url, global.get_referrer())
597        .with_global_scope(global)
598        .build()
599}
600
601/// <https://fetch.spec.whatwg.org/#concept-method-normalize>
602fn normalize_method(m: &str) -> Result<HttpMethod, InvalidMethod> {
603    match_ignore_ascii_case! { m,
604        "delete" => return Ok(HttpMethod::DELETE),
605        "get" => return Ok(HttpMethod::GET),
606        "head" => return Ok(HttpMethod::HEAD),
607        "options" => return Ok(HttpMethod::OPTIONS),
608        "post" => return Ok(HttpMethod::POST),
609        "put" => return Ok(HttpMethod::PUT),
610        _ => (),
611    }
612    debug!("Method: {:?}", m);
613    HttpMethod::from_str(m)
614}
615
616/// <https://fetch.spec.whatwg.org/#concept-method>
617fn is_method(m: &ByteString) -> bool {
618    m.as_str().is_some()
619}
620
621/// <https://fetch.spec.whatwg.org/#cors-safelisted-method>
622fn is_cors_safelisted_method(m: &HttpMethod) -> bool {
623    m == HttpMethod::GET || m == HttpMethod::HEAD || m == HttpMethod::POST
624}
625
626/// <https://url.spec.whatwg.org/#include-credentials>
627fn includes_credentials(input: &ServoUrl) -> bool {
628    !input.username().is_empty() || input.password().is_some()
629}
630
631impl RequestMethods<crate::DomTypeHolder> for Request {
632    /// <https://fetch.spec.whatwg.org/#dom-request>
633    fn Constructor(
634        cx: &mut js::context::JSContext,
635        global: &GlobalScope,
636        proto: Option<HandleObject>,
637        input: RequestInfo,
638        init: RootedTraceableBox<RequestInit>,
639    ) -> Fallible<DomRoot<Request>> {
640        Self::constructor(cx, global, proto, input, &init)
641    }
642
643    /// <https://fetch.spec.whatwg.org/#dom-request-method>
644    fn Method(&self) -> ByteString {
645        let r = self.request.borrow();
646        ByteString::new(r.method.as_ref().as_bytes().into())
647    }
648
649    /// <https://fetch.spec.whatwg.org/#dom-request-url>
650    fn Url(&self) -> USVString {
651        let r = self.request.borrow();
652        USVString(r.url_list.first().map_or("", |u| u.as_str()).into())
653    }
654
655    /// <https://fetch.spec.whatwg.org/#dom-request-headers>
656    fn Headers(&self, cx: &mut js::context::JSContext) -> DomRoot<Headers> {
657        self.headers.or_init(|| Headers::new(cx, &self.global()))
658    }
659
660    /// <https://fetch.spec.whatwg.org/#dom-request-destination>
661    fn Destination(&self) -> RequestDestination {
662        self.request.borrow().destination.convert()
663    }
664
665    /// <https://fetch.spec.whatwg.org/#dom-request-referrer>
666    fn Referrer(&self) -> USVString {
667        let r = self.request.borrow();
668        USVString(match r.referrer {
669            Referrer::NoReferrer => String::from(""),
670            Referrer::Client(_) => String::from("about:client"),
671            Referrer::ReferrerUrl(ref u) => {
672                let u_c = u.clone();
673                u_c.into_string()
674            },
675        })
676    }
677
678    /// <https://fetch.spec.whatwg.org/#dom-request-referrerpolicy>
679    fn ReferrerPolicy(&self) -> ReferrerPolicy {
680        self.request.borrow().referrer_policy.convert()
681    }
682
683    /// <https://fetch.spec.whatwg.org/#dom-request-mode>
684    fn Mode(&self) -> RequestMode {
685        self.request.borrow().mode.clone().convert()
686    }
687
688    /// <https://fetch.spec.whatwg.org/#dom-request-credentials>
689    fn Credentials(&self) -> RequestCredentials {
690        let r = self.request.borrow().clone();
691        r.credentials_mode.convert()
692    }
693
694    /// <https://fetch.spec.whatwg.org/#dom-request-cache>
695    fn Cache(&self) -> RequestCache {
696        let r = self.request.borrow().clone();
697        r.cache_mode.convert()
698    }
699
700    /// <https://fetch.spec.whatwg.org/#dom-request-redirect>
701    fn Redirect(&self) -> RequestRedirect {
702        let r = self.request.borrow().clone();
703        r.redirect_mode.convert()
704    }
705
706    /// <https://fetch.spec.whatwg.org/#dom-request-integrity>
707    fn Integrity(&self) -> DOMString {
708        self.request.borrow().integrity_metadata.clone().into()
709    }
710
711    /// <https://fetch.spec.whatwg.org/#dom-request-duplex>
712    fn Duplex(&self) -> RequestDuplex {
713        // “The duplex getter steps are to return "half".”
714        RequestDuplex::Half
715    }
716
717    /// <https://fetch.spec.whatwg.org/#dom-request-keepalive>
718    fn Keepalive(&self) -> bool {
719        self.request.borrow().keep_alive
720    }
721
722    /// <https://fetch.spec.whatwg.org/#dom-request-isreloadnavigation>
723    /// The isReloadNavigation getter steps are to return true if this’s request’s reload-navigation flag is set; otherwise false.
724    fn IsReloadNavigation(&self) -> bool {
725        self.request.borrow().reload_navigation
726    }
727
728    /// <https://fetch.spec.whatwg.org/#dom-request-ishistorynavigation>
729    // The isHistoryNavigation getter steps are to return true if this’s request’s history-navigation flag is set; otherwise false.
730    fn IsHistoryNavigation(&self) -> bool {
731        self.request.borrow().history_navigation
732    }
733
734    /// <https://fetch.spec.whatwg.org/#dom-body-body>
735    fn GetBody(&self) -> Option<DomRoot<ReadableStream>> {
736        self.body()
737    }
738
739    /// <https://fetch.spec.whatwg.org/#dom-body-bodyused>
740    fn BodyUsed(&self) -> bool {
741        self.is_body_used()
742    }
743
744    /// <https://fetch.spec.whatwg.org/#dom-request-signal>
745    fn Signal(&self) -> DomRoot<AbortSignal> {
746        self.signal
747            .get()
748            .expect("Should always be initialized in constructor and clone")
749    }
750
751    /// <https://fetch.spec.whatwg.org/#dom-request-clone>
752    fn Clone(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<Request>> {
753        // Step 1. If this is unusable, then throw a TypeError.
754        if self.is_unusable() {
755            return Err(Error::Type(c"Request is unusable".to_owned()));
756        }
757
758        // Step 2. Let clonedRequest be the result of cloning this’s request.
759        let cloned_request = Request::clone_from(cx, self)?;
760        // Step 3. Assert: this’s signal is non-null.
761        let signal = self.signal.get().expect("Should always be initialized");
762        // Step 4. Let clonedSignal be the result of creating a dependent
763        // abort signal from « this’s signal », using AbortSignal and this’s relevant realm.
764        let cloned_signal =
765            AbortSignal::create_dependent_abort_signal(cx, vec![signal], &self.global());
766        // Step 5. Let clonedRequestObject be the result of creating a Request object,
767        // given clonedRequest, this’s headers’s guard, clonedSignal and this’s relevant realm.
768        //
769        // These steps already happen in `clone_from`
770        cloned_request.signal.set(Some(&cloned_signal));
771        // Step 6. Return clonedRequestObject.
772        Ok(cloned_request)
773    }
774
775    /// <https://fetch.spec.whatwg.org/#dom-body-text>
776    fn Text(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
777        consume_body(cx, self, BodyType::Text)
778    }
779
780    /// <https://fetch.spec.whatwg.org/#dom-body-blob>
781    fn Blob(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
782        consume_body(cx, self, BodyType::Blob)
783    }
784
785    /// <https://fetch.spec.whatwg.org/#dom-body-formdata>
786    fn FormData(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
787        consume_body(cx, self, BodyType::FormData)
788    }
789
790    /// <https://fetch.spec.whatwg.org/#dom-body-json>
791    fn Json(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
792        consume_body(cx, self, BodyType::Json)
793    }
794
795    /// <https://fetch.spec.whatwg.org/#dom-body-arraybuffer>
796    fn ArrayBuffer(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
797        consume_body(cx, self, BodyType::ArrayBuffer)
798    }
799
800    /// <https://fetch.spec.whatwg.org/#dom-body-bytes>
801    fn Bytes(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
802        consume_body(cx, self, BodyType::Bytes)
803    }
804
805    /// <https://fetch.spec.whatwg.org/#dom-body-textstream>
806    fn TextStream(&self, cx: &mut js::context::JSContext) -> Fallible<DomRoot<ReadableStream>> {
807        body_text_stream(cx, self)
808    }
809}
810
811impl BodyMixin for Request {
812    fn is_body_used(&self) -> bool {
813        let body_stream = self.body_stream.get();
814        body_stream
815            .as_ref()
816            .is_some_and(|stream| stream.is_disturbed())
817    }
818
819    fn is_unusable(&self) -> bool {
820        let body_stream = self.body_stream.get();
821        body_stream
822            .as_ref()
823            .is_some_and(|stream| stream.is_disturbed() || stream.is_locked())
824    }
825
826    fn body(&self) -> Option<DomRoot<ReadableStream>> {
827        self.body_stream.get()
828    }
829
830    fn get_mime_type(&self, cx: &mut js::context::JSContext) -> Vec<u8> {
831        let headers = self.Headers(cx);
832        headers.extract_mime_type()
833    }
834}
835
836impl Convert<CacheMode> for RequestCache {
837    fn convert(self) -> CacheMode {
838        match self {
839            RequestCache::Default => CacheMode::Default,
840            RequestCache::No_store => CacheMode::NoStore,
841            RequestCache::Reload => CacheMode::Reload,
842            RequestCache::No_cache => CacheMode::NoCache,
843            RequestCache::Force_cache => CacheMode::ForceCache,
844            RequestCache::Only_if_cached => CacheMode::OnlyIfCached,
845        }
846    }
847}
848
849impl Convert<RequestCache> for CacheMode {
850    fn convert(self) -> RequestCache {
851        match self {
852            CacheMode::Default => RequestCache::Default,
853            CacheMode::NoStore => RequestCache::No_store,
854            CacheMode::Reload => RequestCache::Reload,
855            CacheMode::NoCache => RequestCache::No_cache,
856            CacheMode::ForceCache => RequestCache::Force_cache,
857            CacheMode::OnlyIfCached => RequestCache::Only_if_cached,
858        }
859    }
860}
861
862impl Convert<CredentialsMode> for RequestCredentials {
863    fn convert(self) -> CredentialsMode {
864        match self {
865            RequestCredentials::Omit => CredentialsMode::Omit,
866            RequestCredentials::Same_origin => CredentialsMode::CredentialsSameOrigin,
867            RequestCredentials::Include => CredentialsMode::Include,
868        }
869    }
870}
871
872impl Convert<RequestCredentials> for CredentialsMode {
873    fn convert(self) -> RequestCredentials {
874        match self {
875            CredentialsMode::Omit => RequestCredentials::Omit,
876            CredentialsMode::CredentialsSameOrigin => RequestCredentials::Same_origin,
877            CredentialsMode::Include => RequestCredentials::Include,
878        }
879    }
880}
881
882impl Convert<Destination> for RequestDestination {
883    fn convert(self) -> Destination {
884        match self {
885            RequestDestination::_empty => Destination::None,
886            RequestDestination::Audio => Destination::Audio,
887            RequestDestination::Document => Destination::Document,
888            RequestDestination::Embed => Destination::Embed,
889            RequestDestination::Font => Destination::Font,
890            RequestDestination::Frame => Destination::Frame,
891            RequestDestination::Iframe => Destination::IFrame,
892            RequestDestination::Image => Destination::Image,
893            RequestDestination::Manifest => Destination::Manifest,
894            RequestDestination::Json => Destination::Json,
895            RequestDestination::Object => Destination::Object,
896            RequestDestination::Report => Destination::Report,
897            RequestDestination::Script => Destination::Script,
898            RequestDestination::Sharedworker => Destination::SharedWorker,
899            RequestDestination::Style => Destination::Style,
900            RequestDestination::Track => Destination::Track,
901            RequestDestination::Video => Destination::Video,
902            RequestDestination::Worker => Destination::Worker,
903            RequestDestination::Xslt => Destination::Xslt,
904        }
905    }
906}
907
908impl Convert<RequestDestination> for Destination {
909    fn convert(self) -> RequestDestination {
910        match self {
911            Destination::None => RequestDestination::_empty,
912            Destination::Audio => RequestDestination::Audio,
913            Destination::Document => RequestDestination::Document,
914            Destination::Embed => RequestDestination::Embed,
915            Destination::Font => RequestDestination::Font,
916            Destination::Frame => RequestDestination::Frame,
917            Destination::IFrame => RequestDestination::Iframe,
918            Destination::Image => RequestDestination::Image,
919            Destination::Manifest => RequestDestination::Manifest,
920            Destination::Json => RequestDestination::Json,
921            Destination::Object => RequestDestination::Object,
922            Destination::Report => RequestDestination::Report,
923            Destination::Script => RequestDestination::Script,
924            Destination::ServiceWorker | Destination::AudioWorklet | Destination::PaintWorklet => {
925                panic!("ServiceWorker request destination should not be exposed to DOM")
926            },
927            Destination::SharedWorker => RequestDestination::Sharedworker,
928            Destination::Style => RequestDestination::Style,
929            Destination::Track => RequestDestination::Track,
930            Destination::Video => RequestDestination::Video,
931            Destination::Worker => RequestDestination::Worker,
932            Destination::Xslt => RequestDestination::Xslt,
933            Destination::WebIdentity => RequestDestination::_empty,
934        }
935    }
936}
937
938impl Convert<NetTraitsRequestMode> for RequestMode {
939    fn convert(self) -> NetTraitsRequestMode {
940        match self {
941            RequestMode::Navigate => NetTraitsRequestMode::Navigate,
942            RequestMode::Same_origin => NetTraitsRequestMode::SameOrigin,
943            RequestMode::No_cors => NetTraitsRequestMode::NoCors,
944            RequestMode::Cors => NetTraitsRequestMode::CorsMode,
945        }
946    }
947}
948
949impl Convert<RequestMode> for NetTraitsRequestMode {
950    fn convert(self) -> RequestMode {
951        match self {
952            NetTraitsRequestMode::Navigate => RequestMode::Navigate,
953            NetTraitsRequestMode::SameOrigin => RequestMode::Same_origin,
954            NetTraitsRequestMode::NoCors => RequestMode::No_cors,
955            NetTraitsRequestMode::CorsMode => RequestMode::Cors,
956            NetTraitsRequestMode::WebSocket { .. } => {
957                unreachable!("Websocket request mode should never be exposed to Dom")
958            },
959        }
960    }
961}
962
963impl Convert<MsgReferrerPolicy> for ReferrerPolicy {
964    fn convert(self) -> MsgReferrerPolicy {
965        match self {
966            ReferrerPolicy::_empty => MsgReferrerPolicy::EmptyString,
967            ReferrerPolicy::No_referrer => MsgReferrerPolicy::NoReferrer,
968            ReferrerPolicy::No_referrer_when_downgrade => {
969                MsgReferrerPolicy::NoReferrerWhenDowngrade
970            },
971            ReferrerPolicy::Origin => MsgReferrerPolicy::Origin,
972            ReferrerPolicy::Origin_when_cross_origin => MsgReferrerPolicy::OriginWhenCrossOrigin,
973            ReferrerPolicy::Unsafe_url => MsgReferrerPolicy::UnsafeUrl,
974            ReferrerPolicy::Same_origin => MsgReferrerPolicy::SameOrigin,
975            ReferrerPolicy::Strict_origin => MsgReferrerPolicy::StrictOrigin,
976            ReferrerPolicy::Strict_origin_when_cross_origin => {
977                MsgReferrerPolicy::StrictOriginWhenCrossOrigin
978            },
979        }
980    }
981}
982
983impl Convert<ReferrerPolicy> for MsgReferrerPolicy {
984    fn convert(self) -> ReferrerPolicy {
985        match self {
986            MsgReferrerPolicy::EmptyString => ReferrerPolicy::_empty,
987            MsgReferrerPolicy::NoReferrer => ReferrerPolicy::No_referrer,
988            MsgReferrerPolicy::NoReferrerWhenDowngrade => {
989                ReferrerPolicy::No_referrer_when_downgrade
990            },
991            MsgReferrerPolicy::Origin => ReferrerPolicy::Origin,
992            MsgReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicy::Origin_when_cross_origin,
993            MsgReferrerPolicy::UnsafeUrl => ReferrerPolicy::Unsafe_url,
994            MsgReferrerPolicy::SameOrigin => ReferrerPolicy::Same_origin,
995            MsgReferrerPolicy::StrictOrigin => ReferrerPolicy::Strict_origin,
996            MsgReferrerPolicy::StrictOriginWhenCrossOrigin => {
997                ReferrerPolicy::Strict_origin_when_cross_origin
998            },
999        }
1000    }
1001}
1002
1003impl Convert<RedirectMode> for RequestRedirect {
1004    fn convert(self) -> RedirectMode {
1005        match self {
1006            RequestRedirect::Follow => RedirectMode::Follow,
1007            RequestRedirect::Error => RedirectMode::Error,
1008            RequestRedirect::Manual => RedirectMode::Manual,
1009        }
1010    }
1011}
1012
1013impl Convert<RequestRedirect> for RedirectMode {
1014    fn convert(self) -> RequestRedirect {
1015        match self {
1016            RedirectMode::Follow => RequestRedirect::Follow,
1017            RedirectMode::Error => RequestRedirect::Error,
1018            RedirectMode::Manual => RequestRedirect::Manual,
1019        }
1020    }
1021}