net_traits/
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::sync::Arc;
6
7use base::generic_channel::GenericSharedMemory;
8use base::id::{PipelineId, WebViewId};
9use content_security_policy::{self as csp};
10use http::header::{AUTHORIZATION, HeaderName};
11use http::{HeaderMap, Method};
12use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
13use ipc_channel::router::ROUTER;
14use malloc_size_of_derive::MallocSizeOf;
15use mime::Mime;
16use parking_lot::Mutex;
17use rustc_hash::FxHashMap;
18use serde::{Deserialize, Serialize};
19use servo_url::{ImmutableOrigin, ServoUrl};
20use tokio::sync::oneshot::Sender as TokioSender;
21use url::Position;
22use uuid::Uuid;
23
24use crate::policy_container::{PolicyContainer, RequestPolicyContainer};
25use crate::pub_domains::is_same_site;
26use crate::response::{HttpsState, RedirectTaint, Response};
27use crate::{ReferrerPolicy, ResourceTimingType};
28
29#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
30/// An id to differentiate one network request from another.
31pub struct RequestId(pub Uuid);
32
33impl Default for RequestId {
34    fn default() -> Self {
35        Self(Uuid::new_v4())
36    }
37}
38
39/// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator)
40#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
41pub enum Initiator {
42    None,
43    Download,
44    ImageSet,
45    Manifest,
46    XSLT,
47    Prefetch,
48    Link,
49}
50
51/// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination)
52pub use csp::Destination;
53
54/// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin)
55#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
56pub enum Origin {
57    Client,
58    Origin(ImmutableOrigin),
59}
60
61impl Origin {
62    pub fn is_opaque(&self) -> bool {
63        matches!(self, Origin::Origin(ImmutableOrigin::Opaque(_)))
64    }
65}
66
67/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer)
68#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
69pub enum Referrer {
70    NoReferrer,
71    /// Contains the url that "client" would be resolved to. See
72    /// [https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer](https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer)
73    ///
74    /// If you are unsure you should probably use
75    /// [`GlobalScope::get_referrer`](https://doc.servo.org/script/dom/globalscope/struct.GlobalScope.html#method.get_referrer)
76    Client(ServoUrl),
77    ReferrerUrl(ServoUrl),
78}
79
80/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
81#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
82pub enum RequestMode {
83    Navigate,
84    SameOrigin,
85    NoCors,
86    CorsMode,
87    WebSocket {
88        protocols: Vec<String>,
89        original_url: ServoUrl,
90    },
91}
92
93/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
94#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
95pub enum CredentialsMode {
96    Omit,
97    CredentialsSameOrigin,
98    Include,
99}
100
101/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode)
102#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
103pub enum CacheMode {
104    Default,
105    NoStore,
106    Reload,
107    NoCache,
108    ForceCache,
109    OnlyIfCached,
110}
111
112/// [Service-workers mode](https://fetch.spec.whatwg.org/#request-service-workers-mode)
113#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
114pub enum ServiceWorkersMode {
115    All,
116    None,
117}
118
119/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode)
120#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
121pub enum RedirectMode {
122    Follow,
123    Error,
124    Manual,
125}
126
127/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting)
128#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
129pub enum ResponseTainting {
130    Basic,
131    CorsTainting,
132    Opaque,
133}
134
135/// <https://html.spec.whatwg.org/multipage/#preload-key>
136#[derive(Clone, Debug, Eq, Hash, Deserialize, MallocSizeOf, Serialize, PartialEq)]
137pub struct PreloadKey {
138    /// <https://html.spec.whatwg.org/multipage/#preload-url>
139    pub url: ServoUrl,
140    /// <https://html.spec.whatwg.org/multipage/#preload-destination>
141    pub destination: Destination,
142    /// <https://html.spec.whatwg.org/multipage/#preload-mode>
143    pub mode: RequestMode,
144    /// <https://html.spec.whatwg.org/multipage/#preload-credentials-mode>
145    pub credentials_mode: CredentialsMode,
146}
147
148impl PreloadKey {
149    pub fn new(request: &RequestBuilder) -> Self {
150        Self {
151            url: request.url.clone(),
152            destination: request.destination,
153            mode: request.mode.clone(),
154            credentials_mode: request.credentials_mode,
155        }
156    }
157}
158
159#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, MallocSizeOf)]
160pub struct PreloadId(pub Uuid);
161
162impl Default for PreloadId {
163    fn default() -> Self {
164        Self(Uuid::new_v4())
165    }
166}
167
168/// <https://html.spec.whatwg.org/multipage/#preload-entry>
169#[derive(Debug, MallocSizeOf)]
170pub struct PreloadEntry {
171    /// <https://html.spec.whatwg.org/multipage/#preload-integrity-metadata>
172    pub integrity_metadata: String,
173    /// <https://html.spec.whatwg.org/multipage/#preload-response>
174    pub response: Option<Response>,
175    /// <https://html.spec.whatwg.org/multipage/#preload-on-response-available>
176    #[ignore_malloc_size_of = "channels are hard"]
177    pub on_response_available: Option<TokioSender<Response>>,
178}
179
180impl PreloadEntry {
181    pub fn new(integrity_metadata: String) -> Self {
182        Self {
183            integrity_metadata,
184            response: None,
185            on_response_available: None,
186        }
187    }
188
189    /// Part of step 11.5 of <https://html.spec.whatwg.org/multipage/#preload>
190    pub fn with_response(&mut self, response: Response) {
191        // Step 11.5. If entry's on response available is null, then set entry's response to response;
192        // otherwise call entry's on response available given response.
193        if let Some(sender) = self.on_response_available.take() {
194            let _ = sender.send(response);
195        } else {
196            self.response = Some(response);
197        }
198    }
199}
200
201pub type PreloadedResources = FxHashMap<PreloadKey, PreloadId>;
202
203/// <https://fetch.spec.whatwg.org/#concept-request-client>
204#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
205pub struct RequestClient {
206    /// <https://html.spec.whatwg.org/multipage/#map-of-preloaded-resources>
207    pub preloaded_resources: PreloadedResources,
208    /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-policy-container>
209    pub policy_container: RequestPolicyContainer,
210    /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-origin>
211    pub origin: Origin,
212    /// <https://html.spec.whatwg.org/multipage/#nested-browsing-context>
213    pub is_nested_browsing_context: bool,
214    /// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy>
215    pub insecure_requests_policy: InsecureRequestsPolicy,
216}
217
218/// <https://html.spec.whatwg.org/multipage/#system-visibility-state>
219#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
220pub enum SystemVisibilityState {
221    #[default]
222    Hidden,
223    Visible,
224}
225
226/// <https://html.spec.whatwg.org/multipage/#traversable-navigable>
227#[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)]
228pub struct TraversableNavigable {
229    /// <https://html.spec.whatwg.org/multipage/#tn-current-session-history-step>
230    current_session_history_step: u8,
231    // TODO: https://html.spec.whatwg.org/multipage/#tn-session-history-entries
232    // TODO: https://html.spec.whatwg.org/multipage/#tn-session-history-traversal-queue
233    /// <https://html.spec.whatwg.org/multipage/#tn-running-nested-apply-history-step>
234    running_nested_apply_history_step: bool,
235    /// <https://html.spec.whatwg.org/multipage/#system-visibility-state>
236    system_visibility_state: SystemVisibilityState,
237    /// <https://html.spec.whatwg.org/multipage/#is-created-by-web-content>
238    is_created_by_web_content: bool,
239}
240
241/// <https://fetch.spec.whatwg.org/#concept-request-window>
242#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
243pub enum TraversableForUserPrompts {
244    NoTraversable,
245    Client,
246    TraversableNavigable(TraversableNavigable),
247}
248
249/// [CORS settings attribute](https://html.spec.whatwg.org/multipage/#attr-crossorigin-anonymous)
250#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
251pub enum CorsSettings {
252    Anonymous,
253    UseCredentials,
254}
255
256impl CorsSettings {
257    /// <https://html.spec.whatwg.org/multipage/#cors-settings-attribute>
258    pub fn from_enumerated_attribute(value: &str) -> CorsSettings {
259        match value.to_ascii_lowercase().as_str() {
260            "anonymous" => CorsSettings::Anonymous,
261            "use-credentials" => CorsSettings::UseCredentials,
262            _ => CorsSettings::Anonymous,
263        }
264    }
265}
266
267/// [Parser Metadata](https://fetch.spec.whatwg.org/#concept-request-parser-metadata)
268#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
269pub enum ParserMetadata {
270    Default,
271    ParserInserted,
272    NotParserInserted,
273}
274
275/// <https://fetch.spec.whatwg.org/#concept-body-source>
276#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
277pub enum BodySource {
278    Null,
279    Object,
280}
281
282/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
283/// which are sent from script to net.
284#[derive(Debug, Deserialize, Serialize)]
285pub enum BodyChunkResponse {
286    /// A chunk of bytes.
287    Chunk(GenericSharedMemory),
288    /// The body is done.
289    Done,
290    /// There was an error streaming the body,
291    /// terminate fetch.
292    Error,
293}
294
295/// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body>
296/// which are sent from net to script
297/// (with the exception of Done, which is sent from script to script).
298#[derive(Debug, Deserialize, Serialize)]
299pub enum BodyChunkRequest {
300    /// Connect a fetch in `net`, with a stream of bytes from `script`.
301    Connect(IpcSender<BodyChunkResponse>),
302    /// Re-extract a new stream from the source, following a redirect.
303    Extract(IpcReceiver<BodyChunkRequest>),
304    /// Ask for another chunk.
305    Chunk,
306    /// Signal the stream is done(sent from script to script).
307    Done,
308    /// Signal the stream has errored(sent from script to script).
309    Error,
310}
311
312/// The net component's view into <https://fetch.spec.whatwg.org/#bodies>
313#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
314pub struct RequestBody {
315    /// Net's channel to communicate with script re this body.
316    #[ignore_malloc_size_of = "Channels are hard"]
317    chan: Arc<Mutex<IpcSender<BodyChunkRequest>>>,
318    /// <https://fetch.spec.whatwg.org/#concept-body-source>
319    source: BodySource,
320    /// <https://fetch.spec.whatwg.org/#concept-body-total-bytes>
321    total_bytes: Option<usize>,
322}
323
324impl RequestBody {
325    pub fn new(
326        chan: IpcSender<BodyChunkRequest>,
327        source: BodySource,
328        total_bytes: Option<usize>,
329    ) -> Self {
330        RequestBody {
331            chan: Arc::new(Mutex::new(chan)),
332            source,
333            total_bytes,
334        }
335    }
336
337    /// Step 12 of <https://fetch.spec.whatwg.org/#concept-http-redirect-fetch>
338    pub fn extract_source(&mut self) {
339        match self.source {
340            BodySource::Null => panic!("Null sources should never be re-directed."),
341            BodySource::Object => {
342                let (chan, port) = ipc::channel().unwrap();
343                let mut selfchan = self.chan.lock();
344                let _ = selfchan.send(BodyChunkRequest::Extract(port));
345                *selfchan = chan;
346            },
347        }
348    }
349
350    pub fn take_stream(&self) -> Arc<Mutex<IpcSender<BodyChunkRequest>>> {
351        self.chan.clone()
352    }
353
354    pub fn source_is_null(&self) -> bool {
355        self.source == BodySource::Null
356    }
357
358    #[expect(clippy::len_without_is_empty)]
359    pub fn len(&self) -> Option<usize> {
360        self.total_bytes
361    }
362}
363
364trait RequestBodySize {
365    fn body_length(&self) -> usize;
366}
367
368impl RequestBodySize for Option<RequestBody> {
369    fn body_length(&self) -> usize {
370        self.as_ref()
371            .and_then(|body| body.len())
372            .unwrap_or_default()
373    }
374}
375
376#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
377pub enum InsecureRequestsPolicy {
378    DoNotUpgrade,
379    Upgrade,
380}
381
382pub trait RequestHeadersSize {
383    fn total_size(&self) -> usize;
384}
385
386impl RequestHeadersSize for HeaderMap {
387    fn total_size(&self) -> usize {
388        self.iter()
389            .map(|(name, value)| name.as_str().len() + value.len())
390            .sum()
391    }
392}
393
394#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
395pub struct RequestBuilder {
396    pub id: RequestId,
397
398    pub preload_id: Option<PreloadId>,
399
400    /// <https://fetch.spec.whatwg.org/#concept-request-method>
401    #[serde(
402        deserialize_with = "::hyper_serde::deserialize",
403        serialize_with = "::hyper_serde::serialize"
404    )]
405    #[ignore_malloc_size_of = "Defined in hyper"]
406    pub method: Method,
407
408    /// <https://fetch.spec.whatwg.org/#concept-request-url>
409    pub url: ServoUrl,
410
411    /// <https://fetch.spec.whatwg.org/#concept-request-header-list>
412    #[serde(
413        deserialize_with = "::hyper_serde::deserialize",
414        serialize_with = "::hyper_serde::serialize"
415    )]
416    #[ignore_malloc_size_of = "Defined in hyper"]
417    pub headers: HeaderMap,
418
419    /// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
420    pub unsafe_request: bool,
421
422    /// <https://fetch.spec.whatwg.org/#concept-request-body>
423    pub body: Option<RequestBody>,
424
425    /// <https://fetch.spec.whatwg.org/#request-service-workers-mode>
426    pub service_workers_mode: ServiceWorkersMode,
427    pub client: Option<RequestClient>,
428    /// <https://fetch.spec.whatwg.org/#concept-request-destination>
429    pub destination: Destination,
430    pub synchronous: bool,
431    pub mode: RequestMode,
432
433    /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode>
434    pub cache_mode: CacheMode,
435
436    /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag>
437    pub use_cors_preflight: bool,
438
439    /// <https://fetch.spec.whatwg.org/#request-keepalive-flag>
440    pub keep_alive: bool,
441
442    /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode>
443    pub credentials_mode: CredentialsMode,
444    pub use_url_credentials: bool,
445
446    /// <https://fetch.spec.whatwg.org/#concept-request-origin>
447    pub origin: Origin,
448
449    /// <https://fetch.spec.whatwg.org/#concept-request-policy-container>
450    pub policy_container: RequestPolicyContainer,
451    pub insecure_requests_policy: InsecureRequestsPolicy,
452    pub has_trustworthy_ancestor_origin: bool,
453
454    /// <https://fetch.spec.whatwg.org/#concept-request-referrer>
455    pub referrer: Referrer,
456
457    /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy>
458    pub referrer_policy: ReferrerPolicy,
459    pub pipeline_id: Option<PipelineId>,
460    pub target_webview_id: Option<WebViewId>,
461
462    /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode>
463    pub redirect_mode: RedirectMode,
464
465    /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata>
466    pub integrity_metadata: String,
467
468    /// <https://fetch.spec.whatwg.org/#concept-request-nonce-metadata>
469    pub cryptographic_nonce_metadata: String,
470
471    // to keep track of redirects
472    pub url_list: Vec<ServoUrl>,
473
474    /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
475    pub parser_metadata: ParserMetadata,
476
477    /// <https://fetch.spec.whatwg.org/#concept-request-initiator>
478    pub initiator: Initiator,
479    pub https_state: HttpsState,
480    pub response_tainting: ResponseTainting,
481    /// Servo internal: if crash details are present, trigger a crash error page with these details.
482    pub crash: Option<String>,
483}
484
485impl RequestBuilder {
486    pub fn new(webview_id: Option<WebViewId>, url: ServoUrl, referrer: Referrer) -> RequestBuilder {
487        RequestBuilder {
488            id: RequestId::default(),
489            preload_id: None,
490            method: Method::GET,
491            url,
492            headers: HeaderMap::new(),
493            unsafe_request: false,
494            body: None,
495            service_workers_mode: ServiceWorkersMode::All,
496            destination: Destination::None,
497            synchronous: false,
498            mode: RequestMode::NoCors,
499            cache_mode: CacheMode::Default,
500            use_cors_preflight: false,
501            keep_alive: false,
502            credentials_mode: CredentialsMode::CredentialsSameOrigin,
503            use_url_credentials: false,
504            origin: Origin::Client,
505            client: None,
506            policy_container: RequestPolicyContainer::default(),
507            insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade,
508            has_trustworthy_ancestor_origin: false,
509            referrer,
510            referrer_policy: ReferrerPolicy::EmptyString,
511            pipeline_id: None,
512            target_webview_id: webview_id,
513            redirect_mode: RedirectMode::Follow,
514            integrity_metadata: "".to_owned(),
515            cryptographic_nonce_metadata: "".to_owned(),
516            url_list: vec![],
517            parser_metadata: ParserMetadata::Default,
518            initiator: Initiator::None,
519            https_state: HttpsState::None,
520            response_tainting: ResponseTainting::Basic,
521            crash: None,
522        }
523    }
524
525    pub fn preload_id(mut self, preload_id: PreloadId) -> RequestBuilder {
526        self.preload_id = Some(preload_id);
527        self
528    }
529
530    /// <https://fetch.spec.whatwg.org/#concept-request-initiator>
531    pub fn initiator(mut self, initiator: Initiator) -> RequestBuilder {
532        self.initiator = initiator;
533        self
534    }
535
536    /// <https://fetch.spec.whatwg.org/#concept-request-method>
537    pub fn method(mut self, method: Method) -> RequestBuilder {
538        self.method = method;
539        self
540    }
541
542    /// <https://fetch.spec.whatwg.org/#concept-request-header-list>
543    pub fn headers(mut self, headers: HeaderMap) -> RequestBuilder {
544        self.headers = headers;
545        self
546    }
547
548    /// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
549    pub fn unsafe_request(mut self, unsafe_request: bool) -> RequestBuilder {
550        self.unsafe_request = unsafe_request;
551        self
552    }
553
554    /// <https://fetch.spec.whatwg.org/#concept-request-body>
555    pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder {
556        self.body = body;
557        self
558    }
559
560    /// <https://fetch.spec.whatwg.org/#concept-request-destination>
561    pub fn destination(mut self, destination: Destination) -> RequestBuilder {
562        self.destination = destination;
563        self
564    }
565
566    pub fn synchronous(mut self, synchronous: bool) -> RequestBuilder {
567        self.synchronous = synchronous;
568        self
569    }
570
571    pub fn mode(mut self, mode: RequestMode) -> RequestBuilder {
572        self.mode = mode;
573        self
574    }
575
576    /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag>
577    pub fn use_cors_preflight(mut self, use_cors_preflight: bool) -> RequestBuilder {
578        self.use_cors_preflight = use_cors_preflight;
579        self
580    }
581
582    /// <https://fetch.spec.whatwg.org/#request-keepalive-flag>
583    pub fn keep_alive(mut self, keep_alive: bool) -> RequestBuilder {
584        self.keep_alive = keep_alive;
585        self
586    }
587
588    /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode>
589    pub fn credentials_mode(mut self, credentials_mode: CredentialsMode) -> RequestBuilder {
590        self.credentials_mode = credentials_mode;
591        self
592    }
593
594    pub fn use_url_credentials(mut self, use_url_credentials: bool) -> RequestBuilder {
595        self.use_url_credentials = use_url_credentials;
596        self
597    }
598
599    /// <https://fetch.spec.whatwg.org/#concept-request-origin>
600    pub fn origin(mut self, origin: ImmutableOrigin) -> RequestBuilder {
601        self.origin = Origin::Origin(origin);
602        self
603    }
604
605    /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy>
606    pub fn referrer_policy(mut self, referrer_policy: ReferrerPolicy) -> RequestBuilder {
607        self.referrer_policy = referrer_policy;
608        self
609    }
610
611    pub fn pipeline_id(mut self, pipeline_id: Option<PipelineId>) -> RequestBuilder {
612        self.pipeline_id = pipeline_id;
613        self
614    }
615
616    /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode>
617    pub fn redirect_mode(mut self, redirect_mode: RedirectMode) -> RequestBuilder {
618        self.redirect_mode = redirect_mode;
619        self
620    }
621
622    /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata>
623    pub fn integrity_metadata(mut self, integrity_metadata: String) -> RequestBuilder {
624        self.integrity_metadata = integrity_metadata;
625        self
626    }
627
628    /// <https://fetch.spec.whatwg.org/#concept-request-nonce-metadata>
629    pub fn cryptographic_nonce_metadata(mut self, nonce_metadata: String) -> RequestBuilder {
630        self.cryptographic_nonce_metadata = nonce_metadata;
631        self
632    }
633
634    /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
635    pub fn parser_metadata(mut self, parser_metadata: ParserMetadata) -> RequestBuilder {
636        self.parser_metadata = parser_metadata;
637        self
638    }
639
640    pub fn https_state(mut self, https_state: HttpsState) -> RequestBuilder {
641        self.https_state = https_state;
642        self
643    }
644
645    pub fn response_tainting(mut self, response_tainting: ResponseTainting) -> RequestBuilder {
646        self.response_tainting = response_tainting;
647        self
648    }
649
650    pub fn crash(mut self, crash: Option<String>) -> Self {
651        self.crash = crash;
652        self
653    }
654
655    /// <https://fetch.spec.whatwg.org/#concept-request-policy-container>
656    pub fn policy_container(mut self, policy_container: PolicyContainer) -> RequestBuilder {
657        self.policy_container = RequestPolicyContainer::PolicyContainer(policy_container);
658        self
659    }
660
661    /// <https://fetch.spec.whatwg.org/#concept-request-client>
662    pub fn client(mut self, client: RequestClient) -> RequestBuilder {
663        self.client = Some(client);
664        self
665    }
666
667    pub fn insecure_requests_policy(
668        mut self,
669        insecure_requests_policy: InsecureRequestsPolicy,
670    ) -> RequestBuilder {
671        self.insecure_requests_policy = insecure_requests_policy;
672        self
673    }
674
675    pub fn has_trustworthy_ancestor_origin(
676        mut self,
677        has_trustworthy_ancestor_origin: bool,
678    ) -> RequestBuilder {
679        self.has_trustworthy_ancestor_origin = has_trustworthy_ancestor_origin;
680        self
681    }
682
683    /// <https://fetch.spec.whatwg.org/#request-service-workers-mode>
684    pub fn service_workers_mode(
685        mut self,
686        service_workers_mode: ServiceWorkersMode,
687    ) -> RequestBuilder {
688        self.service_workers_mode = service_workers_mode;
689        self
690    }
691
692    /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode>
693    pub fn cache_mode(mut self, cache_mode: CacheMode) -> RequestBuilder {
694        self.cache_mode = cache_mode;
695        self
696    }
697
698    pub fn build(self) -> Request {
699        let mut request = Request::new(
700            self.id,
701            self.url.clone(),
702            Some(self.origin),
703            self.referrer,
704            self.pipeline_id,
705            self.target_webview_id,
706            self.https_state,
707        );
708        request.preload_id = self.preload_id;
709        request.initiator = self.initiator;
710        request.method = self.method;
711        request.headers = self.headers;
712        request.unsafe_request = self.unsafe_request;
713        request.body = self.body;
714        request.service_workers_mode = self.service_workers_mode;
715        request.destination = self.destination;
716        request.synchronous = self.synchronous;
717        request.mode = self.mode;
718        request.use_cors_preflight = self.use_cors_preflight;
719        request.keep_alive = self.keep_alive;
720        request.credentials_mode = self.credentials_mode;
721        request.use_url_credentials = self.use_url_credentials;
722        request.cache_mode = self.cache_mode;
723        request.referrer_policy = self.referrer_policy;
724        request.redirect_mode = self.redirect_mode;
725        let mut url_list = self.url_list;
726        if url_list.is_empty() {
727            url_list.push(self.url);
728        }
729        request.redirect_count = url_list.len() as u32 - 1;
730        request.url_list = url_list;
731        request.integrity_metadata = self.integrity_metadata;
732        request.cryptographic_nonce_metadata = self.cryptographic_nonce_metadata;
733        request.parser_metadata = self.parser_metadata;
734        request.response_tainting = self.response_tainting;
735        request.crash = self.crash;
736        request.client = self.client;
737        request.policy_container = self.policy_container;
738        request.insecure_requests_policy = self.insecure_requests_policy;
739        request.has_trustworthy_ancestor_origin = self.has_trustworthy_ancestor_origin;
740        request
741    }
742
743    /// The body length for a keep-alive request. Is 0 if this request is not keep-alive
744    pub fn keep_alive_body_length(&self) -> u64 {
745        assert!(self.keep_alive);
746        self.body.body_length() as u64
747    }
748}
749
750/// A [Request](https://fetch.spec.whatwg.org/#concept-request) as defined by
751/// the Fetch spec.
752#[derive(Clone, MallocSizeOf)]
753pub struct Request {
754    /// The unique id of this request so that the task that triggered it can route
755    /// messages to the correct listeners. This is a UUID that is generated when a request
756    /// is being built.
757    pub id: RequestId,
758    pub preload_id: Option<PreloadId>,
759    /// <https://fetch.spec.whatwg.org/#concept-request-method>
760    #[ignore_malloc_size_of = "Defined in hyper"]
761    pub method: Method,
762    /// <https://fetch.spec.whatwg.org/#local-urls-only-flag>
763    pub local_urls_only: bool,
764    /// <https://fetch.spec.whatwg.org/#concept-request-header-list>
765    #[ignore_malloc_size_of = "Defined in hyper"]
766    pub headers: HeaderMap,
767    /// <https://fetch.spec.whatwg.org/#unsafe-request-flag>
768    pub unsafe_request: bool,
769    /// <https://fetch.spec.whatwg.org/#concept-request-body>
770    pub body: Option<RequestBody>,
771    /// <https://fetch.spec.whatwg.org/#concept-request-client>
772    pub client: Option<RequestClient>,
773    /// <https://fetch.spec.whatwg.org/#concept-request-window>
774    pub traversable_for_user_prompts: TraversableForUserPrompts,
775    pub target_webview_id: Option<WebViewId>,
776    /// <https://fetch.spec.whatwg.org/#request-keepalive-flag>
777    pub keep_alive: bool,
778    /// <https://fetch.spec.whatwg.org/#request-service-workers-mode>
779    pub service_workers_mode: ServiceWorkersMode,
780    /// <https://fetch.spec.whatwg.org/#concept-request-initiator>
781    pub initiator: Initiator,
782    /// <https://fetch.spec.whatwg.org/#concept-request-destination>
783    pub destination: Destination,
784    // TODO: priority object
785    /// <https://fetch.spec.whatwg.org/#concept-request-origin>
786    pub origin: Origin,
787    /// <https://fetch.spec.whatwg.org/#concept-request-referrer>
788    pub referrer: Referrer,
789    /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy>
790    pub referrer_policy: ReferrerPolicy,
791    pub pipeline_id: Option<PipelineId>,
792    /// <https://fetch.spec.whatwg.org/#synchronous-flag>
793    pub synchronous: bool,
794    /// <https://fetch.spec.whatwg.org/#concept-request-mode>
795    pub mode: RequestMode,
796    /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag>
797    pub use_cors_preflight: bool,
798    /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode>
799    pub credentials_mode: CredentialsMode,
800    /// <https://fetch.spec.whatwg.org/#concept-request-use-url-credentials-flag>
801    pub use_url_credentials: bool,
802    /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode>
803    pub cache_mode: CacheMode,
804    /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode>
805    pub redirect_mode: RedirectMode,
806    /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata>
807    pub integrity_metadata: String,
808    /// <https://fetch.spec.whatwg.org/#concept-request-nonce-metadata>
809    pub cryptographic_nonce_metadata: String,
810    // Use the last method on url_list to act as spec current url field, and
811    // first method to act as spec url field
812    /// <https://fetch.spec.whatwg.org/#concept-request-url-list>
813    pub url_list: Vec<ServoUrl>,
814    /// <https://fetch.spec.whatwg.org/#concept-request-redirect-count>
815    pub redirect_count: u32,
816    /// <https://fetch.spec.whatwg.org/#concept-request-response-tainting>
817    pub response_tainting: ResponseTainting,
818    /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata>
819    pub parser_metadata: ParserMetadata,
820    /// <https://fetch.spec.whatwg.org/#concept-request-policy-container>
821    pub policy_container: RequestPolicyContainer,
822    /// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy>
823    pub insecure_requests_policy: InsecureRequestsPolicy,
824    pub has_trustworthy_ancestor_origin: bool,
825    pub https_state: HttpsState,
826    /// Servo internal: if crash details are present, trigger a crash error page with these details.
827    pub crash: Option<String>,
828}
829
830impl Request {
831    pub fn new(
832        id: RequestId,
833        url: ServoUrl,
834        origin: Option<Origin>,
835        referrer: Referrer,
836        pipeline_id: Option<PipelineId>,
837        webview_id: Option<WebViewId>,
838        https_state: HttpsState,
839    ) -> Request {
840        Request {
841            id,
842            preload_id: None,
843            method: Method::GET,
844            local_urls_only: false,
845            headers: HeaderMap::new(),
846            unsafe_request: false,
847            body: None,
848            client: None,
849            traversable_for_user_prompts: TraversableForUserPrompts::Client,
850            keep_alive: false,
851            service_workers_mode: ServiceWorkersMode::All,
852            initiator: Initiator::None,
853            destination: Destination::None,
854            origin: origin.unwrap_or(Origin::Client),
855            referrer,
856            referrer_policy: ReferrerPolicy::EmptyString,
857            pipeline_id,
858            target_webview_id: webview_id,
859            synchronous: false,
860            mode: RequestMode::NoCors,
861            use_cors_preflight: false,
862            credentials_mode: CredentialsMode::CredentialsSameOrigin,
863            use_url_credentials: false,
864            cache_mode: CacheMode::Default,
865            redirect_mode: RedirectMode::Follow,
866            integrity_metadata: String::new(),
867            cryptographic_nonce_metadata: String::new(),
868            url_list: vec![url],
869            parser_metadata: ParserMetadata::Default,
870            redirect_count: 0,
871            response_tainting: ResponseTainting::Basic,
872            policy_container: RequestPolicyContainer::Client,
873            insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade,
874            has_trustworthy_ancestor_origin: false,
875            https_state,
876            crash: None,
877        }
878    }
879
880    /// <https://fetch.spec.whatwg.org/#concept-request-url>
881    pub fn url(&self) -> ServoUrl {
882        self.url_list.first().unwrap().clone()
883    }
884
885    pub fn original_url(&self) -> ServoUrl {
886        match self.mode {
887            RequestMode::WebSocket {
888                protocols: _,
889                ref original_url,
890            } => original_url.clone(),
891            _ => self.url(),
892        }
893    }
894
895    /// <https://fetch.spec.whatwg.org/#concept-request-current-url>
896    pub fn current_url(&self) -> ServoUrl {
897        self.url_list.last().unwrap().clone()
898    }
899
900    /// <https://fetch.spec.whatwg.org/#concept-request-current-url>
901    pub fn current_url_mut(&mut self) -> &mut ServoUrl {
902        self.url_list.last_mut().unwrap()
903    }
904
905    /// <https://fetch.spec.whatwg.org/#navigation-request>
906    pub fn is_navigation_request(&self) -> bool {
907        matches!(
908            self.destination,
909            Destination::Document |
910                Destination::Embed |
911                Destination::Frame |
912                Destination::IFrame |
913                Destination::Object
914        )
915    }
916
917    /// <https://fetch.spec.whatwg.org/#subresource-request>
918    pub fn is_subresource_request(&self) -> bool {
919        matches!(
920            self.destination,
921            Destination::Audio |
922                Destination::Font |
923                Destination::Image |
924                Destination::Manifest |
925                Destination::Script |
926                Destination::Style |
927                Destination::Track |
928                Destination::Video |
929                Destination::Xslt |
930                Destination::None
931        )
932    }
933
934    pub fn timing_type(&self) -> ResourceTimingType {
935        if self.is_navigation_request() {
936            ResourceTimingType::Navigation
937        } else {
938            ResourceTimingType::Resource
939        }
940    }
941
942    /// <https://fetch.spec.whatwg.org/#populate-request-from-client>
943    pub fn populate_request_from_client(&mut self) {
944        // Step 1. If request’s traversable for user prompts is "client":
945        if self.traversable_for_user_prompts == TraversableForUserPrompts::Client {
946            // Step 1.1. Set request’s traversable for user prompts to "no-traversable".
947            self.traversable_for_user_prompts = TraversableForUserPrompts::NoTraversable;
948            // Step 1.2. If request’s client is non-null:
949            if self.client.is_some() {
950                // Step 1.2.1. Let global be request’s client’s global object.
951                // TODO
952                // Step 1.2.2. If global is a Window object and global’s navigable is not null,
953                // then set request’s traversable for user prompts to global’s navigable’s traversable navigable.
954                self.traversable_for_user_prompts =
955                    TraversableForUserPrompts::TraversableNavigable(Default::default());
956            }
957        }
958        // Step 2. If request’s origin is "client":
959        if self.origin == Origin::Client {
960            let Some(client) = self.client.as_ref() else {
961                // Step 2.1. Assert: request’s client is non-null.
962                unreachable!();
963            };
964            // Step 2.2. Set request’s origin to request’s client’s origin.
965            self.origin = client.origin.clone();
966        }
967        // Step 3. If request’s policy container is "client":
968        if matches!(self.policy_container, RequestPolicyContainer::Client) {
969            // Step 3.1. If request’s client is non-null, then set request’s
970            // policy container to a clone of request’s client’s policy container. [HTML]
971            if let Some(client) = self.client.as_ref() {
972                self.policy_container = client.policy_container.clone();
973            } else {
974                // Step 3.2. Otherwise, set request’s policy container to a new policy container.
975                self.policy_container =
976                    RequestPolicyContainer::PolicyContainer(PolicyContainer::default());
977            }
978        }
979    }
980
981    /// The body length for a keep-alive request. Is 0 if this request is not keep-alive
982    pub fn keep_alive_body_length(&self) -> u64 {
983        assert!(self.keep_alive);
984        self.body.body_length() as u64
985    }
986
987    /// <https://fetch.spec.whatwg.org/#total-request-length>
988    pub fn total_request_length(&self) -> usize {
989        // Step 1. Let totalRequestLength be the length of request’s URL, serialized with exclude fragment set to true.
990        let mut total_request_length = self.url()[..Position::AfterQuery].len();
991        // Step 2. Increment totalRequestLength by the length of request’s referrer, serialized.
992        total_request_length += self
993            .referrer
994            .to_url()
995            .map(|url| url.as_str().len())
996            .unwrap_or_default();
997        // Step 3. For each (name, value) of request’s header list, increment totalRequestLength
998        // by name’s length + value’s length.
999        total_request_length += self.headers.total_size();
1000        // Step 4. Increment totalRequestLength by request’s body’s length.
1001        total_request_length += self.body.body_length();
1002        // Step 5. Return totalRequestLength.
1003        total_request_length
1004    }
1005
1006    /// <https://fetch.spec.whatwg.org/#concept-request-tainted-origin>
1007    pub fn redirect_taint_for_request(&self) -> RedirectTaint {
1008        // Step 1. Assert: request’s origin is not "client".
1009        let Origin::Origin(request_origin) = &self.origin else {
1010            unreachable!("origin cannot be \"client\" at this point in time");
1011        };
1012
1013        // Step 2. Let lastURL be null.
1014        let mut last_url = None;
1015
1016        // Step 3. Let taint be "same-origin".
1017        let mut taint = RedirectTaint::SameOrigin;
1018
1019        // Step 4. For each url of request’s URL list:
1020        for url in &self.url_list {
1021            // Step 4.1 If lastURL is null, then set lastURL to url and continue.
1022            let Some(last_url) = &mut last_url else {
1023                last_url = Some(url);
1024                continue;
1025            };
1026
1027            // Step 4.2. If url’s origin is not same site with lastURL’s origin and
1028            // request’s origin is not same site with lastURL’s origin, then return "cross-site".
1029            if !is_same_site(&url.origin(), &last_url.origin()) &&
1030                !is_same_site(request_origin, &last_url.origin())
1031            {
1032                return RedirectTaint::CrossSite;
1033            }
1034
1035            // Step 4.3. If url’s origin is not same origin with lastURL’s origin
1036            // and request’s origin is not same origin with lastURL’s origin, then set taint to "same-site".
1037            if url.origin() != last_url.origin() && *request_origin != last_url.origin() {
1038                taint = RedirectTaint::SameSite;
1039            }
1040
1041            // Step 4.4 Set lastURL to url.
1042            *last_url = url;
1043        }
1044
1045        // Step 5. Return taint.
1046        taint
1047    }
1048}
1049
1050impl Referrer {
1051    pub fn to_url(&self) -> Option<&ServoUrl> {
1052        match *self {
1053            Referrer::NoReferrer => None,
1054            Referrer::Client(ref url) => Some(url),
1055            Referrer::ReferrerUrl(ref url) => Some(url),
1056        }
1057    }
1058}
1059
1060// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte
1061// TODO: values in the control-code range are being quietly stripped out by
1062// HeaderMap and never reach this function to be loudly rejected!
1063fn is_cors_unsafe_request_header_byte(value: &u8) -> bool {
1064    matches!(value,
1065        0x00..=0x08 |
1066        0x10..=0x19 |
1067        0x22 |
1068        0x28 |
1069        0x29 |
1070        0x3A |
1071        0x3C |
1072        0x3E |
1073        0x3F |
1074        0x40 |
1075        0x5B |
1076        0x5C |
1077        0x5D |
1078        0x7B |
1079        0x7D |
1080        0x7F
1081    )
1082}
1083
1084// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
1085// subclause `accept`
1086fn is_cors_safelisted_request_accept(value: &[u8]) -> bool {
1087    !(value.iter().any(is_cors_unsafe_request_header_byte))
1088}
1089
1090// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
1091// subclauses `accept-language`, `content-language`
1092fn is_cors_safelisted_language(value: &[u8]) -> bool {
1093    value.iter().all(|&x| {
1094        matches!(x,
1095            0x30..=0x39 |
1096            0x41..=0x5A |
1097            0x61..=0x7A |
1098            0x20 |
1099            0x2A |
1100            0x2C |
1101            0x2D |
1102            0x2E |
1103            0x3B |
1104            0x3D
1105        )
1106    })
1107}
1108
1109// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
1110// subclause `content-type`
1111pub fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool {
1112    // step 1
1113    if value.iter().any(is_cors_unsafe_request_header_byte) {
1114        return false;
1115    }
1116    // step 2
1117    let value_string = if let Ok(s) = std::str::from_utf8(value) {
1118        s
1119    } else {
1120        return false;
1121    };
1122    let value_mime_result: Result<Mime, _> = value_string.parse();
1123    match value_mime_result {
1124        Err(_) => false, // step 3
1125        Ok(value_mime) => match (value_mime.type_(), value_mime.subtype()) {
1126            (mime::APPLICATION, mime::WWW_FORM_URLENCODED) |
1127            (mime::MULTIPART, mime::FORM_DATA) |
1128            (mime::TEXT, mime::PLAIN) => true,
1129            _ => false, // step 4
1130        },
1131    }
1132}
1133
1134// TODO: "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width":
1135// ... once parsed, the value should not be failure.
1136// https://fetch.spec.whatwg.org/#cors-safelisted-request-header
1137pub fn is_cors_safelisted_request_header<N: AsRef<str>, V: AsRef<[u8]>>(
1138    name: &N,
1139    value: &V,
1140) -> bool {
1141    let name: &str = name.as_ref();
1142    let value: &[u8] = value.as_ref();
1143    if value.len() > 128 {
1144        return false;
1145    }
1146    match name {
1147        "accept" => is_cors_safelisted_request_accept(value),
1148        "accept-language" | "content-language" => is_cors_safelisted_language(value),
1149        "content-type" => is_cors_safelisted_request_content_type(value),
1150        "range" => is_cors_safelisted_request_range(value),
1151        _ => false,
1152    }
1153}
1154
1155pub fn is_cors_safelisted_request_range(value: &[u8]) -> bool {
1156    if let Ok(value_str) = std::str::from_utf8(value) {
1157        return validate_range_header(value_str);
1158    }
1159    false
1160}
1161
1162fn validate_range_header(value: &str) -> bool {
1163    let trimmed = value.trim();
1164    if !trimmed.starts_with("bytes=") {
1165        return false;
1166    }
1167
1168    if let Some(range) = trimmed.strip_prefix("bytes=") {
1169        let mut parts = range.split('-');
1170        let start = parts.next();
1171        let end = parts.next();
1172
1173        if let Some(start) = start {
1174            if let Ok(start_num) = start.parse::<u64>() {
1175                return match end {
1176                    Some(e) if !e.is_empty() => {
1177                        e.parse::<u64>().is_ok_and(|end_num| start_num <= end_num)
1178                    },
1179                    _ => true,
1180                };
1181            }
1182        }
1183    }
1184    false
1185}
1186
1187/// <https://fetch.spec.whatwg.org/#cors-safelisted-method>
1188pub fn is_cors_safelisted_method(method: &Method) -> bool {
1189    matches!(*method, Method::GET | Method::HEAD | Method::POST)
1190}
1191
1192/// <https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name>
1193pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool {
1194    name == AUTHORIZATION
1195}
1196
1197/// <https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names>
1198pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec<HeaderName> {
1199    // Step 1
1200    let mut unsafe_names: Vec<&HeaderName> = vec![];
1201    // Step 2
1202    let mut potentillay_unsafe_names: Vec<&HeaderName> = vec![];
1203    // Step 3
1204    let mut safelist_value_size = 0;
1205
1206    // Step 4
1207    for (name, value) in headers.iter() {
1208        if !is_cors_safelisted_request_header(&name, &value) {
1209            unsafe_names.push(name);
1210        } else {
1211            potentillay_unsafe_names.push(name);
1212            safelist_value_size += value.as_ref().len();
1213        }
1214    }
1215
1216    // Step 5
1217    if safelist_value_size > 1024 {
1218        unsafe_names.extend_from_slice(&potentillay_unsafe_names);
1219    }
1220
1221    // Step 6
1222    convert_header_names_to_sorted_lowercase_set(unsafe_names)
1223}
1224
1225/// <https://fetch.spec.whatwg.org/#ref-for-convert-header-names-to-a-sorted-lowercase-set>
1226pub fn convert_header_names_to_sorted_lowercase_set(
1227    header_names: Vec<&HeaderName>,
1228) -> Vec<HeaderName> {
1229    // HeaderName does not implement the needed traits to use a BTreeSet
1230    // So create a new Vec, sort, then dedup
1231    let mut ordered_set = header_names.to_vec();
1232    ordered_set.sort_by(|a, b| a.as_str().partial_cmp(b.as_str()).unwrap());
1233    ordered_set.dedup();
1234    ordered_set.into_iter().cloned().collect()
1235}
1236
1237pub fn create_request_body_with_content(content: &str) -> RequestBody {
1238    let content_bytes = GenericSharedMemory::from_bytes(content.as_bytes());
1239    let content_len = content_bytes.len();
1240
1241    let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap();
1242    ROUTER.add_typed_route(
1243        chunk_request_receiver,
1244        Box::new(move |message| {
1245            let request = message.unwrap();
1246            if let BodyChunkRequest::Connect(sender) = request {
1247                let _ = sender.send(BodyChunkResponse::Chunk(content_bytes.clone()));
1248                let _ = sender.send(BodyChunkResponse::Done);
1249            }
1250        }),
1251    );
1252
1253    RequestBody::new(chunk_request_sender, BodySource::Object, Some(content_len))
1254}