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