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