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