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