Skip to main content

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