net_traits/
lib.rs

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