Skip to main content

net_traits/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![deny(unsafe_code)]
6
7use std::fmt::{self, Debug, Display};
8use std::sync::{LazyLock, OnceLock};
9use std::thread::{self, JoinHandle};
10
11use content_security_policy::{self as csp};
12use cookie::Cookie;
13use crossbeam_channel::{Receiver, Sender, unbounded};
14use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
15use http::{HeaderMap, HeaderValue, StatusCode, header};
16use hyper_serde::Serde;
17use hyper_util::client::legacy::Error as HyperError;
18use ipc_channel::ipc::{self, IpcSender};
19use ipc_channel::router::ROUTER;
20use malloc_size_of::malloc_size_of_is_0;
21use malloc_size_of_derive::MallocSizeOf;
22use mime::Mime;
23use profile_traits::mem::ReportsChan;
24use rand::{RngCore, rng};
25use request::RequestId;
26use rustc_hash::FxHashMap;
27use rustls_pki_types::CertificateDer;
28use serde::{Deserialize, Serialize};
29use servo_base::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 clear_session_cookies(&self) {
566        let (sender, receiver) = generic_channel::channel().unwrap();
567        let _ = self
568            .core_thread
569            .send(CoreResourceMsg::DeleteSessionCookies(sender));
570        let _ = receiver.recv();
571    }
572
573    pub fn set_cookie_for_url(&self, url: ServoUrl, cookie: Cookie<'static>, source: CookieSource) {
574        let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
575            url,
576            Serde(cookie),
577            source,
578            None,
579        ));
580    }
581
582    pub fn set_cookie_for_url_sync(
583        &self,
584        url: ServoUrl,
585        cookie: Cookie<'static>,
586        source: CookieSource,
587    ) {
588        let (sender, receiver) = generic_channel::channel().unwrap();
589        let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
590            url,
591            Serde(cookie),
592            source,
593            Some(sender),
594        ));
595        let _ = receiver.recv();
596    }
597
598    pub fn cookies_for_url_async(
599        &self,
600        id: CookieOperationId,
601        url: ServoUrl,
602        source: CookieSource,
603    ) {
604        let _ = self
605            .core_thread
606            .send(CoreResourceMsg::EmbedderGetCookiesForUrl(id, url, source));
607    }
608
609    pub fn set_cookie_for_url_async(
610        &self,
611        id: CookieOperationId,
612        url: ServoUrl,
613        cookie: Cookie<'static>,
614        source: CookieSource,
615    ) {
616        let _ = self
617            .core_thread
618            .send(CoreResourceMsg::EmbedderSetCookieForUrl(
619                id,
620                url,
621                Serde(cookie),
622                source,
623            ));
624    }
625}
626
627impl GenericSend<CoreResourceMsg> for ResourceThreads {
628    fn send(&self, msg: CoreResourceMsg) -> SendResult {
629        self.core_thread.send(msg)
630    }
631
632    fn sender(&self) -> GenericSender<CoreResourceMsg> {
633        self.core_thread.clone()
634    }
635}
636
637// Ignore the sub-fields
638malloc_size_of_is_0!(ResourceThreads);
639
640#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
641pub enum IncludeSubdomains {
642    Included,
643    NotIncluded,
644}
645
646#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
647pub enum MessageData {
648    Text(String),
649    Binary(Vec<u8>),
650}
651
652#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
653pub enum WebSocketDomAction {
654    SendMessage(MessageData),
655    Close(Option<u16>, Option<String>),
656}
657
658#[derive(Debug, Deserialize, Serialize)]
659pub enum WebSocketNetworkEvent {
660    ReportCSPViolations(Vec<csp::Violation>),
661    ConnectionEstablished { protocol_in_use: Option<String> },
662    MessageReceived(MessageData),
663    Close(Option<u16>, String),
664    Fail,
665}
666
667#[derive(Debug, Deserialize, Serialize)]
668/// IPC channels to communicate with the script thread about network or DOM events.
669pub enum FetchChannels {
670    ResponseMsg(IpcSender<FetchResponseMsg>),
671    WebSocket {
672        event_sender: IpcSender<WebSocketNetworkEvent>,
673        action_receiver: CallbackSetter<WebSocketDomAction>,
674    },
675    /// If the fetch is just being done to populate the cache,
676    /// not because the data is needed now.
677    Prefetch,
678}
679
680#[derive(Debug, Deserialize, Serialize)]
681pub enum CoreResourceMsg {
682    Fetch(RequestBuilder, FetchChannels),
683    Cancel(Vec<RequestId>),
684    /// Initiate a fetch in response to processing a redirection
685    FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
686    /// Store a cookie for a given originating URL.
687    /// If a sender is provided, the caller will block until the cookie is stored.
688    SetCookieForUrl(
689        ServoUrl,
690        Serde<Cookie<'static>>,
691        CookieSource,
692        Option<GenericSender<()>>,
693    ),
694    /// Store a set of cookies for a given originating URL
695    SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
696    SetCookieForUrlAsync(
697        CookieStoreId,
698        ServoUrl,
699        Serde<Cookie<'static>>,
700        CookieSource,
701    ),
702    /// Retrieve the stored cookies as a header string for a given URL.
703    GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
704    /// Retrieve the stored cookies as a vector for the given URL.
705    /// The response is sent via the provided sender.
706    GetCookiesForUrl(
707        ServoUrl,
708        GenericSender<Vec<Serde<Cookie<'static>>>>,
709        CookieSource,
710    ),
711    /// Retrieve cookies for a URL for embedder. The response is
712    /// sent via [`NetToEmbedderMsg::EmbedderGetCookiesForUrlResponse`].
713    EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
714    /// Set a cookie for a URL on behalf of the embedder. The response is
715    /// sent via [`NetToEmbedderMsg::EmbedderSetCookieForUrlResponse`].
716    EmbedderSetCookieForUrl(
717        CookieOperationId,
718        ServoUrl,
719        Serde<Cookie<'static>>,
720        CookieSource,
721    ),
722    GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
723    GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
724    DeleteCookiesForSites(Vec<String>, GenericSender<()>),
725    /// This currently is used by unit tests and WebDriver only.
726    /// When url is `None`, this clears cookies across all origins.
727    DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
728    /// Delete all session cookies (cookies without an expiry or max-age).
729    DeleteSessionCookies(GenericSender<()>),
730    DeleteCookie(ServoUrl, String),
731    DeleteCookieAsync(CookieStoreId, ServoUrl, String),
732    NewCookieListener(
733        CookieStoreId,
734        GenericCallback<CookieAsyncResponse>,
735        ServoUrl,
736    ),
737    RemoveCookieListener(CookieStoreId),
738    ListCookies(GenericSender<Vec<SiteDescriptor>>),
739    /// Get a history state by a given history state id
740    GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
741    /// Set a history state for a given history state id
742    SetHistoryState(HistoryStateId, Vec<u8>),
743    /// Removes history states for the given ids
744    RemoveHistoryStates(Vec<HistoryStateId>),
745    /// Gets a list of origin descriptors derived from entries in the cache
746    GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
747    /// Clear the network cache.
748    ClearCache(Option<GenericSender<()>>),
749    /// Send the service worker network mediator for an origin to CoreResourceThread
750    NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
751    /// Message forwarded to file manager's handler
752    ToFileManager(FileManagerThreadMsg),
753    StorePreloadedResponse(PreloadId, Response),
754    TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
755    /// Break the load handler loop, send a reply when done cleaning up local resources
756    /// and exit
757    Exit(GenericOneshotSender<()>),
758    CollectMemoryReport(ReportsChan),
759    RevokeTokenForFile(BlobTokenRevocationRequest),
760    RefreshTokenForFile(BlobTokenRefreshRequest),
761}
762
763#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
764pub struct BlobTokenRevocationRequest {
765    pub blob_id: Uuid,
766    pub token: Uuid,
767}
768
769#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
770pub struct BlobTokenRefreshRequest {
771    pub blob_id: Uuid,
772    pub new_token_sender: GenericSender<Uuid>,
773}
774
775#[derive(Clone, Debug, Deserialize, Serialize)]
776pub struct SiteDescriptor {
777    pub name: String,
778}
779
780impl SiteDescriptor {
781    pub fn new(name: String) -> Self {
782        SiteDescriptor { name }
783    }
784}
785
786#[derive(Clone, Debug, Deserialize, Serialize)]
787pub struct CacheEntryDescriptor {
788    pub key: String,
789}
790
791impl CacheEntryDescriptor {
792    pub fn new(key: String) -> Self {
793        Self { key }
794    }
795}
796
797// FIXME: https://github.com/servo/servo/issues/34591
798#[expect(clippy::large_enum_variant)]
799enum ToFetchThreadMessage {
800    Cancel(Vec<RequestId>, CoreResourceThread),
801    StartFetch(
802        /* request_builder */ RequestBuilder,
803        /* response_init */ Option<ResponseInit>,
804        /* callback  */ BoxedFetchCallback,
805        /* core resource thread channel */ CoreResourceThread,
806    ),
807    FetchResponse(FetchResponseMsg),
808    /// Stop the background thread.
809    Exit,
810}
811
812pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
813
814/// A thread to handle fetches in a Servo process. This thread is responsible for
815/// listening for new fetch requests as well as updates on those operations and forwarding
816/// them to crossbeam channels.
817struct FetchThread {
818    /// A list of active fetches. A fetch is no longer active once the
819    /// [`FetchResponseMsg::ProcessResponseEOF`] is received.
820    active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
821    /// A crossbeam receiver attached to the router proxy which converts incoming fetch
822    /// updates from IPC messages to crossbeam messages as well as another sender which
823    /// handles requests from clients wanting to do fetches.
824    receiver: Receiver<ToFetchThreadMessage>,
825    /// An [`IpcSender`] that's sent with every fetch request and leads back to our
826    /// router proxy.
827    to_fetch_sender: IpcSender<FetchResponseMsg>,
828}
829
830impl FetchThread {
831    fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
832        let (sender, receiver) = unbounded();
833        let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
834
835        let sender_clone = sender.clone();
836        ROUTER.add_typed_route(
837            from_fetch_sender,
838            Box::new(move |message| {
839                let message: FetchResponseMsg = message.unwrap();
840                let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
841            }),
842        );
843        let join_handle = thread::Builder::new()
844            .name("FetchThread".to_owned())
845            .spawn(move || {
846                let mut fetch_thread = FetchThread {
847                    active_fetches: FxHashMap::default(),
848                    receiver,
849                    to_fetch_sender,
850                };
851                fetch_thread.run();
852            })
853            .expect("Thread spawning failed");
854        (sender, join_handle)
855    }
856
857    fn run(&mut self) {
858        loop {
859            match self.receiver.recv().unwrap() {
860                ToFetchThreadMessage::StartFetch(
861                    request_builder,
862                    response_init,
863                    callback,
864                    core_resource_thread,
865                ) => {
866                    let request_builder_id = request_builder.id;
867
868                    // Only redirects have a `response_init` field.
869                    let message = match response_init {
870                        Some(response_init) => CoreResourceMsg::FetchRedirect(
871                            request_builder,
872                            response_init,
873                            self.to_fetch_sender.clone(),
874                        ),
875                        None => CoreResourceMsg::Fetch(
876                            request_builder,
877                            FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
878                        ),
879                    };
880
881                    core_resource_thread.send(message).unwrap();
882
883                    let preexisting_fetch =
884                        self.active_fetches.insert(request_builder_id, callback);
885                    // When we terminate a fetch group, all deferred fetches are processed.
886                    // In case we were already processing a deferred fetch, we should not
887                    // process the second call. This should be handled by [`DeferredFetchRecord::process`]
888                    assert!(preexisting_fetch.is_none());
889                },
890                ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
891                    let request_id = fetch_response_msg.request_id();
892                    let fetch_finished =
893                        matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
894
895                    self.active_fetches
896                        .get_mut(&request_id)
897                        .expect("Got fetch response for unknown fetch")(
898                        fetch_response_msg
899                    );
900
901                    if fetch_finished {
902                        self.active_fetches.remove(&request_id);
903                    }
904                },
905                ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
906                    // Errors are ignored here, because Servo sends many cancellation requests when shutting down.
907                    // At this point the networking task might be shut down completely, so just ignore errors
908                    // during this time.
909                    let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
910                },
911                ToFetchThreadMessage::Exit => break,
912            }
913        }
914    }
915}
916
917static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
918
919/// Start the fetch thread,
920/// and returns the join handle to the background thread.
921pub fn start_fetch_thread() -> JoinHandle<()> {
922    let (sender, join_handle) = FetchThread::spawn();
923    FETCH_THREAD
924        .set(sender)
925        .expect("Fetch thread should be set only once on start-up");
926    join_handle
927}
928
929/// Send the exit message to the background thread,
930/// after which the caller can,
931/// and should,
932/// join on the thread.
933pub fn exit_fetch_thread() {
934    let _ = FETCH_THREAD
935        .get()
936        .expect("Fetch thread should always be initialized on start-up")
937        .send(ToFetchThreadMessage::Exit);
938}
939
940/// Instruct the resource thread to make a new fetch request.
941pub fn fetch_async(
942    core_resource_thread: &CoreResourceThread,
943    request: RequestBuilder,
944    response_init: Option<ResponseInit>,
945    callback: BoxedFetchCallback,
946) {
947    let _ = FETCH_THREAD
948        .get()
949        .expect("Fetch thread should always be initialized on start-up")
950        .send(ToFetchThreadMessage::StartFetch(
951            request,
952            response_init,
953            callback,
954            core_resource_thread.clone(),
955        ));
956}
957
958/// Instruct the resource thread to cancel an existing request. Does nothing if the
959/// request has already completed or has not been fetched yet.
960pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
961    let _ = FETCH_THREAD
962        .get()
963        .expect("Fetch thread should always be initialized on start-up")
964        .send(ToFetchThreadMessage::Cancel(
965            request_ids,
966            core_resource_thread.clone(),
967        ));
968}
969
970#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
971pub struct ResourceCorsData {
972    /// CORS Preflight flag
973    pub preflight: bool,
974    /// Origin of CORS Request
975    pub origin: ServoUrl,
976}
977
978/// Metadata about a loaded resource, such as is obtained from HTTP headers.
979#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
980pub struct Metadata {
981    /// Final URL after redirects.
982    pub final_url: ServoUrl,
983
984    /// Location URL from the response headers.
985    pub location_url: Option<Result<ServoUrl, String>>,
986
987    #[ignore_malloc_size_of = "Defined in hyper"]
988    /// MIME type / subtype.
989    pub content_type: Option<Serde<ContentType>>,
990
991    /// Character set.
992    pub charset: Option<String>,
993
994    #[ignore_malloc_size_of = "Defined in hyper"]
995    /// Headers
996    pub headers: Option<Serde<HeaderMap>>,
997
998    /// HTTP Status
999    pub status: HttpStatus,
1000
1001    /// Is successful HTTPS connection
1002    pub https_state: HttpsState,
1003
1004    /// Referrer Url
1005    pub referrer: Option<ServoUrl>,
1006
1007    /// Referrer Policy of the Request used to obtain Response
1008    pub referrer_policy: ReferrerPolicy,
1009    /// Performance information for navigation events
1010    pub timing: Option<ResourceFetchTiming>,
1011    /// True if the request comes from a redirection
1012    pub redirected: bool,
1013    /// Detailed TLS metadata associated with the response, if any.
1014    pub tls_security_info: Option<TlsSecurityInfo>,
1015}
1016
1017impl Metadata {
1018    /// Metadata with defaults for everything optional.
1019    pub fn default(url: ServoUrl) -> Self {
1020        Metadata {
1021            final_url: url,
1022            location_url: None,
1023            content_type: None,
1024            charset: None,
1025            headers: None,
1026            status: HttpStatus::default(),
1027            https_state: HttpsState::None,
1028            referrer: None,
1029            referrer_policy: ReferrerPolicy::EmptyString,
1030            timing: None,
1031            redirected: false,
1032            tls_security_info: None,
1033        }
1034    }
1035
1036    /// Extract the parts of a Mime that we care about.
1037    pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1038        if self.headers.is_none() {
1039            self.headers = Some(Serde(HeaderMap::new()));
1040        }
1041
1042        if let Some(mime) = content_type {
1043            self.headers
1044                .as_mut()
1045                .unwrap()
1046                .typed_insert(ContentType::from(mime.clone()));
1047            if let Some(charset) = mime.get_param(mime::CHARSET) {
1048                self.charset = Some(charset.to_string());
1049            }
1050            self.content_type = Some(Serde(ContentType::from(mime.clone())));
1051        }
1052    }
1053
1054    /// Set the referrer policy associated with the loaded resource.
1055    pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1056        if referrer_policy == ReferrerPolicy::EmptyString {
1057            return;
1058        }
1059
1060        if self.headers.is_none() {
1061            self.headers = Some(Serde(HeaderMap::new()));
1062        }
1063
1064        self.referrer_policy = referrer_policy;
1065
1066        self.headers
1067            .as_mut()
1068            .unwrap()
1069            .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1070    }
1071
1072    /// <https://html.spec.whatwg.org/multipage/#content-type>
1073    pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1074        // The Content-Type metadata of a resource must be obtained and interpreted in a manner consistent with the requirements of MIME Sniffing. [MIMESNIFF]
1075        let no_sniff = self
1076            .headers
1077            .as_deref()
1078            .is_some_and(determine_nosniff)
1079            .into();
1080        let mime = self
1081            .content_type
1082            .clone()
1083            .map(|content_type| content_type.into_inner().into());
1084        MimeClassifier::default().classify(
1085            load_context,
1086            no_sniff,
1087            ApacheBugFlag::from_content_type(mime.as_ref()),
1088            &mime,
1089            data,
1090        )
1091    }
1092}
1093
1094/// The creator of a given cookie
1095#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1096pub enum CookieSource {
1097    /// An HTTP API
1098    HTTP,
1099    /// A non-HTTP API
1100    NonHTTP,
1101}
1102
1103#[derive(Clone, Debug, Deserialize, Serialize)]
1104pub struct CookieChange {
1105    changed: Vec<Serde<Cookie<'static>>>,
1106    deleted: Vec<Serde<Cookie<'static>>>,
1107}
1108
1109#[derive(Clone, Debug, Deserialize, Serialize)]
1110pub enum CookieData {
1111    Change(CookieChange),
1112    Get(Option<Serde<Cookie<'static>>>),
1113    GetAll(Vec<Serde<Cookie<'static>>>),
1114    Set(Result<(), ()>),
1115    Delete(Result<(), ()>),
1116}
1117
1118#[derive(Clone, Debug, Deserialize, Serialize)]
1119pub struct CookieAsyncResponse {
1120    pub data: CookieData,
1121}
1122
1123/// Network errors that have to be exported out of the loaders
1124#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1125pub enum NetworkError {
1126    LoadCancelled,
1127    /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser.
1128    SslValidation(String, Vec<u8>),
1129    /// Crash error, to be converted to Resource::Crash in the HTML parser.
1130    Crash(String),
1131    UnsupportedScheme,
1132    CorsGeneral,
1133    CrossOriginResponse,
1134    CorsCredentials,
1135    CorsAllowMethods,
1136    CorsAllowHeaders,
1137    CorsMethod,
1138    CorsAuthorization,
1139    CorsHeaders,
1140    ConnectionFailure,
1141    RedirectError,
1142    TooManyRedirects,
1143    TooManyInFlightKeepAliveRequests,
1144    InvalidMethod,
1145    ResourceLoadError(String),
1146    ContentSecurityPolicy,
1147    Nosniff,
1148    MimeType(String),
1149    SubresourceIntegrity,
1150    MixedContent,
1151    CacheError,
1152    InvalidPort,
1153    WebsocketConnectionFailure(String),
1154    LocalDirectoryError,
1155    PartialResponseToNonRangeRequestError,
1156    ProtocolHandlerSubstitutionError,
1157    BlobURLStoreError(String),
1158    HttpError(String),
1159    DecompressionError,
1160}
1161
1162impl fmt::Debug for NetworkError {
1163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1164        match self {
1165            NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1166            NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1167            NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1168            NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1169            NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1170            NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1171            NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1172            NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1173            NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1174            NetworkError::ConnectionFailure => write!(f, "Request failed"),
1175            NetworkError::RedirectError => write!(f, "Redirect failed"),
1176            NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1177            NetworkError::TooManyInFlightKeepAliveRequests => {
1178                write!(f, "Too many in flight keep-alive requests")
1179            },
1180            NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1181            NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1182            NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1183            NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1184            NetworkError::MimeType(s) => write!(f, "{}", s),
1185            NetworkError::SubresourceIntegrity => {
1186                write!(f, "Subresource integrity validation failed")
1187            },
1188            NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1189            NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1190            NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1191            NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1192            NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1193            NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1194            NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1195            NetworkError::PartialResponseToNonRangeRequestError => write!(
1196                f,
1197                "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1198            ),
1199            NetworkError::ProtocolHandlerSubstitutionError => {
1200                write!(f, "Failed to parse substituted protocol handler url")
1201            },
1202            NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1203            NetworkError::WebsocketConnectionFailure(s) => {
1204                write!(f, "Websocket connection failure: {}", s)
1205            },
1206            NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1207            NetworkError::DecompressionError => write!(f, "Decompression error"),
1208        }
1209    }
1210}
1211
1212impl NetworkError {
1213    pub fn is_permanent_failure(&self) -> bool {
1214        matches!(
1215            self,
1216            NetworkError::ContentSecurityPolicy |
1217                NetworkError::MixedContent |
1218                NetworkError::SubresourceIntegrity |
1219                NetworkError::Nosniff |
1220                NetworkError::InvalidPort |
1221                NetworkError::CorsGeneral |
1222                NetworkError::CrossOriginResponse |
1223                NetworkError::CorsCredentials |
1224                NetworkError::CorsAllowMethods |
1225                NetworkError::CorsAllowHeaders |
1226                NetworkError::CorsMethod |
1227                NetworkError::CorsAuthorization |
1228                NetworkError::CorsHeaders |
1229                NetworkError::UnsupportedScheme
1230        )
1231    }
1232
1233    pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1234        let error_string = error.to_string();
1235        match certificate {
1236            Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1237            _ => NetworkError::HttpError(error_string),
1238        }
1239    }
1240}
1241
1242/// Normalize `slice`, as defined by
1243/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize).
1244pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1245    const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1246
1247    loop {
1248        match slice.split_first() {
1249            Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1250            _ => break,
1251        }
1252    }
1253
1254    loop {
1255        match slice.split_last() {
1256            Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1257            _ => break,
1258        }
1259    }
1260
1261    slice
1262}
1263
1264pub fn http_percent_encode(bytes: &[u8]) -> String {
1265    // This encode set is used for HTTP header values and is defined at
1266    // https://tools.ietf.org/html/rfc5987#section-3.2
1267    const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
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        .add(b';')
1279        .add(b'<')
1280        .add(b'-')
1281        .add(b'>')
1282        .add(b'?')
1283        .add(b'[')
1284        .add(b'\\')
1285        .add(b']')
1286        .add(b'{')
1287        .add(b'}');
1288
1289    percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1290}
1291
1292/// Returns the cached current system locale, or en-US by default.
1293pub fn get_current_locale() -> &'static (String, HeaderValue) {
1294    static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1295
1296    CURRENT_LOCALE.get_or_init(|| {
1297        let locale_override = servo_config::pref!(intl_locale_override);
1298        let locale = if locale_override.is_empty() {
1299            sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1300        } else {
1301            locale_override
1302        };
1303        let header_value = HeaderValue::from_str(&locale)
1304            .ok()
1305            .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1306        (locale, header_value)
1307    })
1308}
1309
1310/// Step 12 of <https://fetch.spec.whatwg.org/#concept-fetch>
1311pub fn set_default_accept_language(headers: &mut HeaderMap) {
1312    // If request’s header list does not contain `Accept-Language`,
1313    // then user agents should append (`Accept-Language, an appropriate header value) to request’s header list.
1314    if headers.contains_key(header::ACCEPT_LANGUAGE) {
1315        return;
1316    }
1317
1318    // To reduce fingerprinting we set only a single language.
1319    headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1320}
1321
1322pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());