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