Skip to main content

net_traits/
request.rs

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