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