1#![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#[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
72pub 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#[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#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
129pub enum ReferrerPolicy {
130 EmptyString,
132 NoReferrer,
134 NoReferrerWhenDowngrade,
136 Origin,
138 SameOrigin,
140 OriginWhenCrossOrigin,
142 UnsafeUrl,
144 StrictOrigin,
146 #[default]
148 StrictOriginWhenCrossOrigin,
149}
150
151impl ReferrerPolicy {
152 pub fn from_with_legacy(value: &str) -> Self {
154 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 pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
171 headers
173 .as_ref()
174 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
176 .into()
178 }
179}
180
181impl From<&str> for ReferrerPolicy {
182 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
223impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
225 fn from(header: Option<ReferrerPolicyHeader>) -> Self {
226 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#[expect(clippy::large_enum_variant)]
266#[derive(Debug, Deserialize, Serialize)]
267pub enum FetchResponseMsg {
268 ProcessRequestBody(RequestId),
270 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 fn process_request_body(&mut self, request: &Request);
296
297 fn process_response(&mut self, request: &Request, response: &Response);
301
302 fn process_response_chunk(&mut self, request: &Request, chunk: bytes::Bytes);
304
305 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 fn process_response_length_hint(&mut self, request_id: &Request, length: usize);
314}
315
316#[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 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 #[default]
431 Insecure,
432 Weak,
434 Broken,
436 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 #[serde(default)]
516 pub state: TlsSecurityState,
517 pub weakness_reasons: Vec<String>,
519 pub protocol_version: Option<ServoProtocolVersion>,
521 pub cipher_suite: Option<ServoCipherSuite>,
523 pub kea_group_name: Option<ServoNamedGroup>,
525 pub signature_scheme_name: Option<String>,
527 pub alpn_protocol: Option<String>,
529 pub certificate_chain_der: Vec<Vec<u8>>,
531 pub certificate_transparency: Option<String>,
533 pub hsts: bool,
535 pub hpkp: bool,
537 pub used_ech: bool,
539 pub used_delegated_credentials: bool,
541 pub used_ocsp: bool,
543 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
562pub 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
576pub trait AsyncRuntime: Send {
579 fn shutdown(&mut self);
580}
581
582pub type CoreResourceThread = GenericSender<CoreResourceMsg>;
584
585#[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 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
742malloc_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,
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)]
775pub enum FetchChannels {
777 ResponseMsg(GenericCallback<FetchResponseMsg>),
778 WebSocket {
779 event_sender: ProfileGenericCallback<WebSocketNetworkEvent>,
780 action_receiver: CallbackSetter<WebSocketDomAction>,
781 },
782 Prefetch,
785}
786
787#[derive(Debug, Deserialize, Serialize)]
788pub enum CoreResourceMsg {
789 Fetch(RequestBuilder, FetchChannels),
790 Cancel(Vec<RequestId>),
791 FetchRedirect(
793 RequestBuilder,
794 ResponseInit,
795 GenericCallback<FetchResponseMsg>,
796 ),
797 SetCookieForUrl(
800 ServoUrl,
801 Serde<Cookie<'static>>,
802 CookieSource,
803 Option<GenericSender<()>>,
804 ),
805 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
807 SetCookieForUrlAsync(
808 CookieStoreId,
809 ServoUrl,
810 Serde<Cookie<'static>>,
811 CookieSource,
812 ),
813 GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
815 GetCookiesForUrl(
818 ServoUrl,
819 GenericSender<Vec<Serde<Cookie<'static>>>>,
820 CookieSource,
821 ),
822 EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
825 EmbedderSetCookieForUrl(
828 CookieOperationId,
829 ServoUrl,
830 Serde<Cookie<'static>>,
831 CookieSource,
832 ),
833 EmbedderClearCookies(CookieOperationId),
835 EmbedderClearSessionCookies(CookieOperationId),
837 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
838 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
839 DeleteCookiesForSites(Vec<String>, GenericSender<()>),
840 DeleteCookies(Option<ServoUrl>, Option<GenericSender<()>>),
843 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 GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
855 SetHistoryState(HistoryStateId, Vec<u8>),
857 RemoveHistoryStates(Vec<HistoryStateId>),
859 GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
861 ClearCache(Option<GenericSender<()>>),
863 NetworkMediator(GenericSender<CustomResponseMediator>, ImmutableOrigin),
865 ToFileManager(FileManagerThreadMsg),
867 TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
868 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 RequestBuilder,
918 Option<ResponseInit>,
919 BoxedFetchCallback,
920 CoreResourceThread,
921 ),
922 FetchResponse(FetchResponseMsg),
923 Exit,
925}
926
927pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
928
929pub struct FetchThread {
933 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
936 receiver: Receiver<ToFetchThreadMessage>,
940 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 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 break;
1001 }
1002
1003 let preexisting_fetch =
1004 self.active_fetches.insert(request_builder_id, callback);
1005 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 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 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 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
1100pub 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
1110pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
1113 FetchThread::cancel_async_fetch(request_ids, core_resource_thread, false );
1114}
1115
1116pub 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 );
1124}
1125
1126#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
1127pub struct ResourceCorsData {
1128 pub preflight: bool,
1130 pub origin: ServoUrl,
1132}
1133
1134#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
1136pub struct Metadata {
1137 pub final_url: ServoUrl,
1139
1140 pub location_url: Option<Result<ServoUrl, String>>,
1142
1143 pub content_type: Option<Serde<ContentType>>,
1145
1146 pub charset: Option<String>,
1148
1149 pub headers: Option<Serde<HeaderMap>>,
1151
1152 pub status: HttpStatus,
1154
1155 pub referrer: Option<ServoUrl>,
1157
1158 pub referrer_policy: ReferrerPolicy,
1160 pub timing: Option<ResourceFetchTiming>,
1162 pub redirected: bool,
1164 pub tls_security_info: Option<TlsSecurityInfo>,
1166 pub redirect_taint: RedirectTaint,
1168}
1169
1170impl Metadata {
1171 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 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 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 pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1227 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#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1249pub enum CookieSource {
1250 HTTP,
1252 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#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1283pub enum NetworkError {
1284 LoadCancelled,
1285 SslValidation(String, Vec<u8>),
1287 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
1400pub 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
1422pub 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
1428pub 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
1446pub fn set_default_accept_language(headers: &mut HeaderMap) {
1448 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1451 return;
1452 }
1453
1454 headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1456}
1457
1458pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());