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