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::{RngCore, rng};
25use request::RequestId;
26use rustc_hash::FxHashMap;
27use rustls_pki_types::CertificateDer;
28use serde::{Deserialize, Serialize};
29use servo_base::generic_channel::{
30 self, CallbackSetter, GenericCallback, GenericOneshotSender, GenericSend, GenericSender,
31 SendResult,
32};
33use servo_base::id::{CookieStoreId, HistoryStateId, PipelineId};
34use servo_url::{ImmutableOrigin, ServoUrl};
35use uuid::Uuid;
36
37#[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}
261
262#[derive(Deserialize, PartialEq, Serialize, MallocSizeOf)]
263pub struct DebugVec(pub Vec<u8>);
264
265impl From<Vec<u8>> for DebugVec {
266 fn from(v: Vec<u8>) -> Self {
267 Self(v)
268 }
269}
270
271impl std::ops::Deref for DebugVec {
272 type Target = Vec<u8>;
273 fn deref(&self) -> &Self::Target {
274 &self.0
275 }
276}
277
278impl std::fmt::Debug for DebugVec {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 f.write_fmt(format_args!("[...; {}]", self.0.len()))
281 }
282}
283
284impl FetchResponseMsg {
285 pub fn request_id(&self) -> RequestId {
286 match self {
287 FetchResponseMsg::ProcessRequestBody(id) |
288 FetchResponseMsg::ProcessResponse(id, ..) |
289 FetchResponseMsg::ProcessResponseChunk(id, ..) |
290 FetchResponseMsg::ProcessResponseEOF(id, ..) |
291 FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
292 }
293 }
294}
295
296pub trait FetchTaskTarget {
297 fn process_request_body(&mut self, request: &Request);
301
302 fn process_response(&mut self, request: &Request, response: &Response);
306
307 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
309
310 fn process_response_eof(&mut self, request: &Request, response: &Response);
314
315 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
316}
317
318#[derive(Clone, Debug, Deserialize, Serialize)]
319pub enum FilteredMetadata {
320 Basic(Metadata),
321 Cors(Metadata),
322 Opaque,
323 OpaqueRedirect(ServoUrl),
324}
325
326#[expect(clippy::large_enum_variant)]
328#[derive(Clone, Debug, Deserialize, Serialize)]
329pub enum FetchMetadata {
330 Unfiltered(Metadata),
331 Filtered {
332 filtered: FilteredMetadata,
333 unsafe_: Metadata,
334 },
335}
336
337impl FetchMetadata {
338 pub fn metadata(&self) -> &Metadata {
339 match self {
340 Self::Unfiltered(metadata) => metadata,
341 Self::Filtered { unsafe_, .. } => unsafe_,
342 }
343 }
344
345 pub fn is_cors_cross_origin(&self) -> bool {
347 if let Self::Filtered { filtered, .. } = self {
348 match filtered {
349 FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_) => false,
350 FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_) => true,
351 }
352 } else {
353 false
354 }
355 }
356}
357
358impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
359 fn process_request_body(&mut self, request: &Request) {
360 let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
361 }
362
363 fn process_response(&mut self, request: &Request, response: &Response) {
364 let _ = self.send(FetchResponseMsg::ProcessResponse(
365 request.id,
366 response.metadata(),
367 ));
368 }
369
370 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
371 let _ = self.send(FetchResponseMsg::ProcessResponseChunk(
372 request.id,
373 chunk.into(),
374 ));
375 }
376
377 fn process_response_eof(&mut self, request: &Request, response: &Response) {
378 let result = response
379 .get_network_error()
380 .map_or_else(|| Ok(()), |network_error| Err(network_error.clone()));
381 let timing = response.get_resource_timing().inner().clone();
382
383 let _ = self.send(FetchResponseMsg::ProcessResponseEOF(
384 request.id, result, timing,
385 ));
386 }
387
388 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
389 let _ = self.send(FetchResponseMsg::ProcessCspViolations(
390 request.id, violations,
391 ));
392 }
393}
394
395#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
396#[serde(rename_all = "lowercase")]
397pub enum TlsSecurityState {
398 #[default]
400 Insecure,
401 Weak,
403 Broken,
405 Secure,
407}
408
409impl Display for TlsSecurityState {
410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411 let text = match self {
412 TlsSecurityState::Insecure => "insecure",
413 TlsSecurityState::Weak => "weak",
414 TlsSecurityState::Broken => "broken",
415 TlsSecurityState::Secure => "secure",
416 };
417 f.write_str(text)
418 }
419}
420
421#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
422pub struct TlsSecurityInfo {
423 #[serde(default)]
425 pub state: TlsSecurityState,
426 pub weakness_reasons: Vec<String>,
428 pub protocol_version: Option<String>,
430 pub cipher_suite: Option<String>,
432 pub kea_group_name: Option<String>,
434 pub signature_scheme_name: Option<String>,
436 pub alpn_protocol: Option<String>,
438 pub certificate_chain_der: Vec<Vec<u8>>,
440 pub certificate_transparency: Option<String>,
442 pub hsts: bool,
444 pub hpkp: bool,
446 pub used_ech: bool,
448 pub used_delegated_credentials: bool,
450 pub used_ocsp: bool,
452 pub used_private_dns: bool,
454}
455
456impl FetchTaskTarget for IpcSender<WebSocketNetworkEvent> {
457 fn process_request_body(&mut self, _: &Request) {}
458 fn process_response(&mut self, _: &Request, response: &Response) {
459 if response.is_network_error() {
460 let _ = self.send(WebSocketNetworkEvent::Fail);
461 }
462 }
463 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
464 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
465 fn process_csp_violations(&mut self, _: &Request, violations: Vec<csp::Violation>) {
466 let _ = self.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
467 }
468}
469
470pub struct DiscardFetch;
474
475impl FetchTaskTarget for DiscardFetch {
476 fn process_request_body(&mut self, _: &Request) {}
477 fn process_response(&mut self, _: &Request, _: &Response) {}
478 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
479 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
480 fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
481}
482
483pub trait AsyncRuntime: Send {
486 fn shutdown(&mut self);
487}
488
489pub type CoreResourceThread = GenericSender<CoreResourceMsg>;
491
492#[derive(Clone, Debug, Deserialize, Serialize)]
498pub struct ResourceThreads {
499 pub core_thread: CoreResourceThread,
500}
501
502impl ResourceThreads {
503 pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
504 ResourceThreads { core_thread }
505 }
506
507 pub fn cache_entries(&self) -> Vec<CacheEntryDescriptor> {
508 let (sender, receiver) = generic_channel::channel().unwrap();
509 let _ = self
510 .core_thread
511 .send(CoreResourceMsg::GetCacheEntries(sender));
512 receiver.recv().unwrap()
513 }
514
515 pub fn clear_cache(&self) {
516 let (sender, receiver) = generic_channel::channel().unwrap();
523 let _ = self
524 .core_thread
525 .send(CoreResourceMsg::ClearCache(Some(sender)));
526 let _ = receiver.recv();
527 }
528
529 pub fn cookies(&self) -> Vec<SiteDescriptor> {
530 let (sender, receiver) = generic_channel::channel().unwrap();
531 let _ = self.core_thread.send(CoreResourceMsg::ListCookies(sender));
532 receiver.recv().unwrap()
533 }
534
535 pub fn clear_cookies_for_sites(&self, sites: &[&str]) {
536 let sites = sites.iter().map(|site| site.to_string()).collect();
537 let (sender, receiver) = generic_channel::channel().unwrap();
538 let _ = self
539 .core_thread
540 .send(CoreResourceMsg::DeleteCookiesForSites(sites, sender));
541 let _ = receiver.recv();
542 }
543
544 pub fn clear_cookies(&self) {
545 let (sender, receiver) = ipc::channel().unwrap();
546 let _ = self
547 .core_thread
548 .send(CoreResourceMsg::DeleteCookies(None, Some(sender)));
549 let _ = receiver.recv();
550 }
551
552 pub fn cookies_for_url(&self, url: ServoUrl, source: CookieSource) -> Vec<Cookie<'static>> {
553 let (sender, receiver) = generic_channel::channel().unwrap();
554 let _ = self
555 .core_thread
556 .send(CoreResourceMsg::GetCookiesForUrl(url, sender, source));
557 receiver
558 .recv()
559 .unwrap()
560 .into_iter()
561 .map(|cookie| cookie.into_inner())
562 .collect()
563 }
564
565 pub fn clear_session_cookies(&self) {
566 let (sender, receiver) = generic_channel::channel().unwrap();
567 let _ = self
568 .core_thread
569 .send(CoreResourceMsg::DeleteSessionCookies(sender));
570 let _ = receiver.recv();
571 }
572
573 pub fn set_cookie_for_url(&self, url: ServoUrl, cookie: Cookie<'static>, source: CookieSource) {
574 let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
575 url,
576 Serde(cookie),
577 source,
578 None,
579 ));
580 }
581
582 pub fn set_cookie_for_url_sync(
583 &self,
584 url: ServoUrl,
585 cookie: Cookie<'static>,
586 source: CookieSource,
587 ) {
588 let (sender, receiver) = generic_channel::channel().unwrap();
589 let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
590 url,
591 Serde(cookie),
592 source,
593 Some(sender),
594 ));
595 let _ = receiver.recv();
596 }
597
598 pub fn cookies_for_url_async(
599 &self,
600 id: CookieOperationId,
601 url: ServoUrl,
602 source: CookieSource,
603 ) {
604 let _ = self
605 .core_thread
606 .send(CoreResourceMsg::EmbedderGetCookiesForUrl(id, url, source));
607 }
608
609 pub fn set_cookie_for_url_async(
610 &self,
611 id: CookieOperationId,
612 url: ServoUrl,
613 cookie: Cookie<'static>,
614 source: CookieSource,
615 ) {
616 let _ = self
617 .core_thread
618 .send(CoreResourceMsg::EmbedderSetCookieForUrl(
619 id,
620 url,
621 Serde(cookie),
622 source,
623 ));
624 }
625
626 pub fn clear_cookies_async(&self, id: CookieOperationId) {
627 let _ = self
628 .core_thread
629 .send(CoreResourceMsg::EmbedderClearCookies(id));
630 }
631
632 pub fn clear_session_cookies_async(&self, id: CookieOperationId) {
633 let _ = self
634 .core_thread
635 .send(CoreResourceMsg::EmbedderClearSessionCookies(id));
636 }
637}
638
639impl GenericSend<CoreResourceMsg> for ResourceThreads {
640 fn send(&self, msg: CoreResourceMsg) -> SendResult {
641 self.core_thread.send(msg)
642 }
643
644 fn sender(&self) -> GenericSender<CoreResourceMsg> {
645 self.core_thread.clone()
646 }
647}
648
649malloc_size_of_is_0!(ResourceThreads);
651
652#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
653pub enum IncludeSubdomains {
654 Included,
655 NotIncluded,
656}
657
658#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
659pub enum MessageData {
660 Text(String),
661 Binary(Vec<u8>),
662}
663
664#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
665pub enum WebSocketDomAction {
666 SendMessage(MessageData),
667 Close(Option<u16>, Option<String>),
668}
669
670#[derive(Debug, Deserialize, Serialize)]
671pub enum WebSocketNetworkEvent {
672 ReportCSPViolations(Vec<csp::Violation>),
673 ConnectionEstablished { protocol_in_use: Option<String> },
674 MessageReceived(MessageData),
675 Close(Option<u16>, String),
676 Fail,
677}
678
679#[derive(Debug, Deserialize, Serialize)]
680pub enum FetchChannels {
682 ResponseMsg(IpcSender<FetchResponseMsg>),
683 WebSocket {
684 event_sender: IpcSender<WebSocketNetworkEvent>,
685 action_receiver: CallbackSetter<WebSocketDomAction>,
686 },
687 Prefetch,
690}
691
692#[derive(Debug, Deserialize, Serialize)]
693pub enum CoreResourceMsg {
694 Fetch(RequestBuilder, FetchChannels),
695 Cancel(Vec<RequestId>),
696 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
698 SetCookieForUrl(
701 ServoUrl,
702 Serde<Cookie<'static>>,
703 CookieSource,
704 Option<GenericSender<()>>,
705 ),
706 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
708 SetCookieForUrlAsync(
709 CookieStoreId,
710 ServoUrl,
711 Serde<Cookie<'static>>,
712 CookieSource,
713 ),
714 GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
716 GetCookiesForUrl(
719 ServoUrl,
720 GenericSender<Vec<Serde<Cookie<'static>>>>,
721 CookieSource,
722 ),
723 EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
726 EmbedderSetCookieForUrl(
729 CookieOperationId,
730 ServoUrl,
731 Serde<Cookie<'static>>,
732 CookieSource,
733 ),
734 EmbedderClearCookies(CookieOperationId),
736 EmbedderClearSessionCookies(CookieOperationId),
738 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
739 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
740 DeleteCookiesForSites(Vec<String>, GenericSender<()>),
741 DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
744 DeleteSessionCookies(GenericSender<()>),
746 DeleteCookie(ServoUrl, String),
747 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
748 NewCookieListener(
749 CookieStoreId,
750 GenericCallback<CookieAsyncResponse>,
751 ServoUrl,
752 ),
753 RemoveCookieListener(CookieStoreId),
754 ListCookies(GenericSender<Vec<SiteDescriptor>>),
755 GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
757 SetHistoryState(HistoryStateId, Vec<u8>),
759 RemoveHistoryStates(Vec<HistoryStateId>),
761 GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
763 ClearCache(Option<GenericSender<()>>),
765 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
767 ToFileManager(FileManagerThreadMsg),
769 StorePreloadedResponse(PreloadId, Response),
770 TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
771 Exit(GenericOneshotSender<()>),
774 CollectMemoryReport(ReportsChan),
775 RevokeTokenForFile(BlobTokenRevocationRequest),
776 RefreshTokenForFile(BlobTokenRefreshRequest),
777}
778
779#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
780pub struct BlobTokenRevocationRequest {
781 pub blob_id: Uuid,
782 pub token: Uuid,
783}
784
785#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
786pub struct BlobTokenRefreshRequest {
787 pub blob_id: Uuid,
788 pub new_token_sender: GenericSender<Uuid>,
789}
790
791#[derive(Clone, Debug, Deserialize, Serialize)]
792pub struct SiteDescriptor {
793 pub name: String,
794}
795
796impl SiteDescriptor {
797 pub fn new(name: String) -> Self {
798 SiteDescriptor { name }
799 }
800}
801
802#[derive(Clone, Debug, Deserialize, Serialize)]
803pub struct CacheEntryDescriptor {
804 pub key: String,
805}
806
807impl CacheEntryDescriptor {
808 pub fn new(key: String) -> Self {
809 Self { key }
810 }
811}
812
813#[expect(clippy::large_enum_variant)]
815enum ToFetchThreadMessage {
816 Cancel(Vec<RequestId>, CoreResourceThread),
817 StartFetch(
818 RequestBuilder,
819 Option<ResponseInit>,
820 BoxedFetchCallback,
821 CoreResourceThread,
822 ),
823 FetchResponse(FetchResponseMsg),
824 Exit,
826}
827
828pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
829
830struct FetchThread {
834 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
837 receiver: Receiver<ToFetchThreadMessage>,
841 to_fetch_sender: IpcSender<FetchResponseMsg>,
844}
845
846impl FetchThread {
847 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
848 let (sender, receiver) = unbounded();
849 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
850
851 let sender_clone = sender.clone();
852 ROUTER.add_typed_route(
853 from_fetch_sender,
854 Box::new(move |message| {
855 let message: FetchResponseMsg = message.unwrap();
856 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
857 }),
858 );
859 let join_handle = thread::Builder::new()
860 .name("FetchThread".to_owned())
861 .spawn(move || {
862 let mut fetch_thread = FetchThread {
863 active_fetches: FxHashMap::default(),
864 receiver,
865 to_fetch_sender,
866 };
867 fetch_thread.run();
868 })
869 .expect("Thread spawning failed");
870 (sender, join_handle)
871 }
872
873 fn run(&mut self) {
874 loop {
875 match self.receiver.recv().unwrap() {
876 ToFetchThreadMessage::StartFetch(
877 request_builder,
878 response_init,
879 callback,
880 core_resource_thread,
881 ) => {
882 let request_builder_id = request_builder.id;
883
884 let message = match response_init {
886 Some(response_init) => CoreResourceMsg::FetchRedirect(
887 request_builder,
888 response_init,
889 self.to_fetch_sender.clone(),
890 ),
891 None => CoreResourceMsg::Fetch(
892 request_builder,
893 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
894 ),
895 };
896
897 core_resource_thread.send(message).unwrap();
898
899 let preexisting_fetch =
900 self.active_fetches.insert(request_builder_id, callback);
901 assert!(preexisting_fetch.is_none());
905 },
906 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
907 let request_id = fetch_response_msg.request_id();
908 let fetch_finished =
909 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
910
911 self.active_fetches
912 .get_mut(&request_id)
913 .expect("Got fetch response for unknown fetch")(
914 fetch_response_msg
915 );
916
917 if fetch_finished {
918 self.active_fetches.remove(&request_id);
919 }
920 },
921 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
922 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
926 },
927 ToFetchThreadMessage::Exit => break,
928 }
929 }
930 }
931}
932
933static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
934
935pub fn start_fetch_thread() -> JoinHandle<()> {
938 let (sender, join_handle) = FetchThread::spawn();
939 FETCH_THREAD
940 .set(sender)
941 .expect("Fetch thread should be set only once on start-up");
942 join_handle
943}
944
945pub fn exit_fetch_thread() {
950 let _ = FETCH_THREAD
951 .get()
952 .expect("Fetch thread should always be initialized on start-up")
953 .send(ToFetchThreadMessage::Exit);
954}
955
956pub fn fetch_async(
958 core_resource_thread: &CoreResourceThread,
959 request: RequestBuilder,
960 response_init: Option<ResponseInit>,
961 callback: BoxedFetchCallback,
962) {
963 let _ = FETCH_THREAD
964 .get()
965 .expect("Fetch thread should always be initialized on start-up")
966 .send(ToFetchThreadMessage::StartFetch(
967 request,
968 response_init,
969 callback,
970 core_resource_thread.clone(),
971 ));
972}
973
974pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
977 let _ = FETCH_THREAD
978 .get()
979 .expect("Fetch thread should always be initialized on start-up")
980 .send(ToFetchThreadMessage::Cancel(
981 request_ids,
982 core_resource_thread.clone(),
983 ));
984}
985
986#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
987pub struct ResourceCorsData {
988 pub preflight: bool,
990 pub origin: ServoUrl,
992}
993
994#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
996pub struct Metadata {
997 pub final_url: ServoUrl,
999
1000 pub location_url: Option<Result<ServoUrl, String>>,
1002
1003 #[ignore_malloc_size_of = "Defined in hyper"]
1004 pub content_type: Option<Serde<ContentType>>,
1006
1007 pub charset: Option<String>,
1009
1010 #[ignore_malloc_size_of = "Defined in hyper"]
1011 pub headers: Option<Serde<HeaderMap>>,
1013
1014 pub status: HttpStatus,
1016
1017 pub referrer: Option<ServoUrl>,
1019
1020 pub referrer_policy: ReferrerPolicy,
1022 pub timing: Option<ResourceFetchTiming>,
1024 pub redirected: bool,
1026 pub tls_security_info: Option<TlsSecurityInfo>,
1028}
1029
1030impl Metadata {
1031 pub fn default(url: ServoUrl) -> Self {
1033 Metadata {
1034 final_url: url,
1035 location_url: None,
1036 content_type: None,
1037 charset: None,
1038 headers: None,
1039 status: HttpStatus::default(),
1040 referrer: None,
1041 referrer_policy: ReferrerPolicy::EmptyString,
1042 timing: None,
1043 redirected: false,
1044 tls_security_info: None,
1045 }
1046 }
1047
1048 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1050 if self.headers.is_none() {
1051 self.headers = Some(Serde(HeaderMap::new()));
1052 }
1053
1054 if let Some(mime) = content_type {
1055 self.headers
1056 .as_mut()
1057 .unwrap()
1058 .typed_insert(ContentType::from(mime.clone()));
1059 if let Some(charset) = mime.get_param(mime::CHARSET) {
1060 self.charset = Some(charset.to_string());
1061 }
1062 self.content_type = Some(Serde(ContentType::from(mime.clone())));
1063 }
1064 }
1065
1066 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1068 if referrer_policy == ReferrerPolicy::EmptyString {
1069 return;
1070 }
1071
1072 if self.headers.is_none() {
1073 self.headers = Some(Serde(HeaderMap::new()));
1074 }
1075
1076 self.referrer_policy = referrer_policy;
1077
1078 self.headers
1079 .as_mut()
1080 .unwrap()
1081 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1082 }
1083
1084 pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1086 let no_sniff = self
1088 .headers
1089 .as_deref()
1090 .is_some_and(determine_nosniff)
1091 .into();
1092 let mime = self
1093 .content_type
1094 .clone()
1095 .map(|content_type| content_type.into_inner().into());
1096 MimeClassifier::default().classify(
1097 load_context,
1098 no_sniff,
1099 ApacheBugFlag::from_content_type(mime.as_ref()),
1100 &mime,
1101 data,
1102 )
1103 }
1104}
1105
1106#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1108pub enum CookieSource {
1109 HTTP,
1111 NonHTTP,
1113}
1114
1115#[derive(Clone, Debug, Deserialize, Serialize)]
1116pub struct CookieChange {
1117 changed: Vec<Serde<Cookie<'static>>>,
1118 deleted: Vec<Serde<Cookie<'static>>>,
1119}
1120
1121#[derive(Clone, Debug, Deserialize, Serialize)]
1122pub enum CookieData {
1123 Change(CookieChange),
1124 Get(Option<Serde<Cookie<'static>>>),
1125 GetAll(Vec<Serde<Cookie<'static>>>),
1126 Set(Result<(), ()>),
1127 Delete(Result<(), ()>),
1128}
1129
1130#[derive(Clone, Debug, Deserialize, Serialize)]
1131pub struct CookieAsyncResponse {
1132 pub data: CookieData,
1133}
1134
1135#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1137pub enum NetworkError {
1138 LoadCancelled,
1139 SslValidation(String, Vec<u8>),
1141 Crash(String),
1143 UnsupportedScheme,
1144 CorsGeneral,
1145 CrossOriginResponse,
1146 CorsCredentials,
1147 CorsAllowMethods,
1148 CorsAllowHeaders,
1149 CorsMethod,
1150 CorsAuthorization,
1151 CorsHeaders,
1152 ConnectionFailure,
1153 RedirectError,
1154 TooManyRedirects,
1155 TooManyInFlightKeepAliveRequests,
1156 InvalidMethod,
1157 ResourceLoadError(String),
1158 ContentSecurityPolicy,
1159 Nosniff,
1160 MimeType(String),
1161 SubresourceIntegrity,
1162 MixedContent,
1163 CacheError,
1164 InvalidPort,
1165 WebsocketConnectionFailure(String),
1166 LocalDirectoryError,
1167 PartialResponseToNonRangeRequestError,
1168 ProtocolHandlerSubstitutionError,
1169 BlobURLStoreError(String),
1170 HttpError(String),
1171 DecompressionError,
1172}
1173
1174impl fmt::Debug for NetworkError {
1175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1176 match self {
1177 NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1178 NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1179 NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1180 NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1181 NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1182 NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1183 NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1184 NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1185 NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1186 NetworkError::ConnectionFailure => write!(f, "Request failed"),
1187 NetworkError::RedirectError => write!(f, "Redirect failed"),
1188 NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1189 NetworkError::TooManyInFlightKeepAliveRequests => {
1190 write!(f, "Too many in flight keep-alive requests")
1191 },
1192 NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1193 NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1194 NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1195 NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1196 NetworkError::MimeType(s) => write!(f, "{}", s),
1197 NetworkError::SubresourceIntegrity => {
1198 write!(f, "Subresource integrity validation failed")
1199 },
1200 NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1201 NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1202 NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1203 NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1204 NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1205 NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1206 NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1207 NetworkError::PartialResponseToNonRangeRequestError => write!(
1208 f,
1209 "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1210 ),
1211 NetworkError::ProtocolHandlerSubstitutionError => {
1212 write!(f, "Failed to parse substituted protocol handler url")
1213 },
1214 NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1215 NetworkError::WebsocketConnectionFailure(s) => {
1216 write!(f, "Websocket connection failure: {}", s)
1217 },
1218 NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1219 NetworkError::DecompressionError => write!(f, "Decompression error"),
1220 }
1221 }
1222}
1223
1224impl NetworkError {
1225 pub fn is_permanent_failure(&self) -> bool {
1226 matches!(
1227 self,
1228 NetworkError::ContentSecurityPolicy |
1229 NetworkError::MixedContent |
1230 NetworkError::SubresourceIntegrity |
1231 NetworkError::Nosniff |
1232 NetworkError::InvalidPort |
1233 NetworkError::CorsGeneral |
1234 NetworkError::CrossOriginResponse |
1235 NetworkError::CorsCredentials |
1236 NetworkError::CorsAllowMethods |
1237 NetworkError::CorsAllowHeaders |
1238 NetworkError::CorsMethod |
1239 NetworkError::CorsAuthorization |
1240 NetworkError::CorsHeaders |
1241 NetworkError::UnsupportedScheme
1242 )
1243 }
1244
1245 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1246 let error_string = error.to_string();
1247 match certificate {
1248 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1249 _ => NetworkError::HttpError(error_string),
1250 }
1251 }
1252}
1253
1254pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1257 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1258
1259 loop {
1260 match slice.split_first() {
1261 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1262 _ => break,
1263 }
1264 }
1265
1266 loop {
1267 match slice.split_last() {
1268 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1269 _ => break,
1270 }
1271 }
1272
1273 slice
1274}
1275
1276pub fn http_percent_encode(bytes: &[u8]) -> String {
1277 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1280 .add(b' ')
1281 .add(b'"')
1282 .add(b'%')
1283 .add(b'\'')
1284 .add(b'(')
1285 .add(b')')
1286 .add(b'*')
1287 .add(b',')
1288 .add(b'/')
1289 .add(b':')
1290 .add(b';')
1291 .add(b'<')
1292 .add(b'-')
1293 .add(b'>')
1294 .add(b'?')
1295 .add(b'[')
1296 .add(b'\\')
1297 .add(b']')
1298 .add(b'{')
1299 .add(b'}');
1300
1301 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1302}
1303
1304pub fn get_current_locale() -> &'static (String, HeaderValue) {
1306 static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1307
1308 CURRENT_LOCALE.get_or_init(|| {
1309 let locale_override = servo_config::pref!(intl_locale_override);
1310 let locale = if locale_override.is_empty() {
1311 sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1312 } else {
1313 locale_override
1314 };
1315 let header_value = HeaderValue::from_str(&locale)
1316 .ok()
1317 .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1318 (locale, header_value)
1319 })
1320}
1321
1322pub fn set_default_accept_language(headers: &mut HeaderMap) {
1324 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1327 return;
1328 }
1329
1330 headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1332}
1333
1334pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());