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