Skip to main content

net_traits/
lib.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
5#![deny(unsafe_code)]
6
7use std::fmt::{self, Debug, Display};
8use std::sync::{LazyLock, OnceLock};
9use std::thread::{self, JoinHandle};
10
11use content_security_policy::{self as csp};
12use cookie::Cookie;
13use crossbeam_channel::{Receiver, Sender, unbounded};
14use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
15use http::{HeaderMap, HeaderValue, StatusCode, header};
16use hyper_serde::Serde;
17use hyper_util::client::legacy::Error as HyperError;
18use ipc_channel::ipc::{self, IpcSender};
19use ipc_channel::router::ROUTER;
20use malloc_size_of::malloc_size_of_is_0;
21use malloc_size_of_derive::MallocSizeOf;
22use mime::Mime;
23use profile_traits::mem::ReportsChan;
24use rand::{RngCore, rng};
25use request::RequestId;
26use rustc_hash::FxHashMap;
27use rustls_pki_types::CertificateDer;
28use serde::{Deserialize, Serialize};
29use servo_base::generic_channel::{
30    self, CallbackSetter, GenericCallback, GenericOneshotSender, GenericSend, GenericSender,
31    SendResult,
32};
33use servo_base::id::{CookieStoreId, HistoryStateId, PipelineId};
34use servo_url::{ImmutableOrigin, ServoUrl};
35use uuid::Uuid;
36
37/// Identifies a pending asynchronous cookie operation initiated by the embedder.
38#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
39pub struct CookieOperationId(pub u64);
40
41use crate::fetch::headers::determine_nosniff;
42use crate::filemanager_thread::FileManagerThreadMsg;
43use crate::http_status::HttpStatus;
44use crate::mime_classifier::{ApacheBugFlag, MimeClassifier};
45use crate::request::{PreloadId, Request, RequestBuilder};
46use crate::response::{Response, ResponseInit};
47
48pub mod blob_url_store;
49pub mod filemanager_thread;
50pub mod http_status;
51pub mod image_cache;
52pub mod mime_classifier;
53pub mod policy_container;
54pub mod pub_domains;
55pub mod quality;
56pub mod request;
57pub(crate) mod resource_fetch_timing;
58pub mod response;
59pub use resource_fetch_timing::{
60    RedirectEndValue, RedirectStartValue, ResourceAttribute, ResourceFetchTiming,
61    ResourceFetchTimingContainer, ResourceTimeValue, ResourceTimingType,
62};
63
64/// <https://fetch.spec.whatwg.org/#document-accept-header-value>
65pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
66    HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
67
68/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
69pub mod fetch {
70    pub mod headers;
71}
72
73/// A loading context, for context-specific sniffing, as defined in
74/// <https://mimesniff.spec.whatwg.org/#context-specific-sniffing>
75#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
76pub enum LoadContext {
77    Browsing,
78    Image,
79    AudioVideo,
80    Plugin,
81    Style,
82    Script,
83    Font,
84    TextTrack,
85    CacheManifest,
86}
87
88#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
89pub struct CustomResponse {
90    #[serde(
91        deserialize_with = "::hyper_serde::deserialize",
92        serialize_with = "::hyper_serde::serialize"
93    )]
94    pub headers: HeaderMap,
95    #[serde(
96        deserialize_with = "::hyper_serde::deserialize",
97        serialize_with = "::hyper_serde::serialize"
98    )]
99    pub raw_status: (StatusCode, String),
100    pub body: Vec<u8>,
101}
102
103impl CustomResponse {
104    pub fn new(
105        headers: HeaderMap,
106        raw_status: (StatusCode, String),
107        body: Vec<u8>,
108    ) -> CustomResponse {
109        CustomResponse {
110            headers,
111            raw_status,
112            body,
113        }
114    }
115}
116
117#[derive(Clone, Debug, Deserialize, Serialize)]
118pub struct CustomResponseMediator {
119    pub response_chan: IpcSender<Option<CustomResponse>>,
120    pub load_url: ServoUrl,
121}
122
123/// [Policies](https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-states)
124/// for providing a referrer header for a request
125#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
126pub enum ReferrerPolicy {
127    /// ""
128    EmptyString,
129    /// "no-referrer"
130    NoReferrer,
131    /// "no-referrer-when-downgrade"
132    NoReferrerWhenDowngrade,
133    /// "origin"
134    Origin,
135    /// "same-origin"
136    SameOrigin,
137    /// "origin-when-cross-origin"
138    OriginWhenCrossOrigin,
139    /// "unsafe-url"
140    UnsafeUrl,
141    /// "strict-origin"
142    StrictOrigin,
143    /// "strict-origin-when-cross-origin"
144    #[default]
145    StrictOriginWhenCrossOrigin,
146}
147
148impl ReferrerPolicy {
149    /// <https://html.spec.whatwg.org/multipage/#meta-referrer>
150    pub fn from_with_legacy(value: &str) -> Self {
151        // Step 5. If value is one of the values given in the first column of the following table,
152        // then set value to the value given in the second column:
153        match value.to_ascii_lowercase().as_str() {
154            "never" => ReferrerPolicy::NoReferrer,
155            "default" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
156            "always" => ReferrerPolicy::UnsafeUrl,
157            "origin-when-crossorigin" => ReferrerPolicy::OriginWhenCrossOrigin,
158            _ => ReferrerPolicy::from(value),
159        }
160    }
161
162    /// <https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header>
163    pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
164        // Step 4. Return policy.
165        headers
166            .as_ref()
167            // Step 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
168            .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
169            // Step 2-3.
170            .into()
171    }
172}
173
174impl From<&str> for ReferrerPolicy {
175    /// <https://html.spec.whatwg.org/multipage/#referrer-policy-attribute>
176    fn from(value: &str) -> Self {
177        match value.to_ascii_lowercase().as_str() {
178            "no-referrer" => ReferrerPolicy::NoReferrer,
179            "no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
180            "origin" => ReferrerPolicy::Origin,
181            "same-origin" => ReferrerPolicy::SameOrigin,
182            "strict-origin" => ReferrerPolicy::StrictOrigin,
183            "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
184            "origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
185            "unsafe-url" => ReferrerPolicy::UnsafeUrl,
186            _ => ReferrerPolicy::EmptyString,
187        }
188    }
189}
190
191impl Display for ReferrerPolicy {
192    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        let string = match self {
194            ReferrerPolicy::EmptyString => "",
195            ReferrerPolicy::NoReferrer => "no-referrer",
196            ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
197            ReferrerPolicy::Origin => "origin",
198            ReferrerPolicy::SameOrigin => "same-origin",
199            ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
200            ReferrerPolicy::UnsafeUrl => "unsafe-url",
201            ReferrerPolicy::StrictOrigin => "strict-origin",
202            ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
203        };
204        write!(formatter, "{string}")
205    }
206}
207
208/// <https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header>
209impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
210    fn from(header: Option<ReferrerPolicyHeader>) -> Self {
211        // Step 2. Let policy be the empty string.
212        // Step 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
213        header.map_or(ReferrerPolicy::EmptyString, |policy| match policy {
214            ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer,
215            ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => {
216                ReferrerPolicy::NoReferrerWhenDowngrade
217            },
218            ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin,
219            ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin,
220            ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin,
221            ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl,
222            ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin,
223            ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => {
224                ReferrerPolicy::StrictOriginWhenCrossOrigin
225            },
226        })
227    }
228}
229
230impl From<ReferrerPolicy> for ReferrerPolicyHeader {
231    fn from(referrer_policy: ReferrerPolicy) -> Self {
232        match referrer_policy {
233            ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER,
234            ReferrerPolicy::NoReferrerWhenDowngrade => {
235                ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE
236            },
237            ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN,
238            ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN,
239            ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN,
240            ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL,
241            ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN,
242            ReferrerPolicy::EmptyString | ReferrerPolicy::StrictOriginWhenCrossOrigin => {
243                ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
244            },
245        }
246    }
247}
248
249// FIXME: https://github.com/servo/servo/issues/34591
250#[expect(clippy::large_enum_variant)]
251#[derive(Debug, Deserialize, Serialize)]
252pub enum FetchResponseMsg {
253    // todo: should have fields for transmitted/total bytes
254    ProcessRequestBody(RequestId),
255    // todo: send more info about the response (or perhaps the entire Response)
256    ProcessResponse(RequestId, Result<FetchMetadata, NetworkError>),
257    ProcessResponseChunk(RequestId, DebugVec),
258    ProcessResponseEOF(RequestId, Result<(), NetworkError>, ResourceFetchTiming),
259    ProcessCspViolations(RequestId, Vec<csp::Violation>),
260}
261
262#[derive(Deserialize, PartialEq, Serialize, MallocSizeOf)]
263pub struct DebugVec(pub Vec<u8>);
264
265impl From<Vec<u8>> for DebugVec {
266    fn from(v: Vec<u8>) -> Self {
267        Self(v)
268    }
269}
270
271impl std::ops::Deref for DebugVec {
272    type Target = Vec<u8>;
273    fn deref(&self) -> &Self::Target {
274        &self.0
275    }
276}
277
278impl std::fmt::Debug for DebugVec {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        f.write_fmt(format_args!("[...; {}]", self.0.len()))
281    }
282}
283
284impl FetchResponseMsg {
285    pub fn request_id(&self) -> RequestId {
286        match self {
287            FetchResponseMsg::ProcessRequestBody(id) |
288            FetchResponseMsg::ProcessResponse(id, ..) |
289            FetchResponseMsg::ProcessResponseChunk(id, ..) |
290            FetchResponseMsg::ProcessResponseEOF(id, ..) |
291            FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
292        }
293    }
294}
295
296pub trait FetchTaskTarget {
297    /// <https://fetch.spec.whatwg.org/#process-request-body>
298    ///
299    /// Fired when a chunk of the request body is transmitted
300    fn process_request_body(&mut self, request: &Request);
301
302    /// <https://fetch.spec.whatwg.org/#process-response>
303    ///
304    /// Fired when headers are received
305    fn process_response(&mut self, request: &Request, response: &Response);
306
307    /// Fired when a chunk of response content is received
308    fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
309
310    /// <https://fetch.spec.whatwg.org/#process-response-end-of-file>
311    ///
312    /// Fired when the response is fully fetched
313    fn process_response_eof(&mut self, request: &Request, response: &Response);
314
315    fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
316}
317
318#[derive(Clone, Debug, Deserialize, Serialize)]
319pub enum FilteredMetadata {
320    Basic(Metadata),
321    Cors(Metadata),
322    Opaque,
323    OpaqueRedirect(ServoUrl),
324}
325
326// FIXME: https://github.com/servo/servo/issues/34591
327#[expect(clippy::large_enum_variant)]
328#[derive(Clone, Debug, Deserialize, Serialize)]
329pub enum FetchMetadata {
330    Unfiltered(Metadata),
331    Filtered {
332        filtered: FilteredMetadata,
333        unsafe_: Metadata,
334    },
335}
336
337impl FetchMetadata {
338    pub fn metadata(&self) -> &Metadata {
339        match self {
340            Self::Unfiltered(metadata) => metadata,
341            Self::Filtered { unsafe_, .. } => unsafe_,
342        }
343    }
344
345    /// <https://html.spec.whatwg.org/multipage/#cors-cross-origin>
346    pub fn is_cors_cross_origin(&self) -> bool {
347        if let Self::Filtered { filtered, .. } = self {
348            match filtered {
349                FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_) => false,
350                FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_) => true,
351            }
352        } else {
353            false
354        }
355    }
356}
357
358impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
359    fn process_request_body(&mut self, request: &Request) {
360        let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
361    }
362
363    fn process_response(&mut self, request: &Request, response: &Response) {
364        let _ = self.send(FetchResponseMsg::ProcessResponse(
365            request.id,
366            response.metadata(),
367        ));
368    }
369
370    fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
371        let _ = self.send(FetchResponseMsg::ProcessResponseChunk(
372            request.id,
373            chunk.into(),
374        ));
375    }
376
377    fn process_response_eof(&mut self, request: &Request, response: &Response) {
378        let result = response
379            .get_network_error()
380            .map_or_else(|| Ok(()), |network_error| Err(network_error.clone()));
381        let timing = response.get_resource_timing().inner().clone();
382
383        let _ = self.send(FetchResponseMsg::ProcessResponseEOF(
384            request.id, result, timing,
385        ));
386    }
387
388    fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
389        let _ = self.send(FetchResponseMsg::ProcessCspViolations(
390            request.id, violations,
391        ));
392    }
393}
394
395#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
396#[serde(rename_all = "lowercase")]
397pub enum TlsSecurityState {
398    /// The connection used to fetch this resource was not secure.
399    #[default]
400    Insecure,
401    /// This resource was transferred over a connection that used weak encryption.
402    Weak,
403    /// A security error prevented the resource from being loaded.
404    Broken,
405    /// The connection used to fetch this resource was secure.
406    Secure,
407}
408
409impl Display for TlsSecurityState {
410    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411        let text = match self {
412            TlsSecurityState::Insecure => "insecure",
413            TlsSecurityState::Weak => "weak",
414            TlsSecurityState::Broken => "broken",
415            TlsSecurityState::Secure => "secure",
416        };
417        f.write_str(text)
418    }
419}
420
421#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
422pub struct TlsSecurityInfo {
423    // "insecure", "weak", "broken", "secure".
424    #[serde(default)]
425    pub state: TlsSecurityState,
426    // Reasons explaining why the negotiated parameters are considered weak.
427    pub weakness_reasons: Vec<String>,
428    // Negotiated TLS protocol version (e.g. "TLS 1.3").
429    pub protocol_version: Option<String>,
430    // Negotiated cipher suite identifier.
431    pub cipher_suite: Option<String>,
432    // Negotiated key exchange group.
433    pub kea_group_name: Option<String>,
434    // Signature scheme used for certificate verification.
435    pub signature_scheme_name: Option<String>,
436    // Negotiated ALPN protocol (e.g. "h2" for HTTP/2, "http/1.1" for HTTP/1.1).
437    pub alpn_protocol: Option<String>,
438    // Server certificate chain encoded as DER bytes, leaf first.
439    pub certificate_chain_der: Vec<Vec<u8>>,
440    // Certificate Transparency status, if provided.
441    pub certificate_transparency: Option<String>,
442    // HTTP Strict Transport Security flag.
443    pub hsts: bool,
444    // HTTP Public Key Pinning flag (always false, kept for parity).
445    pub hpkp: bool,
446    // Encrypted Client Hello usage flag.
447    pub used_ech: bool,
448    // Delegated credentials usage flag.
449    pub used_delegated_credentials: bool,
450    // OCSP stapling usage flag.
451    pub used_ocsp: bool,
452    // Private DNS usage flag.
453    pub used_private_dns: bool,
454}
455
456impl FetchTaskTarget for IpcSender<WebSocketNetworkEvent> {
457    fn process_request_body(&mut self, _: &Request) {}
458    fn process_response(&mut self, _: &Request, response: &Response) {
459        if response.is_network_error() {
460            let _ = self.send(WebSocketNetworkEvent::Fail);
461        }
462    }
463    fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
464    fn process_response_eof(&mut self, _: &Request, _: &Response) {}
465    fn process_csp_violations(&mut self, _: &Request, violations: Vec<csp::Violation>) {
466        let _ = self.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
467    }
468}
469
470/// A fetch task that discards all data it's sent,
471/// useful when speculatively prefetching data that we don't need right
472/// now, but might need in the future.
473pub struct DiscardFetch;
474
475impl FetchTaskTarget for DiscardFetch {
476    fn process_request_body(&mut self, _: &Request) {}
477    fn process_response(&mut self, _: &Request, _: &Response) {}
478    fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
479    fn process_response_eof(&mut self, _: &Request, _: &Response) {}
480    fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
481}
482
483/// Handle to an async runtime,
484/// only used to shut it down for now.
485pub trait AsyncRuntime: Send {
486    fn shutdown(&mut self);
487}
488
489/// Handle to a resource thread
490pub type CoreResourceThread = GenericSender<CoreResourceMsg>;
491
492// FIXME: Originally we will construct an Arc<ResourceThread> from ResourceThread
493// in script_thread to avoid some performance pitfall. Now we decide to deal with
494// the "Arc" hack implicitly in future.
495// See discussion: http://logs.glob.uno/?c=mozilla%23servo&s=16+May+2016&e=16+May+2016#c430412
496// See also: https://github.com/servo/servo/blob/735480/components/script/script_thread.rs#L313
497#[derive(Clone, Debug, Deserialize, Serialize)]
498pub struct ResourceThreads {
499    pub core_thread: CoreResourceThread,
500}
501
502impl ResourceThreads {
503    pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
504        ResourceThreads { core_thread }
505    }
506
507    pub fn cache_entries(&self) -> Vec<CacheEntryDescriptor> {
508        let (sender, receiver) = generic_channel::channel().unwrap();
509        let _ = self
510            .core_thread
511            .send(CoreResourceMsg::GetCacheEntries(sender));
512        receiver.recv().unwrap()
513    }
514
515    pub fn clear_cache(&self) {
516        // NOTE: Messages used in these methods are currently handled
517        // synchronously on the backend without consulting other threads, so
518        // waiting for the response here cannot deadlock. If the backend
519        // handling ever becomes asynchronous or involves sending messages
520        // back to the originating thread, this code will need to be revisited
521        // to avoid potential deadlocks.
522        let (sender, receiver) = generic_channel::channel().unwrap();
523        let _ = self
524            .core_thread
525            .send(CoreResourceMsg::ClearCache(Some(sender)));
526        let _ = receiver.recv();
527    }
528
529    pub fn cookies(&self) -> Vec<SiteDescriptor> {
530        let (sender, receiver) = generic_channel::channel().unwrap();
531        let _ = self.core_thread.send(CoreResourceMsg::ListCookies(sender));
532        receiver.recv().unwrap()
533    }
534
535    pub fn clear_cookies_for_sites(&self, sites: &[&str]) {
536        let sites = sites.iter().map(|site| site.to_string()).collect();
537        let (sender, receiver) = generic_channel::channel().unwrap();
538        let _ = self
539            .core_thread
540            .send(CoreResourceMsg::DeleteCookiesForSites(sites, sender));
541        let _ = receiver.recv();
542    }
543
544    pub fn clear_cookies(&self) {
545        let (sender, receiver) = ipc::channel().unwrap();
546        let _ = self
547            .core_thread
548            .send(CoreResourceMsg::DeleteCookies(None, Some(sender)));
549        let _ = receiver.recv();
550    }
551
552    pub fn cookies_for_url(&self, url: ServoUrl, source: CookieSource) -> Vec<Cookie<'static>> {
553        let (sender, receiver) = generic_channel::channel().unwrap();
554        let _ = self
555            .core_thread
556            .send(CoreResourceMsg::GetCookiesForUrl(url, sender, source));
557        receiver
558            .recv()
559            .unwrap()
560            .into_iter()
561            .map(|cookie| cookie.into_inner())
562            .collect()
563    }
564
565    pub fn clear_session_cookies(&self) {
566        let (sender, receiver) = generic_channel::channel().unwrap();
567        let _ = self
568            .core_thread
569            .send(CoreResourceMsg::DeleteSessionCookies(sender));
570        let _ = receiver.recv();
571    }
572
573    pub fn set_cookie_for_url(&self, url: ServoUrl, cookie: Cookie<'static>, source: CookieSource) {
574        let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
575            url,
576            Serde(cookie),
577            source,
578            None,
579        ));
580    }
581
582    pub fn set_cookie_for_url_sync(
583        &self,
584        url: ServoUrl,
585        cookie: Cookie<'static>,
586        source: CookieSource,
587    ) {
588        let (sender, receiver) = generic_channel::channel().unwrap();
589        let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
590            url,
591            Serde(cookie),
592            source,
593            Some(sender),
594        ));
595        let _ = receiver.recv();
596    }
597
598    pub fn cookies_for_url_async(
599        &self,
600        id: CookieOperationId,
601        url: ServoUrl,
602        source: CookieSource,
603    ) {
604        let _ = self
605            .core_thread
606            .send(CoreResourceMsg::EmbedderGetCookiesForUrl(id, url, source));
607    }
608
609    pub fn set_cookie_for_url_async(
610        &self,
611        id: CookieOperationId,
612        url: ServoUrl,
613        cookie: Cookie<'static>,
614        source: CookieSource,
615    ) {
616        let _ = self
617            .core_thread
618            .send(CoreResourceMsg::EmbedderSetCookieForUrl(
619                id,
620                url,
621                Serde(cookie),
622                source,
623            ));
624    }
625}
626
627impl GenericSend<CoreResourceMsg> for ResourceThreads {
628    fn send(&self, msg: CoreResourceMsg) -> SendResult {
629        self.core_thread.send(msg)
630    }
631
632    fn sender(&self) -> GenericSender<CoreResourceMsg> {
633        self.core_thread.clone()
634    }
635}
636
637// Ignore the sub-fields
638malloc_size_of_is_0!(ResourceThreads);
639
640#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
641pub enum IncludeSubdomains {
642    Included,
643    NotIncluded,
644}
645
646#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
647pub enum MessageData {
648    Text(String),
649    Binary(Vec<u8>),
650}
651
652#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
653pub enum WebSocketDomAction {
654    SendMessage(MessageData),
655    Close(Option<u16>, Option<String>),
656}
657
658#[derive(Debug, Deserialize, Serialize)]
659pub enum WebSocketNetworkEvent {
660    ReportCSPViolations(Vec<csp::Violation>),
661    ConnectionEstablished { protocol_in_use: Option<String> },
662    MessageReceived(MessageData),
663    Close(Option<u16>, String),
664    Fail,
665}
666
667#[derive(Debug, Deserialize, Serialize)]
668/// IPC channels to communicate with the script thread about network or DOM events.
669pub enum FetchChannels {
670    ResponseMsg(IpcSender<FetchResponseMsg>),
671    WebSocket {
672        event_sender: IpcSender<WebSocketNetworkEvent>,
673        action_receiver: CallbackSetter<WebSocketDomAction>,
674    },
675    /// If the fetch is just being done to populate the cache,
676    /// not because the data is needed now.
677    Prefetch,
678}
679
680#[derive(Debug, Deserialize, Serialize)]
681pub enum CoreResourceMsg {
682    Fetch(RequestBuilder, FetchChannels),
683    Cancel(Vec<RequestId>),
684    /// Initiate a fetch in response to processing a redirection
685    FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
686    /// Store a cookie for a given originating URL.
687    /// If a sender is provided, the caller will block until the cookie is stored.
688    SetCookieForUrl(
689        ServoUrl,
690        Serde<Cookie<'static>>,
691        CookieSource,
692        Option<GenericSender<()>>,
693    ),
694    /// Store a set of cookies for a given originating URL
695    SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
696    SetCookieForUrlAsync(
697        CookieStoreId,
698        ServoUrl,
699        Serde<Cookie<'static>>,
700        CookieSource,
701    ),
702    /// Retrieve the stored cookies as a header string for a given URL.
703    GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
704    /// Retrieve the stored cookies as a vector for the given URL.
705    /// The response is sent via the provided sender.
706    GetCookiesForUrl(
707        ServoUrl,
708        GenericSender<Vec<Serde<Cookie<'static>>>>,
709        CookieSource,
710    ),
711    /// Retrieve cookies for a URL for embedder. The response is
712    /// sent via [`NetToEmbedderMsg::EmbedderGetCookiesForUrlResponse`].
713    EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
714    /// Set a cookie for a URL on behalf of the embedder. The response is
715    /// sent via [`NetToEmbedderMsg::EmbedderSetCookieForUrlResponse`].
716    EmbedderSetCookieForUrl(
717        CookieOperationId,
718        ServoUrl,
719        Serde<Cookie<'static>>,
720        CookieSource,
721    ),
722    GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
723    GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
724    DeleteCookiesForSites(Vec<String>, GenericSender<()>),
725    /// This currently is used by unit tests and WebDriver only.
726    /// When url is `None`, this clears cookies across all origins.
727    DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
728    /// Delete all session cookies (cookies without an expiry or max-age).
729    DeleteSessionCookies(GenericSender<()>),
730    DeleteCookie(ServoUrl, String),
731    DeleteCookieAsync(CookieStoreId, ServoUrl, String),
732    NewCookieListener(
733        CookieStoreId,
734        GenericCallback<CookieAsyncResponse>,
735        ServoUrl,
736    ),
737    RemoveCookieListener(CookieStoreId),
738    ListCookies(GenericSender<Vec<SiteDescriptor>>),
739    /// Get a history state by a given history state id
740    GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
741    /// Set a history state for a given history state id
742    SetHistoryState(HistoryStateId, Vec<u8>),
743    /// Removes history states for the given ids
744    RemoveHistoryStates(Vec<HistoryStateId>),
745    /// Gets a list of origin descriptors derived from entries in the cache
746    GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
747    /// Clear the network cache.
748    ClearCache(Option<GenericSender<()>>),
749    /// Send the service worker network mediator for an origin to CoreResourceThread
750    NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
751    /// Message forwarded to file manager's handler
752    ToFileManager(FileManagerThreadMsg),
753    StorePreloadedResponse(PreloadId, Response),
754    TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
755    /// Break the load handler loop, send a reply when done cleaning up local resources
756    /// and exit
757    Exit(GenericOneshotSender<()>),
758    CollectMemoryReport(ReportsChan),
759    RevokeTokenForFile(BlobTokenRevocationRequest),
760    RefreshTokenForFile(BlobTokenRefreshRequest),
761}
762
763#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
764pub struct BlobTokenRevocationRequest {
765    pub blob_id: Uuid,
766    pub token: Uuid,
767}
768
769#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
770pub struct BlobTokenRefreshRequest {
771    pub blob_id: Uuid,
772    pub new_token_sender: GenericSender<Uuid>,
773}
774
775#[derive(Clone, Debug, Deserialize, Serialize)]
776pub struct SiteDescriptor {
777    pub name: String,
778}
779
780impl SiteDescriptor {
781    pub fn new(name: String) -> Self {
782        SiteDescriptor { name }
783    }
784}
785
786#[derive(Clone, Debug, Deserialize, Serialize)]
787pub struct CacheEntryDescriptor {
788    pub key: String,
789}
790
791impl CacheEntryDescriptor {
792    pub fn new(key: String) -> Self {
793        Self { key }
794    }
795}
796
797// FIXME: https://github.com/servo/servo/issues/34591
798#[expect(clippy::large_enum_variant)]
799enum ToFetchThreadMessage {
800    Cancel(Vec<RequestId>, CoreResourceThread),
801    StartFetch(
802        /* request_builder */ RequestBuilder,
803        /* response_init */ Option<ResponseInit>,
804        /* callback  */ BoxedFetchCallback,
805        /* core resource thread channel */ CoreResourceThread,
806    ),
807    FetchResponse(FetchResponseMsg),
808    /// Stop the background thread.
809    Exit,
810}
811
812pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
813
814/// A thread to handle fetches in a Servo process. This thread is responsible for
815/// listening for new fetch requests as well as updates on those operations and forwarding
816/// them to crossbeam channels.
817struct FetchThread {
818    /// A list of active fetches. A fetch is no longer active once the
819    /// [`FetchResponseMsg::ProcessResponseEOF`] is received.
820    active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
821    /// A crossbeam receiver attached to the router proxy which converts incoming fetch
822    /// updates from IPC messages to crossbeam messages as well as another sender which
823    /// handles requests from clients wanting to do fetches.
824    receiver: Receiver<ToFetchThreadMessage>,
825    /// An [`IpcSender`] that's sent with every fetch request and leads back to our
826    /// router proxy.
827    to_fetch_sender: IpcSender<FetchResponseMsg>,
828}
829
830impl FetchThread {
831    fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
832        let (sender, receiver) = unbounded();
833        let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
834
835        let sender_clone = sender.clone();
836        ROUTER.add_typed_route(
837            from_fetch_sender,
838            Box::new(move |message| {
839                let message: FetchResponseMsg = message.unwrap();
840                let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
841            }),
842        );
843        let join_handle = thread::Builder::new()
844            .name("FetchThread".to_owned())
845            .spawn(move || {
846                let mut fetch_thread = FetchThread {
847                    active_fetches: FxHashMap::default(),
848                    receiver,
849                    to_fetch_sender,
850                };
851                fetch_thread.run();
852            })
853            .expect("Thread spawning failed");
854        (sender, join_handle)
855    }
856
857    fn run(&mut self) {
858        loop {
859            match self.receiver.recv().unwrap() {
860                ToFetchThreadMessage::StartFetch(
861                    request_builder,
862                    response_init,
863                    callback,
864                    core_resource_thread,
865                ) => {
866                    let request_builder_id = request_builder.id;
867
868                    // Only redirects have a `response_init` field.
869                    let message = match response_init {
870                        Some(response_init) => CoreResourceMsg::FetchRedirect(
871                            request_builder,
872                            response_init,
873                            self.to_fetch_sender.clone(),
874                        ),
875                        None => CoreResourceMsg::Fetch(
876                            request_builder,
877                            FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
878                        ),
879                    };
880
881                    core_resource_thread.send(message).unwrap();
882
883                    let preexisting_fetch =
884                        self.active_fetches.insert(request_builder_id, callback);
885                    // When we terminate a fetch group, all deferred fetches are processed.
886                    // In case we were already processing a deferred fetch, we should not
887                    // process the second call. This should be handled by [`DeferredFetchRecord::process`]
888                    assert!(preexisting_fetch.is_none());
889                },
890                ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
891                    let request_id = fetch_response_msg.request_id();
892                    let fetch_finished =
893                        matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
894
895                    self.active_fetches
896                        .get_mut(&request_id)
897                        .expect("Got fetch response for unknown fetch")(
898                        fetch_response_msg
899                    );
900
901                    if fetch_finished {
902                        self.active_fetches.remove(&request_id);
903                    }
904                },
905                ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
906                    // Errors are ignored here, because Servo sends many cancellation requests when shutting down.
907                    // At this point the networking task might be shut down completely, so just ignore errors
908                    // during this time.
909                    let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
910                },
911                ToFetchThreadMessage::Exit => break,
912            }
913        }
914    }
915}
916
917static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
918
919/// Start the fetch thread,
920/// and returns the join handle to the background thread.
921pub fn start_fetch_thread() -> JoinHandle<()> {
922    let (sender, join_handle) = FetchThread::spawn();
923    FETCH_THREAD
924        .set(sender)
925        .expect("Fetch thread should be set only once on start-up");
926    join_handle
927}
928
929/// Send the exit message to the background thread,
930/// after which the caller can,
931/// and should,
932/// join on the thread.
933pub fn exit_fetch_thread() {
934    let _ = FETCH_THREAD
935        .get()
936        .expect("Fetch thread should always be initialized on start-up")
937        .send(ToFetchThreadMessage::Exit);
938}
939
940/// Instruct the resource thread to make a new fetch request.
941pub fn fetch_async(
942    core_resource_thread: &CoreResourceThread,
943    request: RequestBuilder,
944    response_init: Option<ResponseInit>,
945    callback: BoxedFetchCallback,
946) {
947    let _ = FETCH_THREAD
948        .get()
949        .expect("Fetch thread should always be initialized on start-up")
950        .send(ToFetchThreadMessage::StartFetch(
951            request,
952            response_init,
953            callback,
954            core_resource_thread.clone(),
955        ));
956}
957
958/// Instruct the resource thread to cancel an existing request. Does nothing if the
959/// request has already completed or has not been fetched yet.
960pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
961    let _ = FETCH_THREAD
962        .get()
963        .expect("Fetch thread should always be initialized on start-up")
964        .send(ToFetchThreadMessage::Cancel(
965            request_ids,
966            core_resource_thread.clone(),
967        ));
968}
969
970#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
971pub struct ResourceCorsData {
972    /// CORS Preflight flag
973    pub preflight: bool,
974    /// Origin of CORS Request
975    pub origin: ServoUrl,
976}
977
978/// Metadata about a loaded resource, such as is obtained from HTTP headers.
979#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
980pub struct Metadata {
981    /// Final URL after redirects.
982    pub final_url: ServoUrl,
983
984    /// Location URL from the response headers.
985    pub location_url: Option<Result<ServoUrl, String>>,
986
987    #[ignore_malloc_size_of = "Defined in hyper"]
988    /// MIME type / subtype.
989    pub content_type: Option<Serde<ContentType>>,
990
991    /// Character set.
992    pub charset: Option<String>,
993
994    #[ignore_malloc_size_of = "Defined in hyper"]
995    /// Headers
996    pub headers: Option<Serde<HeaderMap>>,
997
998    /// HTTP Status
999    pub status: HttpStatus,
1000
1001    /// Referrer Url
1002    pub referrer: Option<ServoUrl>,
1003
1004    /// Referrer Policy of the Request used to obtain Response
1005    pub referrer_policy: ReferrerPolicy,
1006    /// Performance information for navigation events
1007    pub timing: Option<ResourceFetchTiming>,
1008    /// True if the request comes from a redirection
1009    pub redirected: bool,
1010    /// Detailed TLS metadata associated with the response, if any.
1011    pub tls_security_info: Option<TlsSecurityInfo>,
1012}
1013
1014impl Metadata {
1015    /// Metadata with defaults for everything optional.
1016    pub fn default(url: ServoUrl) -> Self {
1017        Metadata {
1018            final_url: url,
1019            location_url: None,
1020            content_type: None,
1021            charset: None,
1022            headers: None,
1023            status: HttpStatus::default(),
1024            referrer: None,
1025            referrer_policy: ReferrerPolicy::EmptyString,
1026            timing: None,
1027            redirected: false,
1028            tls_security_info: None,
1029        }
1030    }
1031
1032    /// Extract the parts of a Mime that we care about.
1033    pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1034        if self.headers.is_none() {
1035            self.headers = Some(Serde(HeaderMap::new()));
1036        }
1037
1038        if let Some(mime) = content_type {
1039            self.headers
1040                .as_mut()
1041                .unwrap()
1042                .typed_insert(ContentType::from(mime.clone()));
1043            if let Some(charset) = mime.get_param(mime::CHARSET) {
1044                self.charset = Some(charset.to_string());
1045            }
1046            self.content_type = Some(Serde(ContentType::from(mime.clone())));
1047        }
1048    }
1049
1050    /// Set the referrer policy associated with the loaded resource.
1051    pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1052        if referrer_policy == ReferrerPolicy::EmptyString {
1053            return;
1054        }
1055
1056        if self.headers.is_none() {
1057            self.headers = Some(Serde(HeaderMap::new()));
1058        }
1059
1060        self.referrer_policy = referrer_policy;
1061
1062        self.headers
1063            .as_mut()
1064            .unwrap()
1065            .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1066    }
1067
1068    /// <https://html.spec.whatwg.org/multipage/#content-type>
1069    pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1070        // The Content-Type metadata of a resource must be obtained and interpreted in a manner consistent with the requirements of MIME Sniffing. [MIMESNIFF]
1071        let no_sniff = self
1072            .headers
1073            .as_deref()
1074            .is_some_and(determine_nosniff)
1075            .into();
1076        let mime = self
1077            .content_type
1078            .clone()
1079            .map(|content_type| content_type.into_inner().into());
1080        MimeClassifier::default().classify(
1081            load_context,
1082            no_sniff,
1083            ApacheBugFlag::from_content_type(mime.as_ref()),
1084            &mime,
1085            data,
1086        )
1087    }
1088}
1089
1090/// The creator of a given cookie
1091#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1092pub enum CookieSource {
1093    /// An HTTP API
1094    HTTP,
1095    /// A non-HTTP API
1096    NonHTTP,
1097}
1098
1099#[derive(Clone, Debug, Deserialize, Serialize)]
1100pub struct CookieChange {
1101    changed: Vec<Serde<Cookie<'static>>>,
1102    deleted: Vec<Serde<Cookie<'static>>>,
1103}
1104
1105#[derive(Clone, Debug, Deserialize, Serialize)]
1106pub enum CookieData {
1107    Change(CookieChange),
1108    Get(Option<Serde<Cookie<'static>>>),
1109    GetAll(Vec<Serde<Cookie<'static>>>),
1110    Set(Result<(), ()>),
1111    Delete(Result<(), ()>),
1112}
1113
1114#[derive(Clone, Debug, Deserialize, Serialize)]
1115pub struct CookieAsyncResponse {
1116    pub data: CookieData,
1117}
1118
1119/// Network errors that have to be exported out of the loaders
1120#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1121pub enum NetworkError {
1122    LoadCancelled,
1123    /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser.
1124    SslValidation(String, Vec<u8>),
1125    /// Crash error, to be converted to Resource::Crash in the HTML parser.
1126    Crash(String),
1127    UnsupportedScheme,
1128    CorsGeneral,
1129    CrossOriginResponse,
1130    CorsCredentials,
1131    CorsAllowMethods,
1132    CorsAllowHeaders,
1133    CorsMethod,
1134    CorsAuthorization,
1135    CorsHeaders,
1136    ConnectionFailure,
1137    RedirectError,
1138    TooManyRedirects,
1139    TooManyInFlightKeepAliveRequests,
1140    InvalidMethod,
1141    ResourceLoadError(String),
1142    ContentSecurityPolicy,
1143    Nosniff,
1144    MimeType(String),
1145    SubresourceIntegrity,
1146    MixedContent,
1147    CacheError,
1148    InvalidPort,
1149    WebsocketConnectionFailure(String),
1150    LocalDirectoryError,
1151    PartialResponseToNonRangeRequestError,
1152    ProtocolHandlerSubstitutionError,
1153    BlobURLStoreError(String),
1154    HttpError(String),
1155    DecompressionError,
1156}
1157
1158impl fmt::Debug for NetworkError {
1159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1160        match self {
1161            NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1162            NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1163            NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1164            NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1165            NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1166            NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1167            NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1168            NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1169            NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1170            NetworkError::ConnectionFailure => write!(f, "Request failed"),
1171            NetworkError::RedirectError => write!(f, "Redirect failed"),
1172            NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1173            NetworkError::TooManyInFlightKeepAliveRequests => {
1174                write!(f, "Too many in flight keep-alive requests")
1175            },
1176            NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1177            NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1178            NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1179            NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1180            NetworkError::MimeType(s) => write!(f, "{}", s),
1181            NetworkError::SubresourceIntegrity => {
1182                write!(f, "Subresource integrity validation failed")
1183            },
1184            NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1185            NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1186            NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1187            NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1188            NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1189            NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1190            NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1191            NetworkError::PartialResponseToNonRangeRequestError => write!(
1192                f,
1193                "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1194            ),
1195            NetworkError::ProtocolHandlerSubstitutionError => {
1196                write!(f, "Failed to parse substituted protocol handler url")
1197            },
1198            NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1199            NetworkError::WebsocketConnectionFailure(s) => {
1200                write!(f, "Websocket connection failure: {}", s)
1201            },
1202            NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1203            NetworkError::DecompressionError => write!(f, "Decompression error"),
1204        }
1205    }
1206}
1207
1208impl NetworkError {
1209    pub fn is_permanent_failure(&self) -> bool {
1210        matches!(
1211            self,
1212            NetworkError::ContentSecurityPolicy |
1213                NetworkError::MixedContent |
1214                NetworkError::SubresourceIntegrity |
1215                NetworkError::Nosniff |
1216                NetworkError::InvalidPort |
1217                NetworkError::CorsGeneral |
1218                NetworkError::CrossOriginResponse |
1219                NetworkError::CorsCredentials |
1220                NetworkError::CorsAllowMethods |
1221                NetworkError::CorsAllowHeaders |
1222                NetworkError::CorsMethod |
1223                NetworkError::CorsAuthorization |
1224                NetworkError::CorsHeaders |
1225                NetworkError::UnsupportedScheme
1226        )
1227    }
1228
1229    pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1230        let error_string = error.to_string();
1231        match certificate {
1232            Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1233            _ => NetworkError::HttpError(error_string),
1234        }
1235    }
1236}
1237
1238/// Normalize `slice`, as defined by
1239/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize).
1240pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1241    const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1242
1243    loop {
1244        match slice.split_first() {
1245            Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1246            _ => break,
1247        }
1248    }
1249
1250    loop {
1251        match slice.split_last() {
1252            Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1253            _ => break,
1254        }
1255    }
1256
1257    slice
1258}
1259
1260pub fn http_percent_encode(bytes: &[u8]) -> String {
1261    // This encode set is used for HTTP header values and is defined at
1262    // https://tools.ietf.org/html/rfc5987#section-3.2
1263    const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1264        .add(b' ')
1265        .add(b'"')
1266        .add(b'%')
1267        .add(b'\'')
1268        .add(b'(')
1269        .add(b')')
1270        .add(b'*')
1271        .add(b',')
1272        .add(b'/')
1273        .add(b':')
1274        .add(b';')
1275        .add(b'<')
1276        .add(b'-')
1277        .add(b'>')
1278        .add(b'?')
1279        .add(b'[')
1280        .add(b'\\')
1281        .add(b']')
1282        .add(b'{')
1283        .add(b'}');
1284
1285    percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1286}
1287
1288/// Returns the cached current system locale, or en-US by default.
1289pub fn get_current_locale() -> &'static (String, HeaderValue) {
1290    static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1291
1292    CURRENT_LOCALE.get_or_init(|| {
1293        let locale_override = servo_config::pref!(intl_locale_override);
1294        let locale = if locale_override.is_empty() {
1295            sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1296        } else {
1297            locale_override
1298        };
1299        let header_value = HeaderValue::from_str(&locale)
1300            .ok()
1301            .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1302        (locale, header_value)
1303    })
1304}
1305
1306/// Step 12 of <https://fetch.spec.whatwg.org/#concept-fetch>
1307pub fn set_default_accept_language(headers: &mut HeaderMap) {
1308    // If request’s header list does not contain `Accept-Language`,
1309    // then user agents should append (`Accept-Language, an appropriate header value) to request’s header list.
1310    if headers.contains_key(header::ACCEPT_LANGUAGE) {
1311        return;
1312    }
1313
1314    // To reduce fingerprinting we set only a single language.
1315    headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1316}
1317
1318pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());