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