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