1#![deny(unsafe_code)]
6
7use std::fmt::Display;
8use std::sync::{LazyLock, OnceLock};
9use std::thread::{self, JoinHandle};
10
11use base::cross_process_instant::CrossProcessInstant;
12use base::generic_channel::{GenericSend, GenericSender, SendResult};
13use base::id::{CookieStoreId, HistoryStateId};
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::{Error as HttpError, HeaderMap, HeaderValue, StatusCode, header};
19use hyper_serde::Serde;
20use hyper_util::client::legacy::Error as HyperError;
21use ipc_channel::Error as IpcError;
22use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
23use ipc_channel::router::ROUTER;
24use malloc_size_of::malloc_size_of_is_0;
25use malloc_size_of_derive::MallocSizeOf;
26use mime::Mime;
27use request::RequestId;
28use rustc_hash::FxHashMap;
29use rustls_pki_types::CertificateDer;
30use serde::{Deserialize, Serialize};
31use servo_rand::RngCore;
32use servo_url::{ImmutableOrigin, ServoUrl};
33
34use crate::filemanager_thread::FileManagerThreadMsg;
35use crate::http_status::HttpStatus;
36use crate::indexeddb_thread::IndexedDBThreadMsg;
37use crate::request::{Request, RequestBuilder};
38use crate::response::{HttpsState, Response, ResponseInit};
39use crate::storage_thread::StorageThreadMsg;
40
41pub mod blob_url_store;
42pub mod filemanager_thread;
43pub mod http_status;
44pub mod image_cache;
45pub mod indexeddb_thread;
46pub mod mime_classifier;
47pub mod policy_container;
48pub mod pub_domains;
49pub mod quality;
50pub mod request;
51pub mod response;
52pub mod storage_thread;
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 parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
143 headers
145 .as_ref()
146 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
148 .into()
150 }
151}
152
153impl Display for ReferrerPolicy {
154 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 let string = match self {
156 ReferrerPolicy::EmptyString => "",
157 ReferrerPolicy::NoReferrer => "no-referrer",
158 ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
159 ReferrerPolicy::Origin => "origin",
160 ReferrerPolicy::SameOrigin => "same-origin",
161 ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
162 ReferrerPolicy::UnsafeUrl => "unsafe-url",
163 ReferrerPolicy::StrictOrigin => "strict-origin",
164 ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
165 };
166 write!(formatter, "{string}")
167 }
168}
169
170impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
172 fn from(header: Option<ReferrerPolicyHeader>) -> Self {
173 header.map_or(ReferrerPolicy::EmptyString, |policy| match policy {
176 ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer,
177 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => {
178 ReferrerPolicy::NoReferrerWhenDowngrade
179 },
180 ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin,
181 ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin,
182 ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin,
183 ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl,
184 ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin,
185 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => {
186 ReferrerPolicy::StrictOriginWhenCrossOrigin
187 },
188 })
189 }
190}
191
192impl From<ReferrerPolicy> for ReferrerPolicyHeader {
193 fn from(referrer_policy: ReferrerPolicy) -> Self {
194 match referrer_policy {
195 ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER,
196 ReferrerPolicy::NoReferrerWhenDowngrade => {
197 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE
198 },
199 ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN,
200 ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN,
201 ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN,
202 ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL,
203 ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN,
204 ReferrerPolicy::EmptyString | ReferrerPolicy::StrictOriginWhenCrossOrigin => {
205 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
206 },
207 }
208 }
209}
210
211#[expect(clippy::large_enum_variant)]
213#[derive(Debug, Deserialize, Serialize)]
214pub enum FetchResponseMsg {
215 ProcessRequestBody(RequestId),
217 ProcessRequestEOF(RequestId),
218 ProcessResponse(RequestId, Result<FetchMetadata, NetworkError>),
220 ProcessResponseChunk(RequestId, Vec<u8>),
221 ProcessResponseEOF(RequestId, Result<ResourceFetchTiming, NetworkError>),
222 ProcessCspViolations(RequestId, Vec<csp::Violation>),
223}
224
225impl FetchResponseMsg {
226 pub fn request_id(&self) -> RequestId {
227 match self {
228 FetchResponseMsg::ProcessRequestBody(id) |
229 FetchResponseMsg::ProcessRequestEOF(id) |
230 FetchResponseMsg::ProcessResponse(id, ..) |
231 FetchResponseMsg::ProcessResponseChunk(id, ..) |
232 FetchResponseMsg::ProcessResponseEOF(id, ..) |
233 FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
234 }
235 }
236}
237
238pub trait FetchTaskTarget {
239 fn process_request_body(&mut self, request: &Request);
243
244 fn process_request_eof(&mut self, request: &Request);
248
249 fn process_response(&mut self, request: &Request, response: &Response);
253
254 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
256
257 fn process_response_eof(&mut self, request: &Request, response: &Response);
261
262 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
263}
264
265#[derive(Clone, Debug, Deserialize, Serialize)]
266pub enum FilteredMetadata {
267 Basic(Metadata),
268 Cors(Metadata),
269 Opaque,
270 OpaqueRedirect(ServoUrl),
271}
272
273#[expect(clippy::large_enum_variant)]
275#[derive(Clone, Debug, Deserialize, Serialize)]
276pub enum FetchMetadata {
277 Unfiltered(Metadata),
278 Filtered {
279 filtered: FilteredMetadata,
280 unsafe_: Metadata,
281 },
282}
283
284impl FetchMetadata {
285 pub fn metadata(&self) -> &Metadata {
286 match self {
287 Self::Unfiltered(metadata) => metadata,
288 Self::Filtered { unsafe_, .. } => unsafe_,
289 }
290 }
291}
292
293pub trait FetchResponseListener {
294 fn process_request_body(&mut self, request_id: RequestId);
295 fn process_request_eof(&mut self, request_id: RequestId);
296 fn process_response(
297 &mut self,
298 request_id: RequestId,
299 metadata: Result<FetchMetadata, NetworkError>,
300 );
301 fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>);
302 fn process_response_eof(
303 &mut self,
304 request_id: RequestId,
305 response: Result<ResourceFetchTiming, NetworkError>,
306 );
307 fn resource_timing(&self) -> &ResourceFetchTiming;
308 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming;
309 fn submit_resource_timing(&mut self);
310 fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec<csp::Violation>);
311}
312
313impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
314 fn process_request_body(&mut self, request: &Request) {
315 let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
316 }
317
318 fn process_request_eof(&mut self, request: &Request) {
319 let _ = self.send(FetchResponseMsg::ProcessRequestEOF(request.id));
320 }
321
322 fn process_response(&mut self, request: &Request, response: &Response) {
323 let _ = self.send(FetchResponseMsg::ProcessResponse(
324 request.id,
325 response.metadata(),
326 ));
327 }
328
329 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
330 let _ = self.send(FetchResponseMsg::ProcessResponseChunk(request.id, chunk));
331 }
332
333 fn process_response_eof(&mut self, request: &Request, response: &Response) {
334 let payload = if let Some(network_error) = response.get_network_error() {
335 Err(network_error.clone())
336 } else {
337 Ok(response.get_resource_timing().lock().unwrap().clone())
338 };
339
340 let _ = self.send(FetchResponseMsg::ProcessResponseEOF(request.id, payload));
341 }
342
343 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
344 let _ = self.send(FetchResponseMsg::ProcessCspViolations(
345 request.id, violations,
346 ));
347 }
348}
349
350pub struct DiscardFetch;
354
355impl FetchTaskTarget for DiscardFetch {
356 fn process_request_body(&mut self, _: &Request) {}
357 fn process_request_eof(&mut self, _: &Request) {}
358 fn process_response(&mut self, _: &Request, _: &Response) {}
359 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
360 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
361 fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
362}
363
364pub trait Action<Listener> {
365 fn process(self, listener: &mut Listener);
366}
367
368impl<T: FetchResponseListener> Action<T> for FetchResponseMsg {
369 fn process(self, listener: &mut T) {
371 match self {
372 FetchResponseMsg::ProcessRequestBody(request_id) => {
373 listener.process_request_body(request_id)
374 },
375 FetchResponseMsg::ProcessRequestEOF(request_id) => {
376 listener.process_request_eof(request_id)
377 },
378 FetchResponseMsg::ProcessResponse(request_id, meta) => {
379 listener.process_response(request_id, meta)
380 },
381 FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
382 listener.process_response_chunk(request_id, data)
383 },
384 FetchResponseMsg::ProcessResponseEOF(request_id, data) => {
385 match data {
386 Ok(ref response_resource_timing) => {
387 *listener.resource_timing_mut() = response_resource_timing.clone();
389 listener
390 .process_response_eof(request_id, Ok(response_resource_timing.clone()));
391 listener.submit_resource_timing();
394 },
395 Err(e) => listener.process_response_eof(request_id, Err(e)),
400 }
401 },
402 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
403 listener.process_csp_violations(request_id, violations)
404 },
405 }
406 }
407}
408
409pub trait AsyncRuntime: Send {
412 fn shutdown(&mut self);
413}
414
415pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
417
418pub type IpcSendResult = Result<(), IpcError>;
419
420pub trait IpcSend<T>
424where
425 T: serde::Serialize + for<'de> serde::Deserialize<'de>,
426{
427 fn send(&self, _: T) -> IpcSendResult;
429 fn sender(&self) -> IpcSender<T>;
431}
432
433#[derive(Clone, Debug, Deserialize, Serialize)]
439pub struct ResourceThreads {
440 pub core_thread: CoreResourceThread,
441 storage_thread: GenericSender<StorageThreadMsg>,
442 idb_thread: IpcSender<IndexedDBThreadMsg>,
443}
444
445impl ResourceThreads {
446 pub fn new(
447 c: CoreResourceThread,
448 s: GenericSender<StorageThreadMsg>,
449 i: IpcSender<IndexedDBThreadMsg>,
450 ) -> ResourceThreads {
451 ResourceThreads {
452 core_thread: c,
453 storage_thread: s,
454 idb_thread: i,
455 }
456 }
457
458 pub fn clear_cache(&self) {
459 let _ = self.core_thread.send(CoreResourceMsg::ClearCache);
460 }
461}
462
463impl IpcSend<CoreResourceMsg> for ResourceThreads {
464 fn send(&self, msg: CoreResourceMsg) -> IpcSendResult {
465 self.core_thread.send(msg)
466 }
467
468 fn sender(&self) -> IpcSender<CoreResourceMsg> {
469 self.core_thread.clone()
470 }
471}
472
473impl IpcSend<IndexedDBThreadMsg> for ResourceThreads {
474 fn send(&self, msg: IndexedDBThreadMsg) -> IpcSendResult {
475 self.idb_thread.send(msg)
476 }
477
478 fn sender(&self) -> IpcSender<IndexedDBThreadMsg> {
479 self.idb_thread.clone()
480 }
481}
482
483impl GenericSend<StorageThreadMsg> for ResourceThreads {
484 fn send(&self, msg: StorageThreadMsg) -> SendResult {
485 self.storage_thread.send(msg)
486 }
487
488 fn sender(&self) -> GenericSender<StorageThreadMsg> {
489 self.storage_thread.clone()
490 }
491}
492
493malloc_size_of_is_0!(ResourceThreads);
495
496#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
497pub enum IncludeSubdomains {
498 Included,
499 NotIncluded,
500}
501
502#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
503pub enum MessageData {
504 Text(String),
505 Binary(Vec<u8>),
506}
507
508#[derive(Debug, Deserialize, Serialize)]
509pub enum WebSocketDomAction {
510 SendMessage(MessageData),
511 Close(Option<u16>, Option<String>),
512}
513
514#[derive(Debug, Deserialize, Serialize)]
515pub enum WebSocketNetworkEvent {
516 ReportCSPViolations(Vec<csp::Violation>),
517 ConnectionEstablished { protocol_in_use: Option<String> },
518 MessageReceived(MessageData),
519 Close(Option<u16>, String),
520 Fail,
521}
522
523#[derive(Debug, Deserialize, Serialize)]
524pub enum FetchChannels {
526 ResponseMsg(IpcSender<FetchResponseMsg>),
527 WebSocket {
528 event_sender: IpcSender<WebSocketNetworkEvent>,
529 action_receiver: IpcReceiver<WebSocketDomAction>,
530 },
531 Prefetch,
534}
535
536#[derive(Debug, Deserialize, Serialize)]
537pub enum CoreResourceMsg {
538 Fetch(RequestBuilder, FetchChannels),
539 Cancel(Vec<RequestId>),
540 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
542 SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
544 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
546 SetCookieForUrlAsync(
547 CookieStoreId,
548 ServoUrl,
549 Serde<Cookie<'static>>,
550 CookieSource,
551 ),
552 GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
554 GetCookiesDataForUrl(
556 ServoUrl,
557 IpcSender<Vec<Serde<Cookie<'static>>>>,
558 CookieSource,
559 ),
560 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
561 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
562 DeleteCookies(ServoUrl),
563 DeleteCookie(ServoUrl, String),
564 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
565 NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
566 RemoveCookieListener(CookieStoreId),
567 GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
569 SetHistoryState(HistoryStateId, Vec<u8>),
571 RemoveHistoryStates(Vec<HistoryStateId>),
573 ClearCache,
575 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
577 ToFileManager(FileManagerThreadMsg),
579 Exit(IpcSender<()>),
582}
583
584#[expect(clippy::large_enum_variant)]
586enum ToFetchThreadMessage {
587 Cancel(Vec<RequestId>, CoreResourceThread),
588 StartFetch(
589 RequestBuilder,
590 Option<ResponseInit>,
591 BoxedFetchCallback,
592 CoreResourceThread,
593 ),
594 FetchResponse(FetchResponseMsg),
595 Exit,
597}
598
599pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
600
601struct FetchThread {
605 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
608 receiver: Receiver<ToFetchThreadMessage>,
612 to_fetch_sender: IpcSender<FetchResponseMsg>,
615}
616
617impl FetchThread {
618 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
619 let (sender, receiver) = unbounded();
620 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
621
622 let sender_clone = sender.clone();
623 ROUTER.add_typed_route(
624 from_fetch_sender,
625 Box::new(move |message| {
626 let message: FetchResponseMsg = message.unwrap();
627 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
628 }),
629 );
630 let join_handle = thread::Builder::new()
631 .name("FetchThread".to_owned())
632 .spawn(move || {
633 let mut fetch_thread = FetchThread {
634 active_fetches: FxHashMap::default(),
635 receiver,
636 to_fetch_sender,
637 };
638 fetch_thread.run();
639 })
640 .expect("Thread spawning failed");
641 (sender, join_handle)
642 }
643
644 fn run(&mut self) {
645 loop {
646 match self.receiver.recv().unwrap() {
647 ToFetchThreadMessage::StartFetch(
648 request_builder,
649 response_init,
650 callback,
651 core_resource_thread,
652 ) => {
653 let request_builder_id = request_builder.id;
654
655 let message = match response_init {
657 Some(response_init) => CoreResourceMsg::FetchRedirect(
658 request_builder,
659 response_init,
660 self.to_fetch_sender.clone(),
661 ),
662 None => CoreResourceMsg::Fetch(
663 request_builder,
664 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
665 ),
666 };
667
668 core_resource_thread.send(message).unwrap();
669
670 self.active_fetches.insert(request_builder_id, callback);
671 },
672 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
673 let request_id = fetch_response_msg.request_id();
674 let fetch_finished =
675 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
676
677 self.active_fetches
678 .get_mut(&request_id)
679 .expect("Got fetch response for unknown fetch")(
680 fetch_response_msg
681 );
682
683 if fetch_finished {
684 self.active_fetches.remove(&request_id);
685 }
686 },
687 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
688 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
692 },
693 ToFetchThreadMessage::Exit => break,
694 }
695 }
696 }
697}
698
699static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
700
701pub fn start_fetch_thread() -> JoinHandle<()> {
704 let (sender, join_handle) = FetchThread::spawn();
705 FETCH_THREAD
706 .set(sender)
707 .expect("Fetch thread should be set only once on start-up");
708 join_handle
709}
710
711pub fn exit_fetch_thread() {
716 let _ = FETCH_THREAD
717 .get()
718 .expect("Fetch thread should always be initialized on start-up")
719 .send(ToFetchThreadMessage::Exit);
720}
721
722pub fn fetch_async(
724 core_resource_thread: &CoreResourceThread,
725 request: RequestBuilder,
726 response_init: Option<ResponseInit>,
727 callback: BoxedFetchCallback,
728) {
729 let _ = FETCH_THREAD
730 .get()
731 .expect("Fetch thread should always be initialized on start-up")
732 .send(ToFetchThreadMessage::StartFetch(
733 request,
734 response_init,
735 callback,
736 core_resource_thread.clone(),
737 ));
738}
739
740pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
743 let _ = FETCH_THREAD
744 .get()
745 .expect("Fetch thread should always be initialized on start-up")
746 .send(ToFetchThreadMessage::Cancel(
747 request_ids,
748 core_resource_thread.clone(),
749 ));
750}
751
752#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
753pub struct ResourceCorsData {
754 pub preflight: bool,
756 pub origin: ServoUrl,
758}
759
760#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
761pub struct ResourceFetchTiming {
762 pub domain_lookup_start: Option<CrossProcessInstant>,
763 pub timing_check_passed: bool,
764 pub timing_type: ResourceTimingType,
765 pub redirect_count: u16,
767 pub request_start: Option<CrossProcessInstant>,
768 pub secure_connection_start: Option<CrossProcessInstant>,
769 pub response_start: Option<CrossProcessInstant>,
770 pub fetch_start: Option<CrossProcessInstant>,
771 pub response_end: Option<CrossProcessInstant>,
772 pub redirect_start: Option<CrossProcessInstant>,
773 pub redirect_end: Option<CrossProcessInstant>,
774 pub connect_start: Option<CrossProcessInstant>,
775 pub connect_end: Option<CrossProcessInstant>,
776 pub start_time: Option<CrossProcessInstant>,
777}
778
779pub enum RedirectStartValue {
780 #[allow(dead_code)]
781 Zero,
782 FetchStart,
783}
784
785pub enum RedirectEndValue {
786 Zero,
787 ResponseEnd,
788}
789
790pub enum ResourceTimeValue {
793 Zero,
794 Now,
795 FetchStart,
796 RedirectStart,
797}
798
799pub enum ResourceAttribute {
800 RedirectCount(u16),
801 DomainLookupStart,
802 RequestStart,
803 ResponseStart,
804 RedirectStart(RedirectStartValue),
805 RedirectEnd(RedirectEndValue),
806 FetchStart,
807 ConnectStart(CrossProcessInstant),
808 ConnectEnd(CrossProcessInstant),
809 SecureConnectionStart,
810 ResponseEnd,
811 StartTime(ResourceTimeValue),
812}
813
814#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
815pub enum ResourceTimingType {
816 Resource,
817 Navigation,
818 Error,
819 None,
820}
821
822impl ResourceFetchTiming {
823 pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
824 ResourceFetchTiming {
825 timing_type,
826 timing_check_passed: true,
827 domain_lookup_start: None,
828 redirect_count: 0,
829 secure_connection_start: None,
830 request_start: None,
831 response_start: None,
832 fetch_start: None,
833 redirect_start: None,
834 redirect_end: None,
835 connect_start: None,
836 connect_end: None,
837 response_end: None,
838 start_time: None,
839 }
840 }
841
842 pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
845 let should_attribute_always_be_updated = matches!(
846 attribute,
847 ResourceAttribute::FetchStart |
848 ResourceAttribute::ResponseEnd |
849 ResourceAttribute::StartTime(_)
850 );
851 if !self.timing_check_passed && !should_attribute_always_be_updated {
852 return;
853 }
854 let now = Some(CrossProcessInstant::now());
855 match attribute {
856 ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
857 ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
858 ResourceAttribute::RequestStart => self.request_start = now,
859 ResourceAttribute::ResponseStart => self.response_start = now,
860 ResourceAttribute::RedirectStart(val) => match val {
861 RedirectStartValue::Zero => self.redirect_start = None,
862 RedirectStartValue::FetchStart => {
863 if self.redirect_start.is_none() {
864 self.redirect_start = self.fetch_start
865 }
866 },
867 },
868 ResourceAttribute::RedirectEnd(val) => match val {
869 RedirectEndValue::Zero => self.redirect_end = None,
870 RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
871 },
872 ResourceAttribute::FetchStart => self.fetch_start = now,
873 ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
874 ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
875 ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
876 ResourceAttribute::ResponseEnd => self.response_end = now,
877 ResourceAttribute::StartTime(val) => match val {
878 ResourceTimeValue::RedirectStart
879 if self.redirect_start.is_none() || !self.timing_check_passed => {},
880 _ => self.start_time = self.get_time_value(val),
881 },
882 }
883 }
884
885 fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
886 match time {
887 ResourceTimeValue::Zero => None,
888 ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
889 ResourceTimeValue::FetchStart => self.fetch_start,
890 ResourceTimeValue::RedirectStart => self.redirect_start,
891 }
892 }
893
894 pub fn mark_timing_check_failed(&mut self) {
895 self.timing_check_passed = false;
896 self.domain_lookup_start = None;
897 self.redirect_count = 0;
898 self.request_start = None;
899 self.response_start = None;
900 self.redirect_start = None;
901 self.connect_start = None;
902 self.connect_end = None;
903 }
904}
905
906#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
908pub struct Metadata {
909 pub final_url: ServoUrl,
911
912 pub location_url: Option<Result<ServoUrl, String>>,
914
915 #[ignore_malloc_size_of = "Defined in hyper"]
916 pub content_type: Option<Serde<ContentType>>,
918
919 pub charset: Option<String>,
921
922 #[ignore_malloc_size_of = "Defined in hyper"]
923 pub headers: Option<Serde<HeaderMap>>,
925
926 pub status: HttpStatus,
928
929 pub https_state: HttpsState,
931
932 pub referrer: Option<ServoUrl>,
934
935 pub referrer_policy: ReferrerPolicy,
937 pub timing: Option<ResourceFetchTiming>,
939 pub redirected: bool,
941}
942
943impl Metadata {
944 pub fn default(url: ServoUrl) -> Self {
946 Metadata {
947 final_url: url,
948 location_url: None,
949 content_type: None,
950 charset: None,
951 headers: None,
952 status: HttpStatus::default(),
953 https_state: HttpsState::None,
954 referrer: None,
955 referrer_policy: ReferrerPolicy::EmptyString,
956 timing: None,
957 redirected: false,
958 }
959 }
960
961 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
963 if self.headers.is_none() {
964 self.headers = Some(Serde(HeaderMap::new()));
965 }
966
967 if let Some(mime) = content_type {
968 self.headers
969 .as_mut()
970 .unwrap()
971 .typed_insert(ContentType::from(mime.clone()));
972 if let Some(charset) = mime.get_param(mime::CHARSET) {
973 self.charset = Some(charset.to_string());
974 }
975 self.content_type = Some(Serde(ContentType::from(mime.clone())));
976 }
977 }
978
979 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
981 if referrer_policy == ReferrerPolicy::EmptyString {
982 return;
983 }
984
985 if self.headers.is_none() {
986 self.headers = Some(Serde(HeaderMap::new()));
987 }
988
989 self.referrer_policy = referrer_policy;
990
991 self.headers
992 .as_mut()
993 .unwrap()
994 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
995 }
996}
997
998#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
1000pub enum CookieSource {
1001 HTTP,
1003 NonHTTP,
1005}
1006
1007#[derive(Clone, Debug, Deserialize, Serialize)]
1008pub struct CookieChange {
1009 changed: Vec<Serde<Cookie<'static>>>,
1010 deleted: Vec<Serde<Cookie<'static>>>,
1011}
1012
1013#[derive(Clone, Debug, Deserialize, Serialize)]
1014pub enum CookieData {
1015 Change(CookieChange),
1016 Get(Option<Serde<Cookie<'static>>>),
1017 GetAll(Vec<Serde<Cookie<'static>>>),
1018 Set(Result<(), ()>),
1019 Delete(Result<(), ()>),
1020}
1021
1022#[derive(Clone, Debug, Deserialize, Serialize)]
1023pub struct CookieAsyncResponse {
1024 pub data: CookieData,
1025}
1026
1027#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1029pub enum NetworkError {
1030 Internal(String),
1032 LoadCancelled,
1033 SslValidation(String, Vec<u8>),
1035 Crash(String),
1037}
1038
1039impl NetworkError {
1040 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1041 let error_string = error.to_string();
1042 match certificate {
1043 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1044 _ => NetworkError::Internal(error_string),
1045 }
1046 }
1047
1048 pub fn from_http_error(error: &HttpError) -> Self {
1049 NetworkError::Internal(error.to_string())
1050 }
1051}
1052
1053pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1056 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1057
1058 loop {
1059 match slice.split_first() {
1060 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1061 _ => break,
1062 }
1063 }
1064
1065 loop {
1066 match slice.split_last() {
1067 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1068 _ => break,
1069 }
1070 }
1071
1072 slice
1073}
1074
1075pub fn http_percent_encode(bytes: &[u8]) -> String {
1076 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1079 .add(b' ')
1080 .add(b'"')
1081 .add(b'%')
1082 .add(b'\'')
1083 .add(b'(')
1084 .add(b')')
1085 .add(b'*')
1086 .add(b',')
1087 .add(b'/')
1088 .add(b':')
1089 .add(b';')
1090 .add(b'<')
1091 .add(b'-')
1092 .add(b'>')
1093 .add(b'?')
1094 .add(b'[')
1095 .add(b'\\')
1096 .add(b']')
1097 .add(b'{')
1098 .add(b'}');
1099
1100 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1101}
1102
1103pub fn set_default_accept_language(headers: &mut HeaderMap) {
1104 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1105 return;
1106 }
1107
1108 headers.insert(
1110 header::ACCEPT_LANGUAGE,
1111 HeaderValue::from_static("en-US,en;q=0.5"),
1112 );
1113}
1114
1115pub static PRIVILEGED_SECRET: LazyLock<u32> =
1116 LazyLock::new(|| servo_rand::ServoRng::default().next_u32());