Skip to main content

net_traits/
lib.rs

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