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