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    pub fn clear_cookies_async(&self, id: CookieOperationId) {
627        let _ = self
628            .core_thread
629            .send(CoreResourceMsg::EmbedderClearCookies(id));
630    }
631
632    pub fn clear_session_cookies_async(&self, id: CookieOperationId) {
633        let _ = self
634            .core_thread
635            .send(CoreResourceMsg::EmbedderClearSessionCookies(id));
636    }
637}
638
639impl GenericSend<CoreResourceMsg> for ResourceThreads {
640    fn send(&self, msg: CoreResourceMsg) -> SendResult {
641        self.core_thread.send(msg)
642    }
643
644    fn sender(&self) -> GenericSender<CoreResourceMsg> {
645        self.core_thread.clone()
646    }
647}
648
649// Ignore the sub-fields
650malloc_size_of_is_0!(ResourceThreads);
651
652#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
653pub enum IncludeSubdomains {
654    Included,
655    NotIncluded,
656}
657
658#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
659pub enum MessageData {
660    Text(String),
661    Binary(Vec<u8>),
662}
663
664#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
665pub enum WebSocketDomAction {
666    SendMessage(MessageData),
667    Close(Option<u16>, Option<String>),
668}
669
670#[derive(Debug, Deserialize, Serialize)]
671pub enum WebSocketNetworkEvent {
672    ReportCSPViolations(Vec<csp::Violation>),
673    ConnectionEstablished { protocol_in_use: Option<String> },
674    MessageReceived(MessageData),
675    Close(Option<u16>, String),
676    Fail,
677}
678
679#[derive(Debug, Deserialize, Serialize)]
680/// IPC channels to communicate with the script thread about network or DOM events.
681pub enum FetchChannels {
682    ResponseMsg(IpcSender<FetchResponseMsg>),
683    WebSocket {
684        event_sender: IpcSender<WebSocketNetworkEvent>,
685        action_receiver: CallbackSetter<WebSocketDomAction>,
686    },
687    /// If the fetch is just being done to populate the cache,
688    /// not because the data is needed now.
689    Prefetch,
690}
691
692#[derive(Debug, Deserialize, Serialize)]
693pub enum CoreResourceMsg {
694    Fetch(RequestBuilder, FetchChannels),
695    Cancel(Vec<RequestId>),
696    /// Initiate a fetch in response to processing a redirection
697    FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
698    /// Store a cookie for a given originating URL.
699    /// If a sender is provided, the caller will block until the cookie is stored.
700    SetCookieForUrl(
701        ServoUrl,
702        Serde<Cookie<'static>>,
703        CookieSource,
704        Option<GenericSender<()>>,
705    ),
706    /// Store a set of cookies for a given originating URL
707    SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
708    SetCookieForUrlAsync(
709        CookieStoreId,
710        ServoUrl,
711        Serde<Cookie<'static>>,
712        CookieSource,
713    ),
714    /// Retrieve the stored cookies as a header string for a given URL.
715    GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
716    /// Retrieve the stored cookies as a vector for the given URL.
717    /// The response is sent via the provided sender.
718    GetCookiesForUrl(
719        ServoUrl,
720        GenericSender<Vec<Serde<Cookie<'static>>>>,
721        CookieSource,
722    ),
723    /// Retrieve cookies for a URL for embedder. The response is
724    /// sent via [`NetToEmbedderMsg::EmbedderGetCookiesForUrlResponse`].
725    EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
726    /// Set a cookie for a URL on behalf of the embedder. The response is
727    /// sent via [`NetToEmbedderMsg::EmbedderSetCookieForUrlResponse`].
728    EmbedderSetCookieForUrl(
729        CookieOperationId,
730        ServoUrl,
731        Serde<Cookie<'static>>,
732        CookieSource,
733    ),
734    /// Clear all cookies on behalf of the embedder. The response is sent via NetToEmbedderMsg.
735    EmbedderClearCookies(CookieOperationId),
736    /// Clear session cookies on behalf of the embedder. The response is sent via NetToEmbedderMsg.
737    EmbedderClearSessionCookies(CookieOperationId),
738    GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
739    GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
740    DeleteCookiesForSites(Vec<String>, GenericSender<()>),
741    /// This currently is used by unit tests and WebDriver only.
742    /// When url is `None`, this clears cookies across all origins.
743    DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
744    /// Delete all session cookies (cookies without an expiry or max-age).
745    DeleteSessionCookies(GenericSender<()>),
746    DeleteCookie(ServoUrl, String),
747    DeleteCookieAsync(CookieStoreId, ServoUrl, String),
748    NewCookieListener(
749        CookieStoreId,
750        GenericCallback<CookieAsyncResponse>,
751        ServoUrl,
752    ),
753    RemoveCookieListener(CookieStoreId),
754    ListCookies(GenericSender<Vec<SiteDescriptor>>),
755    /// Get a history state by a given history state id
756    GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
757    /// Set a history state for a given history state id
758    SetHistoryState(HistoryStateId, Vec<u8>),
759    /// Removes history states for the given ids
760    RemoveHistoryStates(Vec<HistoryStateId>),
761    /// Gets a list of origin descriptors derived from entries in the cache
762    GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
763    /// Clear the network cache.
764    ClearCache(Option<GenericSender<()>>),
765    /// Send the service worker network mediator for an origin to CoreResourceThread
766    NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
767    /// Message forwarded to file manager's handler
768    ToFileManager(FileManagerThreadMsg),
769    StorePreloadedResponse(PreloadId, Response),
770    TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
771    /// Break the load handler loop, send a reply when done cleaning up local resources
772    /// and exit
773    Exit(GenericOneshotSender<()>),
774    CollectMemoryReport(ReportsChan),
775    RevokeTokenForFile(BlobTokenRevocationRequest),
776    RefreshTokenForFile(BlobTokenRefreshRequest),
777}
778
779#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
780pub struct BlobTokenRevocationRequest {
781    pub blob_id: Uuid,
782    pub token: Uuid,
783}
784
785#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
786pub struct BlobTokenRefreshRequest {
787    pub blob_id: Uuid,
788    pub new_token_sender: GenericSender<Uuid>,
789}
790
791#[derive(Clone, Debug, Deserialize, Serialize)]
792pub struct SiteDescriptor {
793    pub name: String,
794}
795
796impl SiteDescriptor {
797    pub fn new(name: String) -> Self {
798        SiteDescriptor { name }
799    }
800}
801
802#[derive(Clone, Debug, Deserialize, Serialize)]
803pub struct CacheEntryDescriptor {
804    pub key: String,
805}
806
807impl CacheEntryDescriptor {
808    pub fn new(key: String) -> Self {
809        Self { key }
810    }
811}
812
813// FIXME: https://github.com/servo/servo/issues/34591
814#[expect(clippy::large_enum_variant)]
815enum ToFetchThreadMessage {
816    Cancel(Vec<RequestId>, CoreResourceThread),
817    StartFetch(
818        /* request_builder */ RequestBuilder,
819        /* response_init */ Option<ResponseInit>,
820        /* callback  */ BoxedFetchCallback,
821        /* core resource thread channel */ CoreResourceThread,
822    ),
823    FetchResponse(FetchResponseMsg),
824    /// Stop the background thread.
825    Exit,
826}
827
828pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
829
830/// A thread to handle fetches in a Servo process. This thread is responsible for
831/// listening for new fetch requests as well as updates on those operations and forwarding
832/// them to crossbeam channels.
833struct FetchThread {
834    /// A list of active fetches. A fetch is no longer active once the
835    /// [`FetchResponseMsg::ProcessResponseEOF`] is received.
836    active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
837    /// A crossbeam receiver attached to the router proxy which converts incoming fetch
838    /// updates from IPC messages to crossbeam messages as well as another sender which
839    /// handles requests from clients wanting to do fetches.
840    receiver: Receiver<ToFetchThreadMessage>,
841    /// An [`IpcSender`] that's sent with every fetch request and leads back to our
842    /// router proxy.
843    to_fetch_sender: IpcSender<FetchResponseMsg>,
844}
845
846impl FetchThread {
847    fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
848        let (sender, receiver) = unbounded();
849        let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
850
851        let sender_clone = sender.clone();
852        ROUTER.add_typed_route(
853            from_fetch_sender,
854            Box::new(move |message| {
855                let message: FetchResponseMsg = message.unwrap();
856                let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
857            }),
858        );
859        let join_handle = thread::Builder::new()
860            .name("FetchThread".to_owned())
861            .spawn(move || {
862                let mut fetch_thread = FetchThread {
863                    active_fetches: FxHashMap::default(),
864                    receiver,
865                    to_fetch_sender,
866                };
867                fetch_thread.run();
868            })
869            .expect("Thread spawning failed");
870        (sender, join_handle)
871    }
872
873    fn run(&mut self) {
874        loop {
875            match self.receiver.recv().unwrap() {
876                ToFetchThreadMessage::StartFetch(
877                    request_builder,
878                    response_init,
879                    callback,
880                    core_resource_thread,
881                ) => {
882                    let request_builder_id = request_builder.id;
883
884                    // Only redirects have a `response_init` field.
885                    let message = match response_init {
886                        Some(response_init) => CoreResourceMsg::FetchRedirect(
887                            request_builder,
888                            response_init,
889                            self.to_fetch_sender.clone(),
890                        ),
891                        None => CoreResourceMsg::Fetch(
892                            request_builder,
893                            FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
894                        ),
895                    };
896
897                    core_resource_thread.send(message).unwrap();
898
899                    let preexisting_fetch =
900                        self.active_fetches.insert(request_builder_id, callback);
901                    // When we terminate a fetch group, all deferred fetches are processed.
902                    // In case we were already processing a deferred fetch, we should not
903                    // process the second call. This should be handled by [`DeferredFetchRecord::process`]
904                    assert!(preexisting_fetch.is_none());
905                },
906                ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
907                    let request_id = fetch_response_msg.request_id();
908                    let fetch_finished =
909                        matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
910
911                    self.active_fetches
912                        .get_mut(&request_id)
913                        .expect("Got fetch response for unknown fetch")(
914                        fetch_response_msg
915                    );
916
917                    if fetch_finished {
918                        self.active_fetches.remove(&request_id);
919                    }
920                },
921                ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
922                    // Errors are ignored here, because Servo sends many cancellation requests when shutting down.
923                    // At this point the networking task might be shut down completely, so just ignore errors
924                    // during this time.
925                    let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
926                },
927                ToFetchThreadMessage::Exit => break,
928            }
929        }
930    }
931}
932
933static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
934
935/// Start the fetch thread,
936/// and returns the join handle to the background thread.
937pub fn start_fetch_thread() -> JoinHandle<()> {
938    let (sender, join_handle) = FetchThread::spawn();
939    FETCH_THREAD
940        .set(sender)
941        .expect("Fetch thread should be set only once on start-up");
942    join_handle
943}
944
945/// Send the exit message to the background thread,
946/// after which the caller can,
947/// and should,
948/// join on the thread.
949pub fn exit_fetch_thread() {
950    let _ = FETCH_THREAD
951        .get()
952        .expect("Fetch thread should always be initialized on start-up")
953        .send(ToFetchThreadMessage::Exit);
954}
955
956/// Instruct the resource thread to make a new fetch request.
957pub fn fetch_async(
958    core_resource_thread: &CoreResourceThread,
959    request: RequestBuilder,
960    response_init: Option<ResponseInit>,
961    callback: BoxedFetchCallback,
962) {
963    let _ = FETCH_THREAD
964        .get()
965        .expect("Fetch thread should always be initialized on start-up")
966        .send(ToFetchThreadMessage::StartFetch(
967            request,
968            response_init,
969            callback,
970            core_resource_thread.clone(),
971        ));
972}
973
974/// Instruct the resource thread to cancel an existing request. Does nothing if the
975/// request has already completed or has not been fetched yet.
976pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
977    let _ = FETCH_THREAD
978        .get()
979        .expect("Fetch thread should always be initialized on start-up")
980        .send(ToFetchThreadMessage::Cancel(
981            request_ids,
982            core_resource_thread.clone(),
983        ));
984}
985
986#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
987pub struct ResourceCorsData {
988    /// CORS Preflight flag
989    pub preflight: bool,
990    /// Origin of CORS Request
991    pub origin: ServoUrl,
992}
993
994/// Metadata about a loaded resource, such as is obtained from HTTP headers.
995#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
996pub struct Metadata {
997    /// Final URL after redirects.
998    pub final_url: ServoUrl,
999
1000    /// Location URL from the response headers.
1001    pub location_url: Option<Result<ServoUrl, String>>,
1002
1003    #[ignore_malloc_size_of = "Defined in hyper"]
1004    /// MIME type / subtype.
1005    pub content_type: Option<Serde<ContentType>>,
1006
1007    /// Character set.
1008    pub charset: Option<String>,
1009
1010    #[ignore_malloc_size_of = "Defined in hyper"]
1011    /// Headers
1012    pub headers: Option<Serde<HeaderMap>>,
1013
1014    /// HTTP Status
1015    pub status: HttpStatus,
1016
1017    /// Referrer Url
1018    pub referrer: Option<ServoUrl>,
1019
1020    /// Referrer Policy of the Request used to obtain Response
1021    pub referrer_policy: ReferrerPolicy,
1022    /// Performance information for navigation events
1023    pub timing: Option<ResourceFetchTiming>,
1024    /// True if the request comes from a redirection
1025    pub redirected: bool,
1026    /// Detailed TLS metadata associated with the response, if any.
1027    pub tls_security_info: Option<TlsSecurityInfo>,
1028}
1029
1030impl Metadata {
1031    /// Metadata with defaults for everything optional.
1032    pub fn default(url: ServoUrl) -> Self {
1033        Metadata {
1034            final_url: url,
1035            location_url: None,
1036            content_type: None,
1037            charset: None,
1038            headers: None,
1039            status: HttpStatus::default(),
1040            referrer: None,
1041            referrer_policy: ReferrerPolicy::EmptyString,
1042            timing: None,
1043            redirected: false,
1044            tls_security_info: None,
1045        }
1046    }
1047
1048    /// Extract the parts of a Mime that we care about.
1049    pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1050        if self.headers.is_none() {
1051            self.headers = Some(Serde(HeaderMap::new()));
1052        }
1053
1054        if let Some(mime) = content_type {
1055            self.headers
1056                .as_mut()
1057                .unwrap()
1058                .typed_insert(ContentType::from(mime.clone()));
1059            if let Some(charset) = mime.get_param(mime::CHARSET) {
1060                self.charset = Some(charset.to_string());
1061            }
1062            self.content_type = Some(Serde(ContentType::from(mime.clone())));
1063        }
1064    }
1065
1066    /// Set the referrer policy associated with the loaded resource.
1067    pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1068        if referrer_policy == ReferrerPolicy::EmptyString {
1069            return;
1070        }
1071
1072        if self.headers.is_none() {
1073            self.headers = Some(Serde(HeaderMap::new()));
1074        }
1075
1076        self.referrer_policy = referrer_policy;
1077
1078        self.headers
1079            .as_mut()
1080            .unwrap()
1081            .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1082    }
1083
1084    /// <https://html.spec.whatwg.org/multipage/#content-type>
1085    pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1086        // The Content-Type metadata of a resource must be obtained and interpreted in a manner consistent with the requirements of MIME Sniffing. [MIMESNIFF]
1087        let no_sniff = self
1088            .headers
1089            .as_deref()
1090            .is_some_and(determine_nosniff)
1091            .into();
1092        let mime = self
1093            .content_type
1094            .clone()
1095            .map(|content_type| content_type.into_inner().into());
1096        MimeClassifier::default().classify(
1097            load_context,
1098            no_sniff,
1099            ApacheBugFlag::from_content_type(mime.as_ref()),
1100            &mime,
1101            data,
1102        )
1103    }
1104}
1105
1106/// The creator of a given cookie
1107#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1108pub enum CookieSource {
1109    /// An HTTP API
1110    HTTP,
1111    /// A non-HTTP API
1112    NonHTTP,
1113}
1114
1115#[derive(Clone, Debug, Deserialize, Serialize)]
1116pub struct CookieChange {
1117    changed: Vec<Serde<Cookie<'static>>>,
1118    deleted: Vec<Serde<Cookie<'static>>>,
1119}
1120
1121#[derive(Clone, Debug, Deserialize, Serialize)]
1122pub enum CookieData {
1123    Change(CookieChange),
1124    Get(Option<Serde<Cookie<'static>>>),
1125    GetAll(Vec<Serde<Cookie<'static>>>),
1126    Set(Result<(), ()>),
1127    Delete(Result<(), ()>),
1128}
1129
1130#[derive(Clone, Debug, Deserialize, Serialize)]
1131pub struct CookieAsyncResponse {
1132    pub data: CookieData,
1133}
1134
1135/// Network errors that have to be exported out of the loaders
1136#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1137pub enum NetworkError {
1138    LoadCancelled,
1139    /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser.
1140    SslValidation(String, Vec<u8>),
1141    /// Crash error, to be converted to Resource::Crash in the HTML parser.
1142    Crash(String),
1143    UnsupportedScheme,
1144    CorsGeneral,
1145    CrossOriginResponse,
1146    CorsCredentials,
1147    CorsAllowMethods,
1148    CorsAllowHeaders,
1149    CorsMethod,
1150    CorsAuthorization,
1151    CorsHeaders,
1152    ConnectionFailure,
1153    RedirectError,
1154    TooManyRedirects,
1155    TooManyInFlightKeepAliveRequests,
1156    InvalidMethod,
1157    ResourceLoadError(String),
1158    ContentSecurityPolicy,
1159    Nosniff,
1160    MimeType(String),
1161    SubresourceIntegrity,
1162    MixedContent,
1163    CacheError,
1164    InvalidPort,
1165    WebsocketConnectionFailure(String),
1166    LocalDirectoryError,
1167    PartialResponseToNonRangeRequestError,
1168    ProtocolHandlerSubstitutionError,
1169    BlobURLStoreError(String),
1170    HttpError(String),
1171    DecompressionError,
1172}
1173
1174impl fmt::Debug for NetworkError {
1175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1176        match self {
1177            NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1178            NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1179            NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1180            NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1181            NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1182            NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1183            NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1184            NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1185            NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1186            NetworkError::ConnectionFailure => write!(f, "Request failed"),
1187            NetworkError::RedirectError => write!(f, "Redirect failed"),
1188            NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1189            NetworkError::TooManyInFlightKeepAliveRequests => {
1190                write!(f, "Too many in flight keep-alive requests")
1191            },
1192            NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1193            NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1194            NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1195            NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1196            NetworkError::MimeType(s) => write!(f, "{}", s),
1197            NetworkError::SubresourceIntegrity => {
1198                write!(f, "Subresource integrity validation failed")
1199            },
1200            NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1201            NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1202            NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1203            NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1204            NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1205            NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1206            NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1207            NetworkError::PartialResponseToNonRangeRequestError => write!(
1208                f,
1209                "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1210            ),
1211            NetworkError::ProtocolHandlerSubstitutionError => {
1212                write!(f, "Failed to parse substituted protocol handler url")
1213            },
1214            NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1215            NetworkError::WebsocketConnectionFailure(s) => {
1216                write!(f, "Websocket connection failure: {}", s)
1217            },
1218            NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1219            NetworkError::DecompressionError => write!(f, "Decompression error"),
1220        }
1221    }
1222}
1223
1224impl NetworkError {
1225    pub fn is_permanent_failure(&self) -> bool {
1226        matches!(
1227            self,
1228            NetworkError::ContentSecurityPolicy |
1229                NetworkError::MixedContent |
1230                NetworkError::SubresourceIntegrity |
1231                NetworkError::Nosniff |
1232                NetworkError::InvalidPort |
1233                NetworkError::CorsGeneral |
1234                NetworkError::CrossOriginResponse |
1235                NetworkError::CorsCredentials |
1236                NetworkError::CorsAllowMethods |
1237                NetworkError::CorsAllowHeaders |
1238                NetworkError::CorsMethod |
1239                NetworkError::CorsAuthorization |
1240                NetworkError::CorsHeaders |
1241                NetworkError::UnsupportedScheme
1242        )
1243    }
1244
1245    pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1246        let error_string = error.to_string();
1247        match certificate {
1248            Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1249            _ => NetworkError::HttpError(error_string),
1250        }
1251    }
1252}
1253
1254/// Normalize `slice`, as defined by
1255/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize).
1256pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1257    const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1258
1259    loop {
1260        match slice.split_first() {
1261            Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1262            _ => break,
1263        }
1264    }
1265
1266    loop {
1267        match slice.split_last() {
1268            Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1269            _ => break,
1270        }
1271    }
1272
1273    slice
1274}
1275
1276pub fn http_percent_encode(bytes: &[u8]) -> String {
1277    // This encode set is used for HTTP header values and is defined at
1278    // https://tools.ietf.org/html/rfc5987#section-3.2
1279    const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1280        .add(b' ')
1281        .add(b'"')
1282        .add(b'%')
1283        .add(b'\'')
1284        .add(b'(')
1285        .add(b')')
1286        .add(b'*')
1287        .add(b',')
1288        .add(b'/')
1289        .add(b':')
1290        .add(b';')
1291        .add(b'<')
1292        .add(b'-')
1293        .add(b'>')
1294        .add(b'?')
1295        .add(b'[')
1296        .add(b'\\')
1297        .add(b']')
1298        .add(b'{')
1299        .add(b'}');
1300
1301    percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1302}
1303
1304/// Returns the cached current system locale, or en-US by default.
1305pub fn get_current_locale() -> &'static (String, HeaderValue) {
1306    static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1307
1308    CURRENT_LOCALE.get_or_init(|| {
1309        let locale_override = servo_config::pref!(intl_locale_override);
1310        let locale = if locale_override.is_empty() {
1311            sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1312        } else {
1313            locale_override
1314        };
1315        let header_value = HeaderValue::from_str(&locale)
1316            .ok()
1317            .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1318        (locale, header_value)
1319    })
1320}
1321
1322/// Step 12 of <https://fetch.spec.whatwg.org/#concept-fetch>
1323pub fn set_default_accept_language(headers: &mut HeaderMap) {
1324    // If request’s header list does not contain `Accept-Language`,
1325    // then user agents should append (`Accept-Language, an appropriate header value) to request’s header list.
1326    if headers.contains_key(header::ACCEPT_LANGUAGE) {
1327        return;
1328    }
1329
1330    // To reduce fingerprinting we set only a single language.
1331    headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1332}
1333
1334pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());