1#![deny(unsafe_code)]
6
7use std::fmt::{self, Debug, Display};
8use std::sync::{LazyLock, OnceLock};
9use std::thread::{self, JoinHandle};
10
11use content_security_policy::{self as csp};
12use cookie::Cookie;
13use crossbeam_channel::{Receiver, Sender, unbounded};
14use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
15use http::{HeaderMap, HeaderValue, StatusCode, header};
16use hyper_serde::Serde;
17use hyper_util::client::legacy::Error as HyperError;
18use ipc_channel::ipc::{self, IpcSender};
19use ipc_channel::router::ROUTER;
20use malloc_size_of::malloc_size_of_is_0;
21use malloc_size_of_derive::MallocSizeOf;
22use mime::Mime;
23use profile_traits::mem::ReportsChan;
24use rand::{Rng, rng};
25use request::RequestId;
26use rustc_hash::FxHashMap;
27use rustls_pki_types::CertificateDer;
28use serde::{Deserialize, Serialize};
29use servo_base::generic_channel::{
30 self, CallbackSetter, GenericCallback, GenericOneshotSender, GenericSend, GenericSender,
31 SendResult,
32};
33use servo_base::id::{CookieStoreId, HistoryStateId, PipelineId};
34use servo_url::{ImmutableOrigin, ServoUrl};
35use uuid::Uuid;
36
37#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
39pub struct CookieOperationId(pub u64);
40
41use crate::fetch::headers::determine_nosniff;
42use crate::filemanager_thread::FileManagerThreadMsg;
43use crate::http_status::HttpStatus;
44use crate::mime_classifier::{ApacheBugFlag, MimeClassifier};
45use crate::request::{PreloadId, Request, RequestBuilder};
46use crate::response::{Response, ResponseInit};
47
48pub mod blob_url_store;
49pub mod filemanager_thread;
50pub mod http_status;
51pub mod image_cache;
52pub mod mime_classifier;
53pub mod policy_container;
54pub mod pub_domains;
55pub mod quality;
56pub mod request;
57pub(crate) mod resource_fetch_timing;
58pub mod response;
59pub use resource_fetch_timing::{
60 RedirectEndValue, RedirectStartValue, ResourceAttribute, ResourceFetchTiming,
61 ResourceFetchTimingContainer, ResourceTimeValue, ResourceTimingType,
62};
63
64pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
66 HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
67
68pub mod fetch {
70 pub mod headers;
71}
72
73#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
76pub enum LoadContext {
77 Browsing,
78 Image,
79 AudioVideo,
80 Plugin,
81 Style,
82 Script,
83 Font,
84 TextTrack,
85 CacheManifest,
86}
87
88#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
89pub struct CustomResponse {
90 #[serde(
91 deserialize_with = "::hyper_serde::deserialize",
92 serialize_with = "::hyper_serde::serialize"
93 )]
94 pub headers: HeaderMap,
95 #[serde(
96 deserialize_with = "::hyper_serde::deserialize",
97 serialize_with = "::hyper_serde::serialize"
98 )]
99 pub raw_status: (StatusCode, String),
100 pub body: Vec<u8>,
101}
102
103impl CustomResponse {
104 pub fn new(
105 headers: HeaderMap,
106 raw_status: (StatusCode, String),
107 body: Vec<u8>,
108 ) -> CustomResponse {
109 CustomResponse {
110 headers,
111 raw_status,
112 body,
113 }
114 }
115}
116
117#[derive(Clone, Debug, Deserialize, Serialize)]
118pub struct CustomResponseMediator {
119 pub response_chan: IpcSender<Option<CustomResponse>>,
120 pub load_url: ServoUrl,
121}
122
123#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
126pub enum ReferrerPolicy {
127 EmptyString,
129 NoReferrer,
131 NoReferrerWhenDowngrade,
133 Origin,
135 SameOrigin,
137 OriginWhenCrossOrigin,
139 UnsafeUrl,
141 StrictOrigin,
143 #[default]
145 StrictOriginWhenCrossOrigin,
146}
147
148impl ReferrerPolicy {
149 pub fn from_with_legacy(value: &str) -> Self {
151 match value.to_ascii_lowercase().as_str() {
154 "never" => ReferrerPolicy::NoReferrer,
155 "default" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
156 "always" => ReferrerPolicy::UnsafeUrl,
157 "origin-when-crossorigin" => ReferrerPolicy::OriginWhenCrossOrigin,
158 _ => ReferrerPolicy::from(value),
159 }
160 }
161
162 pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
164 headers
166 .as_ref()
167 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
169 .into()
171 }
172}
173
174impl From<&str> for ReferrerPolicy {
175 fn from(value: &str) -> Self {
177 match value.to_ascii_lowercase().as_str() {
178 "no-referrer" => ReferrerPolicy::NoReferrer,
179 "no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
180 "origin" => ReferrerPolicy::Origin,
181 "same-origin" => ReferrerPolicy::SameOrigin,
182 "strict-origin" => ReferrerPolicy::StrictOrigin,
183 "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
184 "origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
185 "unsafe-url" => ReferrerPolicy::UnsafeUrl,
186 _ => ReferrerPolicy::EmptyString,
187 }
188 }
189}
190
191impl Display for ReferrerPolicy {
192 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 let string = match self {
194 ReferrerPolicy::EmptyString => "",
195 ReferrerPolicy::NoReferrer => "no-referrer",
196 ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
197 ReferrerPolicy::Origin => "origin",
198 ReferrerPolicy::SameOrigin => "same-origin",
199 ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
200 ReferrerPolicy::UnsafeUrl => "unsafe-url",
201 ReferrerPolicy::StrictOrigin => "strict-origin",
202 ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
203 };
204 write!(formatter, "{string}")
205 }
206}
207
208impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
210 fn from(header: Option<ReferrerPolicyHeader>) -> Self {
211 header.map_or(ReferrerPolicy::EmptyString, |policy| match policy {
214 ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer,
215 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => {
216 ReferrerPolicy::NoReferrerWhenDowngrade
217 },
218 ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin,
219 ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin,
220 ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin,
221 ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl,
222 ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin,
223 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => {
224 ReferrerPolicy::StrictOriginWhenCrossOrigin
225 },
226 })
227 }
228}
229
230impl From<ReferrerPolicy> for ReferrerPolicyHeader {
231 fn from(referrer_policy: ReferrerPolicy) -> Self {
232 match referrer_policy {
233 ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER,
234 ReferrerPolicy::NoReferrerWhenDowngrade => {
235 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE
236 },
237 ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN,
238 ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN,
239 ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN,
240 ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL,
241 ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN,
242 ReferrerPolicy::EmptyString | ReferrerPolicy::StrictOriginWhenCrossOrigin => {
243 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
244 },
245 }
246 }
247}
248
249#[expect(clippy::large_enum_variant)]
251#[derive(Debug, Deserialize, Serialize)]
252pub enum FetchResponseMsg {
253 ProcessRequestBody(RequestId),
255 ProcessResponse(RequestId, Result<FetchMetadata, NetworkError>),
257 ProcessResponseChunk(RequestId, DebugVec),
258 ProcessResponseEOF(RequestId, Result<(), NetworkError>, ResourceFetchTiming),
259 ProcessCspViolations(RequestId, Vec<csp::Violation>),
260 ProcessContentLength(RequestId, usize),
261}
262
263#[derive(Deserialize, PartialEq, Serialize, MallocSizeOf)]
264pub struct DebugVec(pub Vec<u8>);
265
266impl From<Vec<u8>> for DebugVec {
267 fn from(v: Vec<u8>) -> Self {
268 Self(v)
269 }
270}
271
272impl std::ops::Deref for DebugVec {
273 type Target = Vec<u8>;
274 fn deref(&self) -> &Self::Target {
275 &self.0
276 }
277}
278
279impl std::fmt::Debug for DebugVec {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 f.write_fmt(format_args!("[...; {}]", self.0.len()))
282 }
283}
284
285impl FetchResponseMsg {
286 pub fn request_id(&self) -> RequestId {
287 match self {
288 FetchResponseMsg::ProcessRequestBody(id) |
289 FetchResponseMsg::ProcessResponse(id, ..) |
290 FetchResponseMsg::ProcessResponseChunk(id, ..) |
291 FetchResponseMsg::ProcessResponseEOF(id, ..) |
292 FetchResponseMsg::ProcessCspViolations(id, ..) |
293 FetchResponseMsg::ProcessContentLength(id, _) => *id,
294 }
295 }
296}
297
298pub trait FetchTaskTarget {
299 fn process_request_body(&mut self, request: &Request);
303
304 fn process_response(&mut self, request: &Request, response: &Response);
308
309 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
311
312 fn process_response_eof(&mut self, request: &Request, response: &Response);
316
317 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
318
319 fn process_response_length_hint(&mut self, request_id: &Request, length: usize);
321}
322
323#[derive(Clone, Debug, Deserialize, Serialize)]
324pub enum FilteredMetadata {
325 Basic(Metadata),
326 Cors(Metadata),
327 Opaque,
328 OpaqueRedirect(ServoUrl),
329}
330
331#[expect(clippy::large_enum_variant)]
333#[derive(Clone, Debug, Deserialize, Serialize)]
334pub enum FetchMetadata {
335 Unfiltered(Metadata),
336 Filtered {
337 filtered: FilteredMetadata,
338 unsafe_: Metadata,
339 },
340}
341
342impl FetchMetadata {
343 pub fn metadata(&self) -> &Metadata {
344 match self {
345 Self::Unfiltered(metadata) => metadata,
346 Self::Filtered { unsafe_, .. } => unsafe_,
347 }
348 }
349
350 pub fn is_cors_cross_origin(&self) -> bool {
352 if let Self::Filtered { filtered, .. } = self {
353 match filtered {
354 FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_) => false,
355 FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_) => true,
356 }
357 } else {
358 false
359 }
360 }
361}
362
363impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
364 fn process_request_body(&mut self, request: &Request) {
365 let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
366 }
367
368 fn process_response(&mut self, request: &Request, response: &Response) {
369 let _ = self.send(FetchResponseMsg::ProcessResponse(
370 request.id,
371 response.metadata(),
372 ));
373 }
374
375 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
376 let _ = self.send(FetchResponseMsg::ProcessResponseChunk(
377 request.id,
378 chunk.into(),
379 ));
380 }
381
382 fn process_response_eof(&mut self, request: &Request, response: &Response) {
383 let result = response
384 .get_network_error()
385 .map_or_else(|| Ok(()), |network_error| Err(network_error.clone()));
386 let timing = response.get_resource_timing().inner().clone();
387
388 let _ = self.send(FetchResponseMsg::ProcessResponseEOF(
389 request.id, result, timing,
390 ));
391 }
392
393 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
394 let _ = self.send(FetchResponseMsg::ProcessCspViolations(
395 request.id, violations,
396 ));
397 }
398
399 fn process_response_length_hint(&mut self, request: &Request, length: usize) {
400 let _ = self.send(FetchResponseMsg::ProcessContentLength(request.id, length));
401 }
402}
403
404#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
405#[serde(rename_all = "lowercase")]
406pub enum TlsSecurityState {
407 #[default]
409 Insecure,
410 Weak,
412 Broken,
414 Secure,
416}
417
418impl Display for TlsSecurityState {
419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420 let text = match self {
421 TlsSecurityState::Insecure => "insecure",
422 TlsSecurityState::Weak => "weak",
423 TlsSecurityState::Broken => "broken",
424 TlsSecurityState::Secure => "secure",
425 };
426 f.write_str(text)
427 }
428}
429
430#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
431pub struct TlsSecurityInfo {
432 #[serde(default)]
434 pub state: TlsSecurityState,
435 pub weakness_reasons: Vec<String>,
437 pub protocol_version: Option<String>,
439 pub cipher_suite: Option<String>,
441 pub kea_group_name: Option<String>,
443 pub signature_scheme_name: Option<String>,
445 pub alpn_protocol: Option<String>,
447 pub certificate_chain_der: Vec<Vec<u8>>,
449 pub certificate_transparency: Option<String>,
451 pub hsts: bool,
453 pub hpkp: bool,
455 pub used_ech: bool,
457 pub used_delegated_credentials: bool,
459 pub used_ocsp: bool,
461 pub used_private_dns: bool,
463}
464
465impl FetchTaskTarget for IpcSender<WebSocketNetworkEvent> {
466 fn process_request_body(&mut self, _: &Request) {}
467 fn process_response(&mut self, _: &Request, response: &Response) {
468 if response.is_network_error() {
469 let _ = self.send(WebSocketNetworkEvent::Fail);
470 }
471 }
472 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
473 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
474 fn process_csp_violations(&mut self, _: &Request, violations: Vec<csp::Violation>) {
475 let _ = self.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
476 }
477 fn process_response_length_hint(&mut self, _: &Request, _: usize) {}
478}
479
480pub struct DiscardFetch;
484
485impl FetchTaskTarget for DiscardFetch {
486 fn process_request_body(&mut self, _: &Request) {}
487 fn process_response(&mut self, _: &Request, _: &Response) {}
488 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
489 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
490 fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
491 fn process_response_length_hint(&mut self, _: &Request, _: usize) {}
492}
493
494pub trait AsyncRuntime: Send {
497 fn shutdown(&mut self);
498}
499
500pub type CoreResourceThread = GenericSender<CoreResourceMsg>;
502
503#[derive(Clone, Debug, Deserialize, Serialize)]
509pub struct ResourceThreads {
510 pub core_thread: CoreResourceThread,
511}
512
513impl ResourceThreads {
514 pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
515 ResourceThreads { core_thread }
516 }
517
518 pub fn cache_entries(&self) -> Vec<CacheEntryDescriptor> {
519 let (sender, receiver) = generic_channel::channel().unwrap();
520 let _ = self
521 .core_thread
522 .send(CoreResourceMsg::GetCacheEntries(sender));
523 receiver.recv().unwrap()
524 }
525
526 pub fn clear_cache(&self) {
527 let (sender, receiver) = generic_channel::channel().unwrap();
534 let _ = self
535 .core_thread
536 .send(CoreResourceMsg::ClearCache(Some(sender)));
537 let _ = receiver.recv();
538 }
539
540 pub fn cookies(&self) -> Vec<SiteDescriptor> {
541 let (sender, receiver) = generic_channel::channel().unwrap();
542 let _ = self.core_thread.send(CoreResourceMsg::ListCookies(sender));
543 receiver.recv().unwrap()
544 }
545
546 pub fn clear_cookies_for_sites(&self, sites: &[&str]) {
547 let sites = sites.iter().map(|site| site.to_string()).collect();
548 let (sender, receiver) = generic_channel::channel().unwrap();
549 let _ = self
550 .core_thread
551 .send(CoreResourceMsg::DeleteCookiesForSites(sites, sender));
552 let _ = receiver.recv();
553 }
554
555 pub fn clear_cookies(&self) {
556 let (sender, receiver) = ipc::channel().unwrap();
557 let _ = self
558 .core_thread
559 .send(CoreResourceMsg::DeleteCookies(None, Some(sender)));
560 let _ = receiver.recv();
561 }
562
563 pub fn cookies_for_url(&self, url: ServoUrl, source: CookieSource) -> Vec<Cookie<'static>> {
564 let (sender, receiver) = generic_channel::channel().unwrap();
565 let _ = self
566 .core_thread
567 .send(CoreResourceMsg::GetCookiesForUrl(url, sender, source));
568 receiver
569 .recv()
570 .unwrap()
571 .into_iter()
572 .map(|cookie| cookie.into_inner())
573 .collect()
574 }
575
576 pub fn clear_session_cookies(&self) {
577 let (sender, receiver) = generic_channel::channel().unwrap();
578 let _ = self
579 .core_thread
580 .send(CoreResourceMsg::DeleteSessionCookies(sender));
581 let _ = receiver.recv();
582 }
583
584 pub fn set_cookie_for_url(&self, url: ServoUrl, cookie: Cookie<'static>, source: CookieSource) {
585 let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
586 url,
587 Serde(cookie),
588 source,
589 None,
590 ));
591 }
592
593 pub fn set_cookie_for_url_sync(
594 &self,
595 url: ServoUrl,
596 cookie: Cookie<'static>,
597 source: CookieSource,
598 ) {
599 let (sender, receiver) = generic_channel::channel().unwrap();
600 let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
601 url,
602 Serde(cookie),
603 source,
604 Some(sender),
605 ));
606 let _ = receiver.recv();
607 }
608
609 pub fn cookies_for_url_async(
610 &self,
611 id: CookieOperationId,
612 url: ServoUrl,
613 source: CookieSource,
614 ) {
615 let _ = self
616 .core_thread
617 .send(CoreResourceMsg::EmbedderGetCookiesForUrl(id, url, source));
618 }
619
620 pub fn set_cookie_for_url_async(
621 &self,
622 id: CookieOperationId,
623 url: ServoUrl,
624 cookie: Cookie<'static>,
625 source: CookieSource,
626 ) {
627 let _ = self
628 .core_thread
629 .send(CoreResourceMsg::EmbedderSetCookieForUrl(
630 id,
631 url,
632 Serde(cookie),
633 source,
634 ));
635 }
636
637 pub fn clear_cookies_async(&self, id: CookieOperationId) {
638 let _ = self
639 .core_thread
640 .send(CoreResourceMsg::EmbedderClearCookies(id));
641 }
642
643 pub fn clear_session_cookies_async(&self, id: CookieOperationId) {
644 let _ = self
645 .core_thread
646 .send(CoreResourceMsg::EmbedderClearSessionCookies(id));
647 }
648}
649
650impl GenericSend<CoreResourceMsg> for ResourceThreads {
651 fn send(&self, msg: CoreResourceMsg) -> SendResult {
652 self.core_thread.send(msg)
653 }
654
655 fn sender(&self) -> GenericSender<CoreResourceMsg> {
656 self.core_thread.clone()
657 }
658}
659
660malloc_size_of_is_0!(ResourceThreads);
662
663#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
664pub enum IncludeSubdomains {
665 Included,
666 NotIncluded,
667}
668
669#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
670pub enum MessageData {
671 Text(String),
672 Binary(Vec<u8>),
673}
674
675#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
676pub enum WebSocketDomAction {
677 SendMessage(MessageData),
678 Close(Option<u16>, Option<String>),
679}
680
681#[derive(Debug, Deserialize, Serialize)]
682pub enum WebSocketNetworkEvent {
683 ReportCSPViolations(Vec<csp::Violation>),
684 ConnectionEstablished { protocol_in_use: Option<String> },
685 MessageReceived(MessageData),
686 Close(Option<u16>, String),
687 Fail,
688}
689
690#[derive(Debug, Deserialize, Serialize)]
691pub enum FetchChannels {
693 ResponseMsg(IpcSender<FetchResponseMsg>),
694 WebSocket {
695 event_sender: IpcSender<WebSocketNetworkEvent>,
696 action_receiver: CallbackSetter<WebSocketDomAction>,
697 },
698 Prefetch,
701}
702
703#[derive(Debug, Deserialize, Serialize)]
704pub enum CoreResourceMsg {
705 Fetch(RequestBuilder, FetchChannels),
706 Cancel(Vec<RequestId>),
707 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
709 SetCookieForUrl(
712 ServoUrl,
713 Serde<Cookie<'static>>,
714 CookieSource,
715 Option<GenericSender<()>>,
716 ),
717 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
719 SetCookieForUrlAsync(
720 CookieStoreId,
721 ServoUrl,
722 Serde<Cookie<'static>>,
723 CookieSource,
724 ),
725 GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
727 GetCookiesForUrl(
730 ServoUrl,
731 GenericSender<Vec<Serde<Cookie<'static>>>>,
732 CookieSource,
733 ),
734 EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
737 EmbedderSetCookieForUrl(
740 CookieOperationId,
741 ServoUrl,
742 Serde<Cookie<'static>>,
743 CookieSource,
744 ),
745 EmbedderClearCookies(CookieOperationId),
747 EmbedderClearSessionCookies(CookieOperationId),
749 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
750 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
751 DeleteCookiesForSites(Vec<String>, GenericSender<()>),
752 DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
755 DeleteSessionCookies(GenericSender<()>),
757 DeleteCookie(ServoUrl, String),
758 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
759 NewCookieListener(
760 CookieStoreId,
761 GenericCallback<CookieAsyncResponse>,
762 ServoUrl,
763 ),
764 RemoveCookieListener(CookieStoreId),
765 ListCookies(GenericSender<Vec<SiteDescriptor>>),
766 GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
768 SetHistoryState(HistoryStateId, Vec<u8>),
770 RemoveHistoryStates(Vec<HistoryStateId>),
772 GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
774 ClearCache(Option<GenericSender<()>>),
776 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
778 ToFileManager(FileManagerThreadMsg),
780 StorePreloadedResponse(PreloadId, Response),
781 TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
782 Exit(GenericOneshotSender<()>),
785 CollectMemoryReport(ReportsChan),
786 RevokeTokenForFile(BlobTokenRevocationRequest),
787 RefreshTokenForFile(BlobTokenRefreshRequest),
788}
789
790#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
791pub struct BlobTokenRevocationRequest {
792 pub blob_id: Uuid,
793 pub token: Uuid,
794}
795
796#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
797pub struct BlobTokenRefreshRequest {
798 pub blob_id: Uuid,
799 pub new_token_sender: GenericSender<Uuid>,
800}
801
802#[derive(Clone, Debug, Deserialize, Serialize)]
803pub struct SiteDescriptor {
804 pub name: String,
805}
806
807impl SiteDescriptor {
808 pub fn new(name: String) -> Self {
809 SiteDescriptor { name }
810 }
811}
812
813#[derive(Clone, Debug, Deserialize, Serialize)]
814pub struct CacheEntryDescriptor {
815 pub key: String,
816}
817
818impl CacheEntryDescriptor {
819 pub fn new(key: String) -> Self {
820 Self { key }
821 }
822}
823
824#[expect(clippy::large_enum_variant)]
826enum ToFetchThreadMessage {
827 Cancel(Vec<RequestId>, CoreResourceThread),
828 StartFetch(
829 RequestBuilder,
830 Option<ResponseInit>,
831 BoxedFetchCallback,
832 CoreResourceThread,
833 ),
834 FetchResponse(FetchResponseMsg),
835 Exit,
837}
838
839pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
840
841struct FetchThread {
845 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
848 receiver: Receiver<ToFetchThreadMessage>,
852 to_fetch_sender: IpcSender<FetchResponseMsg>,
855}
856
857impl FetchThread {
858 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
859 let (sender, receiver) = unbounded();
860 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
861
862 let sender_clone = sender.clone();
863 ROUTER.add_typed_route(
864 from_fetch_sender,
865 Box::new(move |message| {
866 let message: FetchResponseMsg = message.unwrap();
867 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
868 }),
869 );
870 let join_handle = thread::Builder::new()
871 .name("FetchThread".to_owned())
872 .spawn(move || {
873 let mut fetch_thread = FetchThread {
874 active_fetches: FxHashMap::default(),
875 receiver,
876 to_fetch_sender,
877 };
878 fetch_thread.run();
879 })
880 .expect("Thread spawning failed");
881 (sender, join_handle)
882 }
883
884 fn run(&mut self) {
885 loop {
886 match self.receiver.recv() {
887 Ok(ToFetchThreadMessage::StartFetch(
888 request_builder,
889 response_init,
890 callback,
891 core_resource_thread,
892 )) => {
893 let request_builder_id = request_builder.id;
894
895 let message = match response_init {
897 Some(response_init) => CoreResourceMsg::FetchRedirect(
898 request_builder,
899 response_init,
900 self.to_fetch_sender.clone(),
901 ),
902 None => CoreResourceMsg::Fetch(
903 request_builder,
904 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
905 ),
906 };
907
908 if core_resource_thread.send(message).is_err() {
909 break;
913 }
914
915 let preexisting_fetch =
916 self.active_fetches.insert(request_builder_id, callback);
917 assert!(preexisting_fetch.is_none());
921 },
922 Ok(ToFetchThreadMessage::FetchResponse(fetch_response_msg)) => {
923 let request_id = fetch_response_msg.request_id();
924 let fetch_finished =
925 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
926
927 self.active_fetches
928 .get_mut(&request_id)
929 .expect("Got fetch response for unknown fetch")(
930 fetch_response_msg
931 );
932
933 if fetch_finished {
934 self.active_fetches.remove(&request_id);
935 }
936 },
937 Ok(ToFetchThreadMessage::Cancel(request_ids, core_resource_thread)) => {
938 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
942 },
943 Ok(ToFetchThreadMessage::Exit) | Err(_) => break,
944 }
945 }
946 }
947}
948
949static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
950
951pub fn start_fetch_thread() -> JoinHandle<()> {
954 let (sender, join_handle) = FetchThread::spawn();
955 FETCH_THREAD
956 .set(sender)
957 .expect("Fetch thread should be set only once on start-up");
958 join_handle
959}
960
961pub fn exit_fetch_thread() {
966 let _ = FETCH_THREAD
967 .get()
968 .expect("Fetch thread should always be initialized on start-up")
969 .send(ToFetchThreadMessage::Exit);
970}
971
972pub fn fetch_async(
974 core_resource_thread: &CoreResourceThread,
975 request: RequestBuilder,
976 response_init: Option<ResponseInit>,
977 callback: BoxedFetchCallback,
978) {
979 let _ = FETCH_THREAD
980 .get()
981 .expect("Fetch thread should always be initialized on start-up")
982 .send(ToFetchThreadMessage::StartFetch(
983 request,
984 response_init,
985 callback,
986 core_resource_thread.clone(),
987 ));
988}
989
990pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
993 let _ = FETCH_THREAD
994 .get()
995 .expect("Fetch thread should always be initialized on start-up")
996 .send(ToFetchThreadMessage::Cancel(
997 request_ids,
998 core_resource_thread.clone(),
999 ));
1000}
1001
1002#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
1003pub struct ResourceCorsData {
1004 pub preflight: bool,
1006 pub origin: ServoUrl,
1008}
1009
1010#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
1012pub struct Metadata {
1013 pub final_url: ServoUrl,
1015
1016 pub location_url: Option<Result<ServoUrl, String>>,
1018
1019 #[ignore_malloc_size_of = "Defined in hyper"]
1020 pub content_type: Option<Serde<ContentType>>,
1022
1023 pub charset: Option<String>,
1025
1026 #[ignore_malloc_size_of = "Defined in hyper"]
1027 pub headers: Option<Serde<HeaderMap>>,
1029
1030 pub status: HttpStatus,
1032
1033 pub referrer: Option<ServoUrl>,
1035
1036 pub referrer_policy: ReferrerPolicy,
1038 pub timing: Option<ResourceFetchTiming>,
1040 pub redirected: bool,
1042 pub tls_security_info: Option<TlsSecurityInfo>,
1044}
1045
1046impl Metadata {
1047 pub fn default(url: ServoUrl) -> Self {
1049 Metadata {
1050 final_url: url,
1051 location_url: None,
1052 content_type: None,
1053 charset: None,
1054 headers: None,
1055 status: HttpStatus::default(),
1056 referrer: None,
1057 referrer_policy: ReferrerPolicy::EmptyString,
1058 timing: None,
1059 redirected: false,
1060 tls_security_info: None,
1061 }
1062 }
1063
1064 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1066 if self.headers.is_none() {
1067 self.headers = Some(Serde(HeaderMap::new()));
1068 }
1069
1070 if let Some(mime) = content_type {
1071 self.headers
1072 .as_mut()
1073 .unwrap()
1074 .typed_insert(ContentType::from(mime.clone()));
1075 if let Some(charset) = mime.get_param(mime::CHARSET) {
1076 self.charset = Some(charset.to_string());
1077 }
1078 self.content_type = Some(Serde(ContentType::from(mime.clone())));
1079 }
1080 }
1081
1082 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1084 if referrer_policy == ReferrerPolicy::EmptyString {
1085 return;
1086 }
1087
1088 if self.headers.is_none() {
1089 self.headers = Some(Serde(HeaderMap::new()));
1090 }
1091
1092 self.referrer_policy = referrer_policy;
1093
1094 self.headers
1095 .as_mut()
1096 .unwrap()
1097 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1098 }
1099
1100 pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1102 let no_sniff = self
1104 .headers
1105 .as_deref()
1106 .is_some_and(determine_nosniff)
1107 .into();
1108 let mime = self
1109 .content_type
1110 .clone()
1111 .map(|content_type| content_type.into_inner().into());
1112 MimeClassifier::default().classify(
1113 load_context,
1114 no_sniff,
1115 ApacheBugFlag::from_content_type(mime.as_ref()),
1116 &mime,
1117 data,
1118 )
1119 }
1120}
1121
1122#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1124pub enum CookieSource {
1125 HTTP,
1127 NonHTTP,
1129}
1130
1131#[derive(Clone, Debug, Deserialize, Serialize)]
1132pub struct CookieChange {
1133 changed: Vec<Serde<Cookie<'static>>>,
1134 deleted: Vec<Serde<Cookie<'static>>>,
1135}
1136
1137#[derive(Clone, Debug, Deserialize, Serialize)]
1138pub enum CookieData {
1139 Change(CookieChange),
1140 Get(Option<Serde<Cookie<'static>>>),
1141 GetAll(Vec<Serde<Cookie<'static>>>),
1142 Set(Result<(), ()>),
1143 Delete(Result<(), ()>),
1144}
1145
1146#[derive(Clone, Debug, Deserialize, Serialize)]
1147pub struct CookieAsyncResponse {
1148 pub data: CookieData,
1149}
1150
1151#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1153pub enum NetworkError {
1154 LoadCancelled,
1155 SslValidation(String, Vec<u8>),
1157 Crash(String),
1159 UnsupportedScheme,
1160 CorsGeneral,
1161 CrossOriginResponse,
1162 CorsCredentials,
1163 CorsAllowMethods,
1164 CorsAllowHeaders,
1165 CorsMethod,
1166 CorsAuthorization,
1167 CorsHeaders,
1168 ConnectionFailure,
1169 RedirectError,
1170 TooManyRedirects,
1171 TooManyInFlightKeepAliveRequests,
1172 InvalidMethod,
1173 ResourceLoadError(String),
1174 ContentSecurityPolicy,
1175 Nosniff,
1176 MimeType(String),
1177 SubresourceIntegrity,
1178 MixedContent,
1179 CacheError,
1180 InvalidPort,
1181 WebsocketConnectionFailure(String),
1182 LocalDirectoryError,
1183 PartialResponseToNonRangeRequestError,
1184 ProtocolHandlerSubstitutionError,
1185 BlobURLStoreError(String),
1186 HttpError(String),
1187 DecompressionError,
1188}
1189
1190impl fmt::Debug for NetworkError {
1191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1192 match self {
1193 NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1194 NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1195 NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1196 NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1197 NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1198 NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1199 NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1200 NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1201 NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1202 NetworkError::ConnectionFailure => write!(f, "Request failed"),
1203 NetworkError::RedirectError => write!(f, "Redirect failed"),
1204 NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1205 NetworkError::TooManyInFlightKeepAliveRequests => {
1206 write!(f, "Too many in flight keep-alive requests")
1207 },
1208 NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1209 NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1210 NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1211 NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1212 NetworkError::MimeType(s) => write!(f, "{}", s),
1213 NetworkError::SubresourceIntegrity => {
1214 write!(f, "Subresource integrity validation failed")
1215 },
1216 NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1217 NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1218 NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1219 NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1220 NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1221 NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1222 NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1223 NetworkError::PartialResponseToNonRangeRequestError => write!(
1224 f,
1225 "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1226 ),
1227 NetworkError::ProtocolHandlerSubstitutionError => {
1228 write!(f, "Failed to parse substituted protocol handler url")
1229 },
1230 NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1231 NetworkError::WebsocketConnectionFailure(s) => {
1232 write!(f, "Websocket connection failure: {}", s)
1233 },
1234 NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1235 NetworkError::DecompressionError => write!(f, "Decompression error"),
1236 }
1237 }
1238}
1239
1240impl NetworkError {
1241 pub fn is_permanent_failure(&self) -> bool {
1242 matches!(
1243 self,
1244 NetworkError::ContentSecurityPolicy |
1245 NetworkError::MixedContent |
1246 NetworkError::SubresourceIntegrity |
1247 NetworkError::Nosniff |
1248 NetworkError::InvalidPort |
1249 NetworkError::CorsGeneral |
1250 NetworkError::CrossOriginResponse |
1251 NetworkError::CorsCredentials |
1252 NetworkError::CorsAllowMethods |
1253 NetworkError::CorsAllowHeaders |
1254 NetworkError::CorsMethod |
1255 NetworkError::CorsAuthorization |
1256 NetworkError::CorsHeaders |
1257 NetworkError::UnsupportedScheme
1258 )
1259 }
1260
1261 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1262 let error_string = error.to_string();
1263 match certificate {
1264 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1265 _ => NetworkError::HttpError(error_string),
1266 }
1267 }
1268}
1269
1270pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1273 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1274
1275 loop {
1276 match slice.split_first() {
1277 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1278 _ => break,
1279 }
1280 }
1281
1282 loop {
1283 match slice.split_last() {
1284 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1285 _ => break,
1286 }
1287 }
1288
1289 slice
1290}
1291
1292pub fn get_current_locale() -> &'static (String, HeaderValue) {
1294 static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1295
1296 CURRENT_LOCALE.get_or_init(|| {
1297 let locale_override = servo_config::pref!(intl_locale_override);
1298 let locale = if locale_override.is_empty() {
1299 sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1300 } else {
1301 locale_override
1302 };
1303 let header_value = HeaderValue::from_str(&locale)
1304 .ok()
1305 .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1306 (locale, header_value)
1307 })
1308}
1309
1310pub fn set_default_accept_language(headers: &mut HeaderMap) {
1312 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1315 return;
1316 }
1317
1318 headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1320}
1321
1322pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());