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
627impl GenericSend<CoreResourceMsg> for ResourceThreads {
628 fn send(&self, msg: CoreResourceMsg) -> SendResult {
629 self.core_thread.send(msg)
630 }
631
632 fn sender(&self) -> GenericSender<CoreResourceMsg> {
633 self.core_thread.clone()
634 }
635}
636
637malloc_size_of_is_0!(ResourceThreads);
639
640#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
641pub enum IncludeSubdomains {
642 Included,
643 NotIncluded,
644}
645
646#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
647pub enum MessageData {
648 Text(String),
649 Binary(Vec<u8>),
650}
651
652#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
653pub enum WebSocketDomAction {
654 SendMessage(MessageData),
655 Close(Option<u16>, Option<String>),
656}
657
658#[derive(Debug, Deserialize, Serialize)]
659pub enum WebSocketNetworkEvent {
660 ReportCSPViolations(Vec<csp::Violation>),
661 ConnectionEstablished { protocol_in_use: Option<String> },
662 MessageReceived(MessageData),
663 Close(Option<u16>, String),
664 Fail,
665}
666
667#[derive(Debug, Deserialize, Serialize)]
668pub enum FetchChannels {
670 ResponseMsg(IpcSender<FetchResponseMsg>),
671 WebSocket {
672 event_sender: IpcSender<WebSocketNetworkEvent>,
673 action_receiver: CallbackSetter<WebSocketDomAction>,
674 },
675 Prefetch,
678}
679
680#[derive(Debug, Deserialize, Serialize)]
681pub enum CoreResourceMsg {
682 Fetch(RequestBuilder, FetchChannels),
683 Cancel(Vec<RequestId>),
684 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
686 SetCookieForUrl(
689 ServoUrl,
690 Serde<Cookie<'static>>,
691 CookieSource,
692 Option<GenericSender<()>>,
693 ),
694 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
696 SetCookieForUrlAsync(
697 CookieStoreId,
698 ServoUrl,
699 Serde<Cookie<'static>>,
700 CookieSource,
701 ),
702 GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
704 GetCookiesForUrl(
707 ServoUrl,
708 GenericSender<Vec<Serde<Cookie<'static>>>>,
709 CookieSource,
710 ),
711 EmbedderGetCookiesForUrl(CookieOperationId, ServoUrl, CookieSource),
714 EmbedderSetCookieForUrl(
717 CookieOperationId,
718 ServoUrl,
719 Serde<Cookie<'static>>,
720 CookieSource,
721 ),
722 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
723 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
724 DeleteCookiesForSites(Vec<String>, GenericSender<()>),
725 DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
728 DeleteSessionCookies(GenericSender<()>),
730 DeleteCookie(ServoUrl, String),
731 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
732 NewCookieListener(
733 CookieStoreId,
734 GenericCallback<CookieAsyncResponse>,
735 ServoUrl,
736 ),
737 RemoveCookieListener(CookieStoreId),
738 ListCookies(GenericSender<Vec<SiteDescriptor>>),
739 GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
741 SetHistoryState(HistoryStateId, Vec<u8>),
743 RemoveHistoryStates(Vec<HistoryStateId>),
745 GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
747 ClearCache(Option<GenericSender<()>>),
749 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
751 ToFileManager(FileManagerThreadMsg),
753 StorePreloadedResponse(PreloadId, Response),
754 TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
755 Exit(GenericOneshotSender<()>),
758 CollectMemoryReport(ReportsChan),
759 RevokeTokenForFile(BlobTokenRevocationRequest),
760 RefreshTokenForFile(BlobTokenRefreshRequest),
761}
762
763#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
764pub struct BlobTokenRevocationRequest {
765 pub blob_id: Uuid,
766 pub token: Uuid,
767}
768
769#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
770pub struct BlobTokenRefreshRequest {
771 pub blob_id: Uuid,
772 pub new_token_sender: GenericSender<Uuid>,
773}
774
775#[derive(Clone, Debug, Deserialize, Serialize)]
776pub struct SiteDescriptor {
777 pub name: String,
778}
779
780impl SiteDescriptor {
781 pub fn new(name: String) -> Self {
782 SiteDescriptor { name }
783 }
784}
785
786#[derive(Clone, Debug, Deserialize, Serialize)]
787pub struct CacheEntryDescriptor {
788 pub key: String,
789}
790
791impl CacheEntryDescriptor {
792 pub fn new(key: String) -> Self {
793 Self { key }
794 }
795}
796
797#[expect(clippy::large_enum_variant)]
799enum ToFetchThreadMessage {
800 Cancel(Vec<RequestId>, CoreResourceThread),
801 StartFetch(
802 RequestBuilder,
803 Option<ResponseInit>,
804 BoxedFetchCallback,
805 CoreResourceThread,
806 ),
807 FetchResponse(FetchResponseMsg),
808 Exit,
810}
811
812pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
813
814struct FetchThread {
818 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
821 receiver: Receiver<ToFetchThreadMessage>,
825 to_fetch_sender: IpcSender<FetchResponseMsg>,
828}
829
830impl FetchThread {
831 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
832 let (sender, receiver) = unbounded();
833 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
834
835 let sender_clone = sender.clone();
836 ROUTER.add_typed_route(
837 from_fetch_sender,
838 Box::new(move |message| {
839 let message: FetchResponseMsg = message.unwrap();
840 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
841 }),
842 );
843 let join_handle = thread::Builder::new()
844 .name("FetchThread".to_owned())
845 .spawn(move || {
846 let mut fetch_thread = FetchThread {
847 active_fetches: FxHashMap::default(),
848 receiver,
849 to_fetch_sender,
850 };
851 fetch_thread.run();
852 })
853 .expect("Thread spawning failed");
854 (sender, join_handle)
855 }
856
857 fn run(&mut self) {
858 loop {
859 match self.receiver.recv().unwrap() {
860 ToFetchThreadMessage::StartFetch(
861 request_builder,
862 response_init,
863 callback,
864 core_resource_thread,
865 ) => {
866 let request_builder_id = request_builder.id;
867
868 let message = match response_init {
870 Some(response_init) => CoreResourceMsg::FetchRedirect(
871 request_builder,
872 response_init,
873 self.to_fetch_sender.clone(),
874 ),
875 None => CoreResourceMsg::Fetch(
876 request_builder,
877 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
878 ),
879 };
880
881 core_resource_thread.send(message).unwrap();
882
883 let preexisting_fetch =
884 self.active_fetches.insert(request_builder_id, callback);
885 assert!(preexisting_fetch.is_none());
889 },
890 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
891 let request_id = fetch_response_msg.request_id();
892 let fetch_finished =
893 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
894
895 self.active_fetches
896 .get_mut(&request_id)
897 .expect("Got fetch response for unknown fetch")(
898 fetch_response_msg
899 );
900
901 if fetch_finished {
902 self.active_fetches.remove(&request_id);
903 }
904 },
905 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
906 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
910 },
911 ToFetchThreadMessage::Exit => break,
912 }
913 }
914 }
915}
916
917static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
918
919pub fn start_fetch_thread() -> JoinHandle<()> {
922 let (sender, join_handle) = FetchThread::spawn();
923 FETCH_THREAD
924 .set(sender)
925 .expect("Fetch thread should be set only once on start-up");
926 join_handle
927}
928
929pub fn exit_fetch_thread() {
934 let _ = FETCH_THREAD
935 .get()
936 .expect("Fetch thread should always be initialized on start-up")
937 .send(ToFetchThreadMessage::Exit);
938}
939
940pub fn fetch_async(
942 core_resource_thread: &CoreResourceThread,
943 request: RequestBuilder,
944 response_init: Option<ResponseInit>,
945 callback: BoxedFetchCallback,
946) {
947 let _ = FETCH_THREAD
948 .get()
949 .expect("Fetch thread should always be initialized on start-up")
950 .send(ToFetchThreadMessage::StartFetch(
951 request,
952 response_init,
953 callback,
954 core_resource_thread.clone(),
955 ));
956}
957
958pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
961 let _ = FETCH_THREAD
962 .get()
963 .expect("Fetch thread should always be initialized on start-up")
964 .send(ToFetchThreadMessage::Cancel(
965 request_ids,
966 core_resource_thread.clone(),
967 ));
968}
969
970#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
971pub struct ResourceCorsData {
972 pub preflight: bool,
974 pub origin: ServoUrl,
976}
977
978#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
980pub struct Metadata {
981 pub final_url: ServoUrl,
983
984 pub location_url: Option<Result<ServoUrl, String>>,
986
987 #[ignore_malloc_size_of = "Defined in hyper"]
988 pub content_type: Option<Serde<ContentType>>,
990
991 pub charset: Option<String>,
993
994 #[ignore_malloc_size_of = "Defined in hyper"]
995 pub headers: Option<Serde<HeaderMap>>,
997
998 pub status: HttpStatus,
1000
1001 pub referrer: Option<ServoUrl>,
1003
1004 pub referrer_policy: ReferrerPolicy,
1006 pub timing: Option<ResourceFetchTiming>,
1008 pub redirected: bool,
1010 pub tls_security_info: Option<TlsSecurityInfo>,
1012}
1013
1014impl Metadata {
1015 pub fn default(url: ServoUrl) -> Self {
1017 Metadata {
1018 final_url: url,
1019 location_url: None,
1020 content_type: None,
1021 charset: None,
1022 headers: None,
1023 status: HttpStatus::default(),
1024 referrer: None,
1025 referrer_policy: ReferrerPolicy::EmptyString,
1026 timing: None,
1027 redirected: false,
1028 tls_security_info: None,
1029 }
1030 }
1031
1032 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1034 if self.headers.is_none() {
1035 self.headers = Some(Serde(HeaderMap::new()));
1036 }
1037
1038 if let Some(mime) = content_type {
1039 self.headers
1040 .as_mut()
1041 .unwrap()
1042 .typed_insert(ContentType::from(mime.clone()));
1043 if let Some(charset) = mime.get_param(mime::CHARSET) {
1044 self.charset = Some(charset.to_string());
1045 }
1046 self.content_type = Some(Serde(ContentType::from(mime.clone())));
1047 }
1048 }
1049
1050 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1052 if referrer_policy == ReferrerPolicy::EmptyString {
1053 return;
1054 }
1055
1056 if self.headers.is_none() {
1057 self.headers = Some(Serde(HeaderMap::new()));
1058 }
1059
1060 self.referrer_policy = referrer_policy;
1061
1062 self.headers
1063 .as_mut()
1064 .unwrap()
1065 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1066 }
1067
1068 pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1070 let no_sniff = self
1072 .headers
1073 .as_deref()
1074 .is_some_and(determine_nosniff)
1075 .into();
1076 let mime = self
1077 .content_type
1078 .clone()
1079 .map(|content_type| content_type.into_inner().into());
1080 MimeClassifier::default().classify(
1081 load_context,
1082 no_sniff,
1083 ApacheBugFlag::from_content_type(mime.as_ref()),
1084 &mime,
1085 data,
1086 )
1087 }
1088}
1089
1090#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1092pub enum CookieSource {
1093 HTTP,
1095 NonHTTP,
1097}
1098
1099#[derive(Clone, Debug, Deserialize, Serialize)]
1100pub struct CookieChange {
1101 changed: Vec<Serde<Cookie<'static>>>,
1102 deleted: Vec<Serde<Cookie<'static>>>,
1103}
1104
1105#[derive(Clone, Debug, Deserialize, Serialize)]
1106pub enum CookieData {
1107 Change(CookieChange),
1108 Get(Option<Serde<Cookie<'static>>>),
1109 GetAll(Vec<Serde<Cookie<'static>>>),
1110 Set(Result<(), ()>),
1111 Delete(Result<(), ()>),
1112}
1113
1114#[derive(Clone, Debug, Deserialize, Serialize)]
1115pub struct CookieAsyncResponse {
1116 pub data: CookieData,
1117}
1118
1119#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1121pub enum NetworkError {
1122 LoadCancelled,
1123 SslValidation(String, Vec<u8>),
1125 Crash(String),
1127 UnsupportedScheme,
1128 CorsGeneral,
1129 CrossOriginResponse,
1130 CorsCredentials,
1131 CorsAllowMethods,
1132 CorsAllowHeaders,
1133 CorsMethod,
1134 CorsAuthorization,
1135 CorsHeaders,
1136 ConnectionFailure,
1137 RedirectError,
1138 TooManyRedirects,
1139 TooManyInFlightKeepAliveRequests,
1140 InvalidMethod,
1141 ResourceLoadError(String),
1142 ContentSecurityPolicy,
1143 Nosniff,
1144 MimeType(String),
1145 SubresourceIntegrity,
1146 MixedContent,
1147 CacheError,
1148 InvalidPort,
1149 WebsocketConnectionFailure(String),
1150 LocalDirectoryError,
1151 PartialResponseToNonRangeRequestError,
1152 ProtocolHandlerSubstitutionError,
1153 BlobURLStoreError(String),
1154 HttpError(String),
1155 DecompressionError,
1156}
1157
1158impl fmt::Debug for NetworkError {
1159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1160 match self {
1161 NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1162 NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1163 NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1164 NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1165 NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1166 NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1167 NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1168 NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1169 NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1170 NetworkError::ConnectionFailure => write!(f, "Request failed"),
1171 NetworkError::RedirectError => write!(f, "Redirect failed"),
1172 NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1173 NetworkError::TooManyInFlightKeepAliveRequests => {
1174 write!(f, "Too many in flight keep-alive requests")
1175 },
1176 NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1177 NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1178 NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1179 NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1180 NetworkError::MimeType(s) => write!(f, "{}", s),
1181 NetworkError::SubresourceIntegrity => {
1182 write!(f, "Subresource integrity validation failed")
1183 },
1184 NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1185 NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1186 NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1187 NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1188 NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1189 NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1190 NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1191 NetworkError::PartialResponseToNonRangeRequestError => write!(
1192 f,
1193 "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1194 ),
1195 NetworkError::ProtocolHandlerSubstitutionError => {
1196 write!(f, "Failed to parse substituted protocol handler url")
1197 },
1198 NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1199 NetworkError::WebsocketConnectionFailure(s) => {
1200 write!(f, "Websocket connection failure: {}", s)
1201 },
1202 NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1203 NetworkError::DecompressionError => write!(f, "Decompression error"),
1204 }
1205 }
1206}
1207
1208impl NetworkError {
1209 pub fn is_permanent_failure(&self) -> bool {
1210 matches!(
1211 self,
1212 NetworkError::ContentSecurityPolicy |
1213 NetworkError::MixedContent |
1214 NetworkError::SubresourceIntegrity |
1215 NetworkError::Nosniff |
1216 NetworkError::InvalidPort |
1217 NetworkError::CorsGeneral |
1218 NetworkError::CrossOriginResponse |
1219 NetworkError::CorsCredentials |
1220 NetworkError::CorsAllowMethods |
1221 NetworkError::CorsAllowHeaders |
1222 NetworkError::CorsMethod |
1223 NetworkError::CorsAuthorization |
1224 NetworkError::CorsHeaders |
1225 NetworkError::UnsupportedScheme
1226 )
1227 }
1228
1229 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1230 let error_string = error.to_string();
1231 match certificate {
1232 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1233 _ => NetworkError::HttpError(error_string),
1234 }
1235 }
1236}
1237
1238pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1241 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1242
1243 loop {
1244 match slice.split_first() {
1245 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1246 _ => break,
1247 }
1248 }
1249
1250 loop {
1251 match slice.split_last() {
1252 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1253 _ => break,
1254 }
1255 }
1256
1257 slice
1258}
1259
1260pub fn http_percent_encode(bytes: &[u8]) -> String {
1261 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1264 .add(b' ')
1265 .add(b'"')
1266 .add(b'%')
1267 .add(b'\'')
1268 .add(b'(')
1269 .add(b')')
1270 .add(b'*')
1271 .add(b',')
1272 .add(b'/')
1273 .add(b':')
1274 .add(b';')
1275 .add(b'<')
1276 .add(b'-')
1277 .add(b'>')
1278 .add(b'?')
1279 .add(b'[')
1280 .add(b'\\')
1281 .add(b']')
1282 .add(b'{')
1283 .add(b'}');
1284
1285 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1286}
1287
1288pub fn get_current_locale() -> &'static (String, HeaderValue) {
1290 static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1291
1292 CURRENT_LOCALE.get_or_init(|| {
1293 let locale_override = servo_config::pref!(intl_locale_override);
1294 let locale = if locale_override.is_empty() {
1295 sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1296 } else {
1297 locale_override
1298 };
1299 let header_value = HeaderValue::from_str(&locale)
1300 .ok()
1301 .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1302 (locale, header_value)
1303 })
1304}
1305
1306pub fn set_default_accept_language(headers: &mut HeaderMap) {
1308 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1311 return;
1312 }
1313
1314 headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1316}
1317
1318pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());