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