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