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