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