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