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