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