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