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