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