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