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::cross_process_instant::CrossProcessInstant;
30use servo_base::generic_channel::{
31 self, CallbackSetter, GenericCallback, GenericOneshotSender, GenericSend, GenericSender,
32 SendResult,
33};
34use servo_base::id::{CookieStoreId, HistoryStateId, PipelineId};
35use servo_url::{ImmutableOrigin, ServoUrl};
36
37use crate::fetch::headers::determine_nosniff;
38use crate::filemanager_thread::FileManagerThreadMsg;
39use crate::http_status::HttpStatus;
40use crate::mime_classifier::{ApacheBugFlag, MimeClassifier};
41use crate::request::{PreloadId, Request, RequestBuilder};
42use crate::response::{HttpsState, Response, ResponseInit};
43
44pub mod blob_url_store;
45pub mod filemanager_thread;
46pub mod http_status;
47pub mod image_cache;
48pub mod mime_classifier;
49pub mod policy_container;
50pub mod pub_domains;
51pub mod quality;
52pub mod request;
53pub mod response;
54
55pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
57 HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
58
59pub mod fetch {
61 pub mod headers;
62}
63
64#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
67pub enum LoadContext {
68 Browsing,
69 Image,
70 AudioVideo,
71 Plugin,
72 Style,
73 Script,
74 Font,
75 TextTrack,
76 CacheManifest,
77}
78
79#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
80pub struct CustomResponse {
81 #[ignore_malloc_size_of = "Defined in hyper"]
82 #[serde(
83 deserialize_with = "::hyper_serde::deserialize",
84 serialize_with = "::hyper_serde::serialize"
85 )]
86 pub headers: HeaderMap,
87 #[ignore_malloc_size_of = "Defined in hyper"]
88 #[serde(
89 deserialize_with = "::hyper_serde::deserialize",
90 serialize_with = "::hyper_serde::serialize"
91 )]
92 pub raw_status: (StatusCode, String),
93 pub body: Vec<u8>,
94}
95
96impl CustomResponse {
97 pub fn new(
98 headers: HeaderMap,
99 raw_status: (StatusCode, String),
100 body: Vec<u8>,
101 ) -> CustomResponse {
102 CustomResponse {
103 headers,
104 raw_status,
105 body,
106 }
107 }
108}
109
110#[derive(Clone, Debug, Deserialize, Serialize)]
111pub struct CustomResponseMediator {
112 pub response_chan: IpcSender<Option<CustomResponse>>,
113 pub load_url: ServoUrl,
114}
115
116#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
119pub enum ReferrerPolicy {
120 EmptyString,
122 NoReferrer,
124 NoReferrerWhenDowngrade,
126 Origin,
128 SameOrigin,
130 OriginWhenCrossOrigin,
132 UnsafeUrl,
134 StrictOrigin,
136 #[default]
138 StrictOriginWhenCrossOrigin,
139}
140
141impl ReferrerPolicy {
142 pub fn from_with_legacy(value: &str) -> Self {
144 match value.to_ascii_lowercase().as_str() {
147 "never" => ReferrerPolicy::NoReferrer,
148 "default" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
149 "always" => ReferrerPolicy::UnsafeUrl,
150 "origin-when-crossorigin" => ReferrerPolicy::OriginWhenCrossOrigin,
151 _ => ReferrerPolicy::from(value),
152 }
153 }
154
155 pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
157 headers
159 .as_ref()
160 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
162 .into()
164 }
165}
166
167impl From<&str> for ReferrerPolicy {
168 fn from(value: &str) -> Self {
170 match value.to_ascii_lowercase().as_str() {
171 "no-referrer" => ReferrerPolicy::NoReferrer,
172 "no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
173 "origin" => ReferrerPolicy::Origin,
174 "same-origin" => ReferrerPolicy::SameOrigin,
175 "strict-origin" => ReferrerPolicy::StrictOrigin,
176 "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
177 "origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
178 "unsafe-url" => ReferrerPolicy::UnsafeUrl,
179 _ => ReferrerPolicy::EmptyString,
180 }
181 }
182}
183
184impl Display for ReferrerPolicy {
185 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 let string = match self {
187 ReferrerPolicy::EmptyString => "",
188 ReferrerPolicy::NoReferrer => "no-referrer",
189 ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
190 ReferrerPolicy::Origin => "origin",
191 ReferrerPolicy::SameOrigin => "same-origin",
192 ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
193 ReferrerPolicy::UnsafeUrl => "unsafe-url",
194 ReferrerPolicy::StrictOrigin => "strict-origin",
195 ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
196 };
197 write!(formatter, "{string}")
198 }
199}
200
201impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
203 fn from(header: Option<ReferrerPolicyHeader>) -> Self {
204 header.map_or(ReferrerPolicy::EmptyString, |policy| match policy {
207 ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer,
208 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => {
209 ReferrerPolicy::NoReferrerWhenDowngrade
210 },
211 ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin,
212 ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin,
213 ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin,
214 ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl,
215 ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin,
216 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => {
217 ReferrerPolicy::StrictOriginWhenCrossOrigin
218 },
219 })
220 }
221}
222
223impl From<ReferrerPolicy> for ReferrerPolicyHeader {
224 fn from(referrer_policy: ReferrerPolicy) -> Self {
225 match referrer_policy {
226 ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER,
227 ReferrerPolicy::NoReferrerWhenDowngrade => {
228 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE
229 },
230 ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN,
231 ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN,
232 ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN,
233 ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL,
234 ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN,
235 ReferrerPolicy::EmptyString | ReferrerPolicy::StrictOriginWhenCrossOrigin => {
236 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
237 },
238 }
239 }
240}
241
242#[expect(clippy::large_enum_variant)]
244#[derive(Debug, Deserialize, Serialize)]
245pub enum FetchResponseMsg {
246 ProcessRequestBody(RequestId),
248 ProcessResponse(RequestId, Result<FetchMetadata, NetworkError>),
250 ProcessResponseChunk(RequestId, DebugVec),
251 ProcessResponseEOF(RequestId, Result<(), NetworkError>, ResourceFetchTiming),
252 ProcessCspViolations(RequestId, Vec<csp::Violation>),
253}
254
255#[derive(Deserialize, PartialEq, Serialize, MallocSizeOf)]
256pub struct DebugVec(pub Vec<u8>);
257
258impl From<Vec<u8>> for DebugVec {
259 fn from(v: Vec<u8>) -> Self {
260 Self(v)
261 }
262}
263
264impl std::ops::Deref for DebugVec {
265 type Target = Vec<u8>;
266 fn deref(&self) -> &Self::Target {
267 &self.0
268 }
269}
270
271impl std::fmt::Debug for DebugVec {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273 f.write_fmt(format_args!("[...; {}]", self.0.len()))
274 }
275}
276
277impl FetchResponseMsg {
278 pub fn request_id(&self) -> RequestId {
279 match self {
280 FetchResponseMsg::ProcessRequestBody(id) |
281 FetchResponseMsg::ProcessResponse(id, ..) |
282 FetchResponseMsg::ProcessResponseChunk(id, ..) |
283 FetchResponseMsg::ProcessResponseEOF(id, ..) |
284 FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
285 }
286 }
287}
288
289pub trait FetchTaskTarget {
290 fn process_request_body(&mut self, request: &Request);
294
295 fn process_response(&mut self, request: &Request, response: &Response);
299
300 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
302
303 fn process_response_eof(&mut self, request: &Request, response: &Response);
307
308 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
309}
310
311#[derive(Clone, Debug, Deserialize, Serialize)]
312pub enum FilteredMetadata {
313 Basic(Metadata),
314 Cors(Metadata),
315 Opaque,
316 OpaqueRedirect(ServoUrl),
317}
318
319#[expect(clippy::large_enum_variant)]
321#[derive(Clone, Debug, Deserialize, Serialize)]
322pub enum FetchMetadata {
323 Unfiltered(Metadata),
324 Filtered {
325 filtered: FilteredMetadata,
326 unsafe_: Metadata,
327 },
328}
329
330impl FetchMetadata {
331 pub fn metadata(&self) -> &Metadata {
332 match self {
333 Self::Unfiltered(metadata) => metadata,
334 Self::Filtered { unsafe_, .. } => unsafe_,
335 }
336 }
337
338 pub fn is_cors_cross_origin(&self) -> bool {
340 if let Self::Filtered { filtered, .. } = self {
341 match filtered {
342 FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_) => false,
343 FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_) => true,
344 }
345 } else {
346 false
347 }
348 }
349}
350
351impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
352 fn process_request_body(&mut self, request: &Request) {
353 let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
354 }
355
356 fn process_response(&mut self, request: &Request, response: &Response) {
357 let _ = self.send(FetchResponseMsg::ProcessResponse(
358 request.id,
359 response.metadata(),
360 ));
361 }
362
363 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
364 let _ = self.send(FetchResponseMsg::ProcessResponseChunk(
365 request.id,
366 chunk.into(),
367 ));
368 }
369
370 fn process_response_eof(&mut self, request: &Request, response: &Response) {
371 let result = response
372 .get_network_error()
373 .map_or_else(|| Ok(()), |network_error| Err(network_error.clone()));
374 let timing = response.get_resource_timing().lock().clone();
375
376 let _ = self.send(FetchResponseMsg::ProcessResponseEOF(
377 request.id, result, timing,
378 ));
379 }
380
381 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
382 let _ = self.send(FetchResponseMsg::ProcessCspViolations(
383 request.id, violations,
384 ));
385 }
386}
387
388#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
389#[serde(rename_all = "lowercase")]
390pub enum TlsSecurityState {
391 #[default]
393 Insecure,
394 Weak,
396 Broken,
398 Secure,
400}
401
402impl Display for TlsSecurityState {
403 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 let text = match self {
405 TlsSecurityState::Insecure => "insecure",
406 TlsSecurityState::Weak => "weak",
407 TlsSecurityState::Broken => "broken",
408 TlsSecurityState::Secure => "secure",
409 };
410 f.write_str(text)
411 }
412}
413
414#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
415pub struct TlsSecurityInfo {
416 #[serde(default)]
418 pub state: TlsSecurityState,
419 pub weakness_reasons: Vec<String>,
421 pub protocol_version: Option<String>,
423 pub cipher_suite: Option<String>,
425 pub kea_group_name: Option<String>,
427 pub signature_scheme_name: Option<String>,
429 pub alpn_protocol: Option<String>,
431 pub certificate_chain_der: Vec<Vec<u8>>,
433 pub certificate_transparency: Option<String>,
435 pub hsts: bool,
437 pub hpkp: bool,
439 pub used_ech: bool,
441 pub used_delegated_credentials: bool,
443 pub used_ocsp: bool,
445 pub used_private_dns: bool,
447}
448
449impl FetchTaskTarget for IpcSender<WebSocketNetworkEvent> {
450 fn process_request_body(&mut self, _: &Request) {}
451 fn process_response(&mut self, _: &Request, response: &Response) {
452 if response.is_network_error() {
453 let _ = self.send(WebSocketNetworkEvent::Fail);
454 }
455 }
456 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
457 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
458 fn process_csp_violations(&mut self, _: &Request, violations: Vec<csp::Violation>) {
459 let _ = self.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
460 }
461}
462
463pub struct DiscardFetch;
467
468impl FetchTaskTarget for DiscardFetch {
469 fn process_request_body(&mut self, _: &Request) {}
470 fn process_response(&mut self, _: &Request, _: &Response) {}
471 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
472 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
473 fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
474}
475
476pub trait AsyncRuntime: Send {
479 fn shutdown(&mut self);
480}
481
482pub type CoreResourceThread = GenericSender<CoreResourceMsg>;
484
485#[derive(Clone, Debug, Deserialize, Serialize)]
491pub struct ResourceThreads {
492 pub core_thread: CoreResourceThread,
493}
494
495impl ResourceThreads {
496 pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
497 ResourceThreads { core_thread }
498 }
499
500 pub fn cache_entries(&self) -> Vec<CacheEntryDescriptor> {
501 let (sender, receiver) = generic_channel::channel().unwrap();
502 let _ = self
503 .core_thread
504 .send(CoreResourceMsg::GetCacheEntries(sender));
505 receiver.recv().unwrap()
506 }
507
508 pub fn clear_cache(&self) {
509 let (sender, receiver) = generic_channel::channel().unwrap();
516 let _ = self
517 .core_thread
518 .send(CoreResourceMsg::ClearCache(Some(sender)));
519 let _ = receiver.recv();
520 }
521
522 pub fn cookies(&self) -> Vec<SiteDescriptor> {
523 let (sender, receiver) = generic_channel::channel().unwrap();
524 let _ = self.core_thread.send(CoreResourceMsg::ListCookies(sender));
525 receiver.recv().unwrap()
526 }
527
528 pub fn clear_cookies_for_sites(&self, sites: &[&str]) {
529 let sites = sites.iter().map(|site| site.to_string()).collect();
530 let (sender, receiver) = generic_channel::channel().unwrap();
531 let _ = self
532 .core_thread
533 .send(CoreResourceMsg::DeleteCookiesForSites(sites, sender));
534 let _ = receiver.recv();
535 }
536
537 pub fn clear_cookies(&self) {
538 let (sender, receiver) = ipc::channel().unwrap();
539 let _ = self
540 .core_thread
541 .send(CoreResourceMsg::DeleteCookies(None, Some(sender)));
542 let _ = receiver.recv();
543 }
544
545 pub fn cookies_for_url(&self, url: ServoUrl, source: CookieSource) -> Vec<Cookie<'static>> {
546 let (sender, receiver) = generic_channel::channel().unwrap();
547 let _ = self
548 .core_thread
549 .send(CoreResourceMsg::GetCookiesForUrl(url, sender, source));
550 receiver
551 .recv()
552 .unwrap()
553 .into_iter()
554 .map(|cookie| cookie.into_inner())
555 .collect()
556 }
557
558 pub fn set_cookie_for_url(&self, url: ServoUrl, cookie: Cookie<'static>, source: CookieSource) {
559 let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
560 url,
561 Serde(cookie),
562 source,
563 None,
564 ));
565 }
566
567 pub fn set_cookie_for_url_sync(
568 &self,
569 url: ServoUrl,
570 cookie: Cookie<'static>,
571 source: CookieSource,
572 ) {
573 let (sender, receiver) = generic_channel::channel().unwrap();
574 let _ = self.core_thread.send(CoreResourceMsg::SetCookieForUrl(
575 url,
576 Serde(cookie),
577 source,
578 Some(sender),
579 ));
580 let _ = receiver.recv();
581 }
582}
583
584impl GenericSend<CoreResourceMsg> for ResourceThreads {
585 fn send(&self, msg: CoreResourceMsg) -> SendResult {
586 self.core_thread.send(msg)
587 }
588
589 fn sender(&self) -> GenericSender<CoreResourceMsg> {
590 self.core_thread.clone()
591 }
592}
593
594malloc_size_of_is_0!(ResourceThreads);
596
597#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
598pub enum IncludeSubdomains {
599 Included,
600 NotIncluded,
601}
602
603#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
604pub enum MessageData {
605 Text(String),
606 Binary(Vec<u8>),
607}
608
609#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
610pub enum WebSocketDomAction {
611 SendMessage(MessageData),
612 Close(Option<u16>, Option<String>),
613}
614
615#[derive(Debug, Deserialize, Serialize)]
616pub enum WebSocketNetworkEvent {
617 ReportCSPViolations(Vec<csp::Violation>),
618 ConnectionEstablished { protocol_in_use: Option<String> },
619 MessageReceived(MessageData),
620 Close(Option<u16>, String),
621 Fail,
622}
623
624#[derive(Debug, Deserialize, Serialize)]
625pub enum FetchChannels {
627 ResponseMsg(IpcSender<FetchResponseMsg>),
628 WebSocket {
629 event_sender: IpcSender<WebSocketNetworkEvent>,
630 action_receiver: CallbackSetter<WebSocketDomAction>,
631 },
632 Prefetch,
635}
636
637#[derive(Debug, Deserialize, Serialize)]
638pub enum CoreResourceMsg {
639 Fetch(RequestBuilder, FetchChannels),
640 Cancel(Vec<RequestId>),
641 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
643 SetCookieForUrl(
646 ServoUrl,
647 Serde<Cookie<'static>>,
648 CookieSource,
649 Option<GenericSender<()>>,
650 ),
651 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
653 SetCookieForUrlAsync(
654 CookieStoreId,
655 ServoUrl,
656 Serde<Cookie<'static>>,
657 CookieSource,
658 ),
659 GetCookieStringForUrl(ServoUrl, GenericSender<Option<String>>, CookieSource),
661 GetCookiesForUrl(
663 ServoUrl,
664 GenericSender<Vec<Serde<Cookie<'static>>>>,
665 CookieSource,
666 ),
667 GetCookiesDataForUrl(
669 ServoUrl,
670 GenericSender<Vec<Serde<Cookie<'static>>>>,
671 CookieSource,
672 ),
673 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
674 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
675 DeleteCookiesForSites(Vec<String>, GenericSender<()>),
676 DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
679 DeleteCookie(ServoUrl, String),
680 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
681 NewCookieListener(
682 CookieStoreId,
683 GenericCallback<CookieAsyncResponse>,
684 ServoUrl,
685 ),
686 RemoveCookieListener(CookieStoreId),
687 ListCookies(GenericSender<Vec<SiteDescriptor>>),
688 GetHistoryState(HistoryStateId, GenericSender<Option<Vec<u8>>>),
690 SetHistoryState(HistoryStateId, Vec<u8>),
692 RemoveHistoryStates(Vec<HistoryStateId>),
694 GetCacheEntries(GenericSender<Vec<CacheEntryDescriptor>>),
696 ClearCache(Option<GenericSender<()>>),
698 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
700 ToFileManager(FileManagerThreadMsg),
702 StorePreloadedResponse(PreloadId, Response),
703 TotalSizeOfInFlightKeepAliveRecords(PipelineId, GenericSender<u64>),
704 Exit(GenericOneshotSender<()>),
707 CollectMemoryReport(ReportsChan),
708}
709
710#[derive(Clone, Debug, Deserialize, Serialize)]
711pub struct SiteDescriptor {
712 pub name: String,
713}
714
715impl SiteDescriptor {
716 pub fn new(name: String) -> Self {
717 SiteDescriptor { name }
718 }
719}
720
721#[derive(Clone, Debug, Deserialize, Serialize)]
722pub struct CacheEntryDescriptor {
723 pub key: String,
724}
725
726impl CacheEntryDescriptor {
727 pub fn new(key: String) -> Self {
728 Self { key }
729 }
730}
731
732#[expect(clippy::large_enum_variant)]
734enum ToFetchThreadMessage {
735 Cancel(Vec<RequestId>, CoreResourceThread),
736 StartFetch(
737 RequestBuilder,
738 Option<ResponseInit>,
739 BoxedFetchCallback,
740 CoreResourceThread,
741 ),
742 FetchResponse(FetchResponseMsg),
743 Exit,
745}
746
747pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
748
749struct FetchThread {
753 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
756 receiver: Receiver<ToFetchThreadMessage>,
760 to_fetch_sender: IpcSender<FetchResponseMsg>,
763}
764
765impl FetchThread {
766 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
767 let (sender, receiver) = unbounded();
768 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
769
770 let sender_clone = sender.clone();
771 ROUTER.add_typed_route(
772 from_fetch_sender,
773 Box::new(move |message| {
774 let message: FetchResponseMsg = message.unwrap();
775 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
776 }),
777 );
778 let join_handle = thread::Builder::new()
779 .name("FetchThread".to_owned())
780 .spawn(move || {
781 let mut fetch_thread = FetchThread {
782 active_fetches: FxHashMap::default(),
783 receiver,
784 to_fetch_sender,
785 };
786 fetch_thread.run();
787 })
788 .expect("Thread spawning failed");
789 (sender, join_handle)
790 }
791
792 fn run(&mut self) {
793 loop {
794 match self.receiver.recv().unwrap() {
795 ToFetchThreadMessage::StartFetch(
796 request_builder,
797 response_init,
798 callback,
799 core_resource_thread,
800 ) => {
801 let request_builder_id = request_builder.id;
802
803 let message = match response_init {
805 Some(response_init) => CoreResourceMsg::FetchRedirect(
806 request_builder,
807 response_init,
808 self.to_fetch_sender.clone(),
809 ),
810 None => CoreResourceMsg::Fetch(
811 request_builder,
812 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
813 ),
814 };
815
816 core_resource_thread.send(message).unwrap();
817
818 let preexisting_fetch =
819 self.active_fetches.insert(request_builder_id, callback);
820 assert!(preexisting_fetch.is_none());
824 },
825 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
826 let request_id = fetch_response_msg.request_id();
827 let fetch_finished =
828 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
829
830 self.active_fetches
831 .get_mut(&request_id)
832 .expect("Got fetch response for unknown fetch")(
833 fetch_response_msg
834 );
835
836 if fetch_finished {
837 self.active_fetches.remove(&request_id);
838 }
839 },
840 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
841 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
845 },
846 ToFetchThreadMessage::Exit => break,
847 }
848 }
849 }
850}
851
852static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
853
854pub fn start_fetch_thread() -> JoinHandle<()> {
857 let (sender, join_handle) = FetchThread::spawn();
858 FETCH_THREAD
859 .set(sender)
860 .expect("Fetch thread should be set only once on start-up");
861 join_handle
862}
863
864pub fn exit_fetch_thread() {
869 let _ = FETCH_THREAD
870 .get()
871 .expect("Fetch thread should always be initialized on start-up")
872 .send(ToFetchThreadMessage::Exit);
873}
874
875pub fn fetch_async(
877 core_resource_thread: &CoreResourceThread,
878 request: RequestBuilder,
879 response_init: Option<ResponseInit>,
880 callback: BoxedFetchCallback,
881) {
882 let _ = FETCH_THREAD
883 .get()
884 .expect("Fetch thread should always be initialized on start-up")
885 .send(ToFetchThreadMessage::StartFetch(
886 request,
887 response_init,
888 callback,
889 core_resource_thread.clone(),
890 ));
891}
892
893pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
896 let _ = FETCH_THREAD
897 .get()
898 .expect("Fetch thread should always be initialized on start-up")
899 .send(ToFetchThreadMessage::Cancel(
900 request_ids,
901 core_resource_thread.clone(),
902 ));
903}
904
905#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
906pub struct ResourceCorsData {
907 pub preflight: bool,
909 pub origin: ServoUrl,
911}
912
913#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
914pub struct ResourceFetchTiming {
915 pub domain_lookup_start: Option<CrossProcessInstant>,
916 pub timing_check_passed: bool,
917 pub timing_type: ResourceTimingType,
918 pub redirect_count: u16,
920 pub request_start: Option<CrossProcessInstant>,
921 pub secure_connection_start: Option<CrossProcessInstant>,
922 pub response_start: Option<CrossProcessInstant>,
923 pub fetch_start: Option<CrossProcessInstant>,
924 pub response_end: Option<CrossProcessInstant>,
925 pub redirect_start: Option<CrossProcessInstant>,
926 pub redirect_end: Option<CrossProcessInstant>,
927 pub connect_start: Option<CrossProcessInstant>,
928 pub connect_end: Option<CrossProcessInstant>,
929 pub start_time: Option<CrossProcessInstant>,
930 pub preloaded: bool,
931}
932
933pub enum RedirectStartValue {
934 Zero,
935 FetchStart,
936}
937
938pub enum RedirectEndValue {
939 Zero,
940 ResponseEnd,
941}
942
943pub enum ResourceTimeValue {
946 Zero,
947 Now,
948 FetchStart,
949 RedirectStart,
950}
951
952pub enum ResourceAttribute {
953 RedirectCount(u16),
954 DomainLookupStart,
955 RequestStart,
956 ResponseStart,
957 RedirectStart(RedirectStartValue),
958 RedirectEnd(RedirectEndValue),
959 FetchStart,
960 ConnectStart(CrossProcessInstant),
961 ConnectEnd(CrossProcessInstant),
962 SecureConnectionStart,
963 ResponseEnd,
964 StartTime(ResourceTimeValue),
965}
966
967#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
968pub enum ResourceTimingType {
969 Resource,
970 Navigation,
971 Error,
972 None,
973}
974
975impl ResourceFetchTiming {
976 pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
977 ResourceFetchTiming {
978 timing_type,
979 timing_check_passed: true,
980 domain_lookup_start: None,
981 redirect_count: 0,
982 secure_connection_start: None,
983 request_start: None,
984 response_start: None,
985 fetch_start: None,
986 redirect_start: None,
987 redirect_end: None,
988 connect_start: None,
989 connect_end: None,
990 response_end: None,
991 start_time: None,
992 preloaded: false,
993 }
994 }
995
996 pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
999 let should_attribute_always_be_updated = matches!(
1000 attribute,
1001 ResourceAttribute::FetchStart |
1002 ResourceAttribute::ResponseEnd |
1003 ResourceAttribute::StartTime(_)
1004 );
1005 if !self.timing_check_passed && !should_attribute_always_be_updated {
1006 return;
1007 }
1008 let now = Some(CrossProcessInstant::now());
1009 match attribute {
1010 ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
1011 ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
1012 ResourceAttribute::RequestStart => self.request_start = now,
1013 ResourceAttribute::ResponseStart => self.response_start = now,
1014 ResourceAttribute::RedirectStart(val) => match val {
1015 RedirectStartValue::Zero => self.redirect_start = None,
1016 RedirectStartValue::FetchStart => {
1017 if self.redirect_start.is_none() {
1018 self.redirect_start = self.fetch_start
1019 }
1020 },
1021 },
1022 ResourceAttribute::RedirectEnd(val) => match val {
1023 RedirectEndValue::Zero => self.redirect_end = None,
1024 RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
1025 },
1026 ResourceAttribute::FetchStart => self.fetch_start = now,
1027 ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
1028 ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
1029 ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
1030 ResourceAttribute::ResponseEnd => self.response_end = now,
1031 ResourceAttribute::StartTime(val) => match val {
1032 ResourceTimeValue::RedirectStart
1033 if self.redirect_start.is_none() || !self.timing_check_passed => {},
1034 _ => self.start_time = self.get_time_value(val),
1035 },
1036 }
1037 }
1038
1039 fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
1040 match time {
1041 ResourceTimeValue::Zero => None,
1042 ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
1043 ResourceTimeValue::FetchStart => self.fetch_start,
1044 ResourceTimeValue::RedirectStart => self.redirect_start,
1045 }
1046 }
1047
1048 pub fn mark_timing_check_failed(&mut self) {
1049 self.timing_check_passed = false;
1050 self.domain_lookup_start = None;
1051 self.redirect_count = 0;
1052 self.request_start = None;
1053 self.response_start = None;
1054 self.redirect_start = None;
1055 self.connect_start = None;
1056 self.connect_end = None;
1057 }
1058}
1059
1060#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
1062pub struct Metadata {
1063 pub final_url: ServoUrl,
1065
1066 pub location_url: Option<Result<ServoUrl, String>>,
1068
1069 #[ignore_malloc_size_of = "Defined in hyper"]
1070 pub content_type: Option<Serde<ContentType>>,
1072
1073 pub charset: Option<String>,
1075
1076 #[ignore_malloc_size_of = "Defined in hyper"]
1077 pub headers: Option<Serde<HeaderMap>>,
1079
1080 pub status: HttpStatus,
1082
1083 pub https_state: HttpsState,
1085
1086 pub referrer: Option<ServoUrl>,
1088
1089 pub referrer_policy: ReferrerPolicy,
1091 pub timing: Option<ResourceFetchTiming>,
1093 pub redirected: bool,
1095 pub tls_security_info: Option<TlsSecurityInfo>,
1097}
1098
1099impl Metadata {
1100 pub fn default(url: ServoUrl) -> Self {
1102 Metadata {
1103 final_url: url,
1104 location_url: None,
1105 content_type: None,
1106 charset: None,
1107 headers: None,
1108 status: HttpStatus::default(),
1109 https_state: HttpsState::None,
1110 referrer: None,
1111 referrer_policy: ReferrerPolicy::EmptyString,
1112 timing: None,
1113 redirected: false,
1114 tls_security_info: None,
1115 }
1116 }
1117
1118 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
1120 if self.headers.is_none() {
1121 self.headers = Some(Serde(HeaderMap::new()));
1122 }
1123
1124 if let Some(mime) = content_type {
1125 self.headers
1126 .as_mut()
1127 .unwrap()
1128 .typed_insert(ContentType::from(mime.clone()));
1129 if let Some(charset) = mime.get_param(mime::CHARSET) {
1130 self.charset = Some(charset.to_string());
1131 }
1132 self.content_type = Some(Serde(ContentType::from(mime.clone())));
1133 }
1134 }
1135
1136 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
1138 if referrer_policy == ReferrerPolicy::EmptyString {
1139 return;
1140 }
1141
1142 if self.headers.is_none() {
1143 self.headers = Some(Serde(HeaderMap::new()));
1144 }
1145
1146 self.referrer_policy = referrer_policy;
1147
1148 self.headers
1149 .as_mut()
1150 .unwrap()
1151 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
1152 }
1153
1154 pub fn resource_content_type_metadata(&self, load_context: LoadContext, data: &[u8]) -> Mime {
1156 let no_sniff = self
1158 .headers
1159 .as_deref()
1160 .is_some_and(determine_nosniff)
1161 .into();
1162 let mime = self
1163 .content_type
1164 .clone()
1165 .map(|content_type| content_type.into_inner().into());
1166 MimeClassifier::default().classify(
1167 load_context,
1168 no_sniff,
1169 ApacheBugFlag::from_content_type(mime.as_ref()),
1170 &mime,
1171 data,
1172 )
1173 }
1174}
1175
1176#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1178pub enum CookieSource {
1179 HTTP,
1181 NonHTTP,
1183}
1184
1185#[derive(Clone, Debug, Deserialize, Serialize)]
1186pub struct CookieChange {
1187 changed: Vec<Serde<Cookie<'static>>>,
1188 deleted: Vec<Serde<Cookie<'static>>>,
1189}
1190
1191#[derive(Clone, Debug, Deserialize, Serialize)]
1192pub enum CookieData {
1193 Change(CookieChange),
1194 Get(Option<Serde<Cookie<'static>>>),
1195 GetAll(Vec<Serde<Cookie<'static>>>),
1196 Set(Result<(), ()>),
1197 Delete(Result<(), ()>),
1198}
1199
1200#[derive(Clone, Debug, Deserialize, Serialize)]
1201pub struct CookieAsyncResponse {
1202 pub data: CookieData,
1203}
1204
1205#[derive(Clone, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1207pub enum NetworkError {
1208 LoadCancelled,
1209 SslValidation(String, Vec<u8>),
1211 Crash(String),
1213 UnsupportedScheme,
1214 CorsGeneral,
1215 CrossOriginResponse,
1216 CorsCredentials,
1217 CorsAllowMethods,
1218 CorsAllowHeaders,
1219 CorsMethod,
1220 CorsAuthorization,
1221 CorsHeaders,
1222 ConnectionFailure,
1223 RedirectError,
1224 TooManyRedirects,
1225 TooManyInFlightKeepAliveRequests,
1226 InvalidMethod,
1227 ResourceLoadError(String),
1228 ContentSecurityPolicy,
1229 Nosniff,
1230 MimeType(String),
1231 SubresourceIntegrity,
1232 MixedContent,
1233 CacheError,
1234 InvalidPort,
1235 WebsocketConnectionFailure(String),
1236 LocalDirectoryError,
1237 PartialResponseToNonRangeRequestError,
1238 ProtocolHandlerSubstitutionError,
1239 BlobURLStoreError(String),
1240 HttpError(String),
1241 DecompressionError,
1242}
1243
1244impl fmt::Debug for NetworkError {
1245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1246 match self {
1247 NetworkError::UnsupportedScheme => write!(f, "Unsupported scheme"),
1248 NetworkError::CorsGeneral => write!(f, "CORS check failed"),
1249 NetworkError::CrossOriginResponse => write!(f, "Cross-origin response"),
1250 NetworkError::CorsCredentials => write!(f, "Cross-origin credentials check failed"),
1251 NetworkError::CorsAllowMethods => write!(f, "CORS ACAM check failed"),
1252 NetworkError::CorsAllowHeaders => write!(f, "CORS ACAH check failed"),
1253 NetworkError::CorsMethod => write!(f, "CORS method check failed"),
1254 NetworkError::CorsAuthorization => write!(f, "CORS authorization check failed"),
1255 NetworkError::CorsHeaders => write!(f, "CORS headers check failed"),
1256 NetworkError::ConnectionFailure => write!(f, "Request failed"),
1257 NetworkError::RedirectError => write!(f, "Redirect failed"),
1258 NetworkError::TooManyRedirects => write!(f, "Too many redirects"),
1259 NetworkError::TooManyInFlightKeepAliveRequests => {
1260 write!(f, "Too many in flight keep-alive requests")
1261 },
1262 NetworkError::InvalidMethod => write!(f, "Unexpected method"),
1263 NetworkError::ResourceLoadError(s) => write!(f, "{}", s),
1264 NetworkError::ContentSecurityPolicy => write!(f, "Blocked by Content-Security-Policy"),
1265 NetworkError::Nosniff => write!(f, "Blocked by nosniff"),
1266 NetworkError::MimeType(s) => write!(f, "{}", s),
1267 NetworkError::SubresourceIntegrity => {
1268 write!(f, "Subresource integrity validation failed")
1269 },
1270 NetworkError::MixedContent => write!(f, "Blocked as mixed content"),
1271 NetworkError::CacheError => write!(f, "Couldn't find response in cache"),
1272 NetworkError::InvalidPort => write!(f, "Request attempted on bad port"),
1273 NetworkError::LocalDirectoryError => write!(f, "Local directory access failed"),
1274 NetworkError::LoadCancelled => write!(f, "Load cancelled"),
1275 NetworkError::SslValidation(s, _) => write!(f, "SSL validation error: {}", s),
1276 NetworkError::Crash(s) => write!(f, "Crash: {}", s),
1277 NetworkError::PartialResponseToNonRangeRequestError => write!(
1278 f,
1279 "Refusing to provide partial response from earlier ranged request to API that did not make a range request"
1280 ),
1281 NetworkError::ProtocolHandlerSubstitutionError => {
1282 write!(f, "Failed to parse substituted protocol handler url")
1283 },
1284 NetworkError::BlobURLStoreError(s) => write!(f, "Blob URL store error: {}", s),
1285 NetworkError::WebsocketConnectionFailure(s) => {
1286 write!(f, "Websocket connection failure: {}", s)
1287 },
1288 NetworkError::HttpError(s) => write!(f, "HTTP failure: {}", s),
1289 NetworkError::DecompressionError => write!(f, "Decompression error"),
1290 }
1291 }
1292}
1293
1294impl NetworkError {
1295 pub fn is_permanent_failure(&self) -> bool {
1296 matches!(
1297 self,
1298 NetworkError::ContentSecurityPolicy |
1299 NetworkError::MixedContent |
1300 NetworkError::SubresourceIntegrity |
1301 NetworkError::Nosniff |
1302 NetworkError::InvalidPort |
1303 NetworkError::CorsGeneral |
1304 NetworkError::CrossOriginResponse |
1305 NetworkError::CorsCredentials |
1306 NetworkError::CorsAllowMethods |
1307 NetworkError::CorsAllowHeaders |
1308 NetworkError::CorsMethod |
1309 NetworkError::CorsAuthorization |
1310 NetworkError::CorsHeaders |
1311 NetworkError::UnsupportedScheme
1312 )
1313 }
1314
1315 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1316 let error_string = error.to_string();
1317 match certificate {
1318 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1319 _ => NetworkError::HttpError(error_string),
1320 }
1321 }
1322}
1323
1324pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1327 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1328
1329 loop {
1330 match slice.split_first() {
1331 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1332 _ => break,
1333 }
1334 }
1335
1336 loop {
1337 match slice.split_last() {
1338 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1339 _ => break,
1340 }
1341 }
1342
1343 slice
1344}
1345
1346pub fn http_percent_encode(bytes: &[u8]) -> String {
1347 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1350 .add(b' ')
1351 .add(b'"')
1352 .add(b'%')
1353 .add(b'\'')
1354 .add(b'(')
1355 .add(b')')
1356 .add(b'*')
1357 .add(b',')
1358 .add(b'/')
1359 .add(b':')
1360 .add(b';')
1361 .add(b'<')
1362 .add(b'-')
1363 .add(b'>')
1364 .add(b'?')
1365 .add(b'[')
1366 .add(b'\\')
1367 .add(b']')
1368 .add(b'{')
1369 .add(b'}');
1370
1371 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1372}
1373
1374pub fn get_current_locale() -> &'static (String, HeaderValue) {
1376 static CURRENT_LOCALE: OnceLock<(String, HeaderValue)> = OnceLock::new();
1377
1378 CURRENT_LOCALE.get_or_init(|| {
1379 let locale_override = servo_config::pref!(intl_locale_override);
1380 let locale = if locale_override.is_empty() {
1381 sys_locale::get_locale().unwrap_or_else(|| "en-US".into())
1382 } else {
1383 locale_override
1384 };
1385 let header_value = HeaderValue::from_str(&locale)
1386 .ok()
1387 .unwrap_or_else(|| HeaderValue::from_static("en-US"));
1388 (locale, header_value)
1389 })
1390}
1391
1392pub fn set_default_accept_language(headers: &mut HeaderMap) {
1394 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1397 return;
1398 }
1399
1400 headers.insert(header::ACCEPT_LANGUAGE, get_current_locale().1.clone());
1402}
1403
1404pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());