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