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::id::{CookieStoreId, HistoryStateId};
13use base::{IpcSend, IpcSendResult};
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::ipc::{self, IpcError, 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 rand::{RngCore, rng};
27use request::RequestId;
28use rustc_hash::FxHashMap;
29use rustls_pki_types::CertificateDer;
30use serde::{Deserialize, Serialize};
31use servo_url::{ImmutableOrigin, ServoUrl};
32
33use crate::filemanager_thread::FileManagerThreadMsg;
34use crate::http_status::HttpStatus;
35use crate::request::{Request, RequestBuilder};
36use crate::response::{HttpsState, Response, ResponseInit};
37
38pub mod blob_url_store;
39pub mod filemanager_thread;
40pub mod http_status;
41pub mod image_cache;
42pub mod mime_classifier;
43pub mod policy_container;
44pub mod pub_domains;
45pub mod quality;
46pub mod request;
47pub mod response;
48
49pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
51 HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
52
53pub mod fetch {
55 pub mod headers;
56}
57
58#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
61pub enum LoadContext {
62 Browsing,
63 Image,
64 AudioVideo,
65 Plugin,
66 Style,
67 Script,
68 Font,
69 TextTrack,
70 CacheManifest,
71}
72
73#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
74pub struct CustomResponse {
75 #[ignore_malloc_size_of = "Defined in hyper"]
76 #[serde(
77 deserialize_with = "::hyper_serde::deserialize",
78 serialize_with = "::hyper_serde::serialize"
79 )]
80 pub headers: HeaderMap,
81 #[ignore_malloc_size_of = "Defined in hyper"]
82 #[serde(
83 deserialize_with = "::hyper_serde::deserialize",
84 serialize_with = "::hyper_serde::serialize"
85 )]
86 pub raw_status: (StatusCode, String),
87 pub body: Vec<u8>,
88}
89
90impl CustomResponse {
91 pub fn new(
92 headers: HeaderMap,
93 raw_status: (StatusCode, String),
94 body: Vec<u8>,
95 ) -> CustomResponse {
96 CustomResponse {
97 headers,
98 raw_status,
99 body,
100 }
101 }
102}
103
104#[derive(Clone, Debug, Deserialize, Serialize)]
105pub struct CustomResponseMediator {
106 pub response_chan: IpcSender<Option<CustomResponse>>,
107 pub load_url: ServoUrl,
108}
109
110#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
113pub enum ReferrerPolicy {
114 EmptyString,
116 NoReferrer,
118 NoReferrerWhenDowngrade,
120 Origin,
122 SameOrigin,
124 OriginWhenCrossOrigin,
126 UnsafeUrl,
128 StrictOrigin,
130 #[default]
132 StrictOriginWhenCrossOrigin,
133}
134
135impl ReferrerPolicy {
136 pub fn from_with_legacy(value: &str) -> Self {
138 match value.to_ascii_lowercase().as_str() {
141 "never" => ReferrerPolicy::NoReferrer,
142 "default" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
143 "always" => ReferrerPolicy::UnsafeUrl,
144 "origin-when-crossorigin" => ReferrerPolicy::OriginWhenCrossOrigin,
145 _ => ReferrerPolicy::from(value),
146 }
147 }
148
149 pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
151 headers
153 .as_ref()
154 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
156 .into()
158 }
159}
160
161impl From<&str> for ReferrerPolicy {
162 fn from(value: &str) -> Self {
164 match value.to_ascii_lowercase().as_str() {
165 "no-referrer" => ReferrerPolicy::NoReferrer,
166 "no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
167 "origin" => ReferrerPolicy::Origin,
168 "same-origin" => ReferrerPolicy::SameOrigin,
169 "strict-origin" => ReferrerPolicy::StrictOrigin,
170 "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
171 "origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
172 "unsafe-url" => ReferrerPolicy::UnsafeUrl,
173 _ => ReferrerPolicy::EmptyString,
174 }
175 }
176}
177
178impl Display for ReferrerPolicy {
179 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 let string = match self {
181 ReferrerPolicy::EmptyString => "",
182 ReferrerPolicy::NoReferrer => "no-referrer",
183 ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
184 ReferrerPolicy::Origin => "origin",
185 ReferrerPolicy::SameOrigin => "same-origin",
186 ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
187 ReferrerPolicy::UnsafeUrl => "unsafe-url",
188 ReferrerPolicy::StrictOrigin => "strict-origin",
189 ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
190 };
191 write!(formatter, "{string}")
192 }
193}
194
195impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
197 fn from(header: Option<ReferrerPolicyHeader>) -> Self {
198 header.map_or(ReferrerPolicy::EmptyString, |policy| match policy {
201 ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer,
202 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => {
203 ReferrerPolicy::NoReferrerWhenDowngrade
204 },
205 ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin,
206 ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin,
207 ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin,
208 ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl,
209 ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin,
210 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => {
211 ReferrerPolicy::StrictOriginWhenCrossOrigin
212 },
213 })
214 }
215}
216
217impl From<ReferrerPolicy> for ReferrerPolicyHeader {
218 fn from(referrer_policy: ReferrerPolicy) -> Self {
219 match referrer_policy {
220 ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER,
221 ReferrerPolicy::NoReferrerWhenDowngrade => {
222 ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE
223 },
224 ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN,
225 ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN,
226 ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN,
227 ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL,
228 ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN,
229 ReferrerPolicy::EmptyString | ReferrerPolicy::StrictOriginWhenCrossOrigin => {
230 ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
231 },
232 }
233 }
234}
235
236#[expect(clippy::large_enum_variant)]
238#[derive(Debug, Deserialize, Serialize)]
239pub enum FetchResponseMsg {
240 ProcessRequestBody(RequestId),
242 ProcessRequestEOF(RequestId),
243 ProcessResponse(RequestId, Result<FetchMetadata, NetworkError>),
245 ProcessResponseChunk(RequestId, DebugVec),
246 ProcessResponseEOF(RequestId, Result<ResourceFetchTiming, NetworkError>),
247 ProcessCspViolations(RequestId, Vec<csp::Violation>),
248}
249
250#[derive(Deserialize, PartialEq, Serialize)]
251pub struct DebugVec(pub Vec<u8>);
252
253impl From<Vec<u8>> for DebugVec {
254 fn from(v: Vec<u8>) -> Self {
255 Self(v)
256 }
257}
258
259impl std::ops::Deref for DebugVec {
260 type Target = Vec<u8>;
261 fn deref(&self) -> &Self::Target {
262 &self.0
263 }
264}
265
266impl std::fmt::Debug for DebugVec {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 f.write_fmt(format_args!("[...; {}]", self.0.len()))
269 }
270}
271
272impl FetchResponseMsg {
273 pub fn request_id(&self) -> RequestId {
274 match self {
275 FetchResponseMsg::ProcessRequestBody(id) |
276 FetchResponseMsg::ProcessRequestEOF(id) |
277 FetchResponseMsg::ProcessResponse(id, ..) |
278 FetchResponseMsg::ProcessResponseChunk(id, ..) |
279 FetchResponseMsg::ProcessResponseEOF(id, ..) |
280 FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
281 }
282 }
283}
284
285pub trait FetchTaskTarget {
286 fn process_request_body(&mut self, request: &Request);
290
291 fn process_request_eof(&mut self, request: &Request);
295
296 fn process_response(&mut self, request: &Request, response: &Response);
300
301 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
303
304 fn process_response_eof(&mut self, request: &Request, response: &Response);
308
309 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
310}
311
312#[derive(Clone, Debug, Deserialize, Serialize)]
313pub enum FilteredMetadata {
314 Basic(Metadata),
315 Cors(Metadata),
316 Opaque,
317 OpaqueRedirect(ServoUrl),
318}
319
320#[expect(clippy::large_enum_variant)]
322#[derive(Clone, Debug, Deserialize, Serialize)]
323pub enum FetchMetadata {
324 Unfiltered(Metadata),
325 Filtered {
326 filtered: FilteredMetadata,
327 unsafe_: Metadata,
328 },
329}
330
331impl FetchMetadata {
332 pub fn metadata(&self) -> &Metadata {
333 match self {
334 Self::Unfiltered(metadata) => metadata,
335 Self::Filtered { unsafe_, .. } => unsafe_,
336 }
337 }
338}
339
340impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
341 fn process_request_body(&mut self, request: &Request) {
342 let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
343 }
344
345 fn process_request_eof(&mut self, request: &Request) {
346 let _ = self.send(FetchResponseMsg::ProcessRequestEOF(request.id));
347 }
348
349 fn process_response(&mut self, request: &Request, response: &Response) {
350 let _ = self.send(FetchResponseMsg::ProcessResponse(
351 request.id,
352 response.metadata(),
353 ));
354 }
355
356 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
357 let _ = self.send(FetchResponseMsg::ProcessResponseChunk(
358 request.id,
359 chunk.into(),
360 ));
361 }
362
363 fn process_response_eof(&mut self, request: &Request, response: &Response) {
364 let payload = if let Some(network_error) = response.get_network_error() {
365 Err(network_error.clone())
366 } else {
367 Ok(response.get_resource_timing().lock().clone())
368 };
369
370 let _ = self.send(FetchResponseMsg::ProcessResponseEOF(request.id, payload));
371 }
372
373 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
374 let _ = self.send(FetchResponseMsg::ProcessCspViolations(
375 request.id, violations,
376 ));
377 }
378}
379
380impl FetchTaskTarget for IpcSender<WebSocketNetworkEvent> {
381 fn process_request_body(&mut self, _: &Request) {}
382 fn process_request_eof(&mut self, _: &Request) {}
383 fn process_response(&mut self, _: &Request, response: &Response) {
384 if response.is_network_error() {
385 let _ = self.send(WebSocketNetworkEvent::Fail);
386 }
387 }
388 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
389 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
390 fn process_csp_violations(&mut self, _: &Request, violations: Vec<csp::Violation>) {
391 let _ = self.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
392 }
393}
394
395pub struct DiscardFetch;
399
400impl FetchTaskTarget for DiscardFetch {
401 fn process_request_body(&mut self, _: &Request) {}
402 fn process_request_eof(&mut self, _: &Request) {}
403 fn process_response(&mut self, _: &Request, _: &Response) {}
404 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
405 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
406 fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
407}
408
409pub trait AsyncRuntime: Send {
412 fn shutdown(&mut self);
413}
414
415pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
417
418#[derive(Clone, Debug, Deserialize, Serialize)]
424pub struct ResourceThreads {
425 pub core_thread: CoreResourceThread,
426}
427
428impl ResourceThreads {
429 pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
430 ResourceThreads { core_thread }
431 }
432
433 pub fn clear_cache(&self) {
434 let _ = self.core_thread.send(CoreResourceMsg::ClearCache);
435 }
436
437 pub fn clear_cookies(&self) {
438 let (sender, receiver) = ipc::channel().unwrap();
439 let _ = self
440 .core_thread
441 .send(CoreResourceMsg::DeleteCookies(None, Some(sender)));
442 let _ = receiver.recv();
443 }
444}
445
446impl IpcSend<CoreResourceMsg> for ResourceThreads {
447 fn send(&self, msg: CoreResourceMsg) -> IpcSendResult {
448 self.core_thread.send(msg).map_err(IpcError::Bincode)
449 }
450
451 fn sender(&self) -> IpcSender<CoreResourceMsg> {
452 self.core_thread.clone()
453 }
454}
455
456malloc_size_of_is_0!(ResourceThreads);
458
459#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
460pub enum IncludeSubdomains {
461 Included,
462 NotIncluded,
463}
464
465#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
466pub enum MessageData {
467 Text(String),
468 Binary(Vec<u8>),
469}
470
471#[derive(Debug, Deserialize, Serialize)]
472pub enum WebSocketDomAction {
473 SendMessage(MessageData),
474 Close(Option<u16>, Option<String>),
475}
476
477#[derive(Debug, Deserialize, Serialize)]
478pub enum WebSocketNetworkEvent {
479 ReportCSPViolations(Vec<csp::Violation>),
480 ConnectionEstablished { protocol_in_use: Option<String> },
481 MessageReceived(MessageData),
482 Close(Option<u16>, String),
483 Fail,
484}
485
486#[derive(Debug, Deserialize, Serialize)]
487pub enum FetchChannels {
489 ResponseMsg(IpcSender<FetchResponseMsg>),
490 WebSocket {
491 event_sender: IpcSender<WebSocketNetworkEvent>,
492 action_receiver: IpcReceiver<WebSocketDomAction>,
493 },
494 Prefetch,
497}
498
499#[derive(Debug, Deserialize, Serialize)]
500pub enum CoreResourceMsg {
501 Fetch(RequestBuilder, FetchChannels),
502 Cancel(Vec<RequestId>),
503 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
505 SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
507 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
509 SetCookieForUrlAsync(
510 CookieStoreId,
511 ServoUrl,
512 Serde<Cookie<'static>>,
513 CookieSource,
514 ),
515 GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
517 GetCookiesDataForUrl(
519 ServoUrl,
520 IpcSender<Vec<Serde<Cookie<'static>>>>,
521 CookieSource,
522 ),
523 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
524 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
525 DeleteCookies(Option<ServoUrl>, Option<IpcSender<()>>),
526 DeleteCookie(ServoUrl, String),
527 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
528 NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
529 RemoveCookieListener(CookieStoreId),
530 GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
532 SetHistoryState(HistoryStateId, Vec<u8>),
534 RemoveHistoryStates(Vec<HistoryStateId>),
536 ClearCache,
538 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
540 ToFileManager(FileManagerThreadMsg),
542 Exit(IpcSender<()>),
545}
546
547#[expect(clippy::large_enum_variant)]
549enum ToFetchThreadMessage {
550 Cancel(Vec<RequestId>, CoreResourceThread),
551 StartFetch(
552 RequestBuilder,
553 Option<ResponseInit>,
554 BoxedFetchCallback,
555 CoreResourceThread,
556 ),
557 FetchResponse(FetchResponseMsg),
558 Exit,
560}
561
562pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
563
564struct FetchThread {
568 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
571 receiver: Receiver<ToFetchThreadMessage>,
575 to_fetch_sender: IpcSender<FetchResponseMsg>,
578}
579
580impl FetchThread {
581 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
582 let (sender, receiver) = unbounded();
583 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
584
585 let sender_clone = sender.clone();
586 ROUTER.add_typed_route(
587 from_fetch_sender,
588 Box::new(move |message| {
589 let message: FetchResponseMsg = message.unwrap();
590 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
591 }),
592 );
593 let join_handle = thread::Builder::new()
594 .name("FetchThread".to_owned())
595 .spawn(move || {
596 let mut fetch_thread = FetchThread {
597 active_fetches: FxHashMap::default(),
598 receiver,
599 to_fetch_sender,
600 };
601 fetch_thread.run();
602 })
603 .expect("Thread spawning failed");
604 (sender, join_handle)
605 }
606
607 fn run(&mut self) {
608 loop {
609 match self.receiver.recv().unwrap() {
610 ToFetchThreadMessage::StartFetch(
611 request_builder,
612 response_init,
613 callback,
614 core_resource_thread,
615 ) => {
616 let request_builder_id = request_builder.id;
617
618 let message = match response_init {
620 Some(response_init) => CoreResourceMsg::FetchRedirect(
621 request_builder,
622 response_init,
623 self.to_fetch_sender.clone(),
624 ),
625 None => CoreResourceMsg::Fetch(
626 request_builder,
627 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
628 ),
629 };
630
631 core_resource_thread.send(message).unwrap();
632
633 self.active_fetches.insert(request_builder_id, callback);
634 },
635 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
636 let request_id = fetch_response_msg.request_id();
637 let fetch_finished =
638 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
639
640 self.active_fetches
641 .get_mut(&request_id)
642 .expect("Got fetch response for unknown fetch")(
643 fetch_response_msg
644 );
645
646 if fetch_finished {
647 self.active_fetches.remove(&request_id);
648 }
649 },
650 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
651 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
655 },
656 ToFetchThreadMessage::Exit => break,
657 }
658 }
659 }
660}
661
662static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
663
664pub fn start_fetch_thread() -> JoinHandle<()> {
667 let (sender, join_handle) = FetchThread::spawn();
668 FETCH_THREAD
669 .set(sender)
670 .expect("Fetch thread should be set only once on start-up");
671 join_handle
672}
673
674pub fn exit_fetch_thread() {
679 let _ = FETCH_THREAD
680 .get()
681 .expect("Fetch thread should always be initialized on start-up")
682 .send(ToFetchThreadMessage::Exit);
683}
684
685pub fn fetch_async(
687 core_resource_thread: &CoreResourceThread,
688 request: RequestBuilder,
689 response_init: Option<ResponseInit>,
690 callback: BoxedFetchCallback,
691) {
692 let _ = FETCH_THREAD
693 .get()
694 .expect("Fetch thread should always be initialized on start-up")
695 .send(ToFetchThreadMessage::StartFetch(
696 request,
697 response_init,
698 callback,
699 core_resource_thread.clone(),
700 ));
701}
702
703pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
706 let _ = FETCH_THREAD
707 .get()
708 .expect("Fetch thread should always be initialized on start-up")
709 .send(ToFetchThreadMessage::Cancel(
710 request_ids,
711 core_resource_thread.clone(),
712 ));
713}
714
715#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
716pub struct ResourceCorsData {
717 pub preflight: bool,
719 pub origin: ServoUrl,
721}
722
723#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
724pub struct ResourceFetchTiming {
725 pub domain_lookup_start: Option<CrossProcessInstant>,
726 pub timing_check_passed: bool,
727 pub timing_type: ResourceTimingType,
728 pub redirect_count: u16,
730 pub request_start: Option<CrossProcessInstant>,
731 pub secure_connection_start: Option<CrossProcessInstant>,
732 pub response_start: Option<CrossProcessInstant>,
733 pub fetch_start: Option<CrossProcessInstant>,
734 pub response_end: Option<CrossProcessInstant>,
735 pub redirect_start: Option<CrossProcessInstant>,
736 pub redirect_end: Option<CrossProcessInstant>,
737 pub connect_start: Option<CrossProcessInstant>,
738 pub connect_end: Option<CrossProcessInstant>,
739 pub start_time: Option<CrossProcessInstant>,
740}
741
742pub enum RedirectStartValue {
743 Zero,
744 FetchStart,
745}
746
747pub enum RedirectEndValue {
748 Zero,
749 ResponseEnd,
750}
751
752pub enum ResourceTimeValue {
755 Zero,
756 Now,
757 FetchStart,
758 RedirectStart,
759}
760
761pub enum ResourceAttribute {
762 RedirectCount(u16),
763 DomainLookupStart,
764 RequestStart,
765 ResponseStart,
766 RedirectStart(RedirectStartValue),
767 RedirectEnd(RedirectEndValue),
768 FetchStart,
769 ConnectStart(CrossProcessInstant),
770 ConnectEnd(CrossProcessInstant),
771 SecureConnectionStart,
772 ResponseEnd,
773 StartTime(ResourceTimeValue),
774}
775
776#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
777pub enum ResourceTimingType {
778 Resource,
779 Navigation,
780 Error,
781 None,
782}
783
784impl ResourceFetchTiming {
785 pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
786 ResourceFetchTiming {
787 timing_type,
788 timing_check_passed: true,
789 domain_lookup_start: None,
790 redirect_count: 0,
791 secure_connection_start: None,
792 request_start: None,
793 response_start: None,
794 fetch_start: None,
795 redirect_start: None,
796 redirect_end: None,
797 connect_start: None,
798 connect_end: None,
799 response_end: None,
800 start_time: None,
801 }
802 }
803
804 pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
807 let should_attribute_always_be_updated = matches!(
808 attribute,
809 ResourceAttribute::FetchStart |
810 ResourceAttribute::ResponseEnd |
811 ResourceAttribute::StartTime(_)
812 );
813 if !self.timing_check_passed && !should_attribute_always_be_updated {
814 return;
815 }
816 let now = Some(CrossProcessInstant::now());
817 match attribute {
818 ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
819 ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
820 ResourceAttribute::RequestStart => self.request_start = now,
821 ResourceAttribute::ResponseStart => self.response_start = now,
822 ResourceAttribute::RedirectStart(val) => match val {
823 RedirectStartValue::Zero => self.redirect_start = None,
824 RedirectStartValue::FetchStart => {
825 if self.redirect_start.is_none() {
826 self.redirect_start = self.fetch_start
827 }
828 },
829 },
830 ResourceAttribute::RedirectEnd(val) => match val {
831 RedirectEndValue::Zero => self.redirect_end = None,
832 RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
833 },
834 ResourceAttribute::FetchStart => self.fetch_start = now,
835 ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
836 ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
837 ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
838 ResourceAttribute::ResponseEnd => self.response_end = now,
839 ResourceAttribute::StartTime(val) => match val {
840 ResourceTimeValue::RedirectStart
841 if self.redirect_start.is_none() || !self.timing_check_passed => {},
842 _ => self.start_time = self.get_time_value(val),
843 },
844 }
845 }
846
847 fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
848 match time {
849 ResourceTimeValue::Zero => None,
850 ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
851 ResourceTimeValue::FetchStart => self.fetch_start,
852 ResourceTimeValue::RedirectStart => self.redirect_start,
853 }
854 }
855
856 pub fn mark_timing_check_failed(&mut self) {
857 self.timing_check_passed = false;
858 self.domain_lookup_start = None;
859 self.redirect_count = 0;
860 self.request_start = None;
861 self.response_start = None;
862 self.redirect_start = None;
863 self.connect_start = None;
864 self.connect_end = None;
865 }
866}
867
868#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
870pub struct Metadata {
871 pub final_url: ServoUrl,
873
874 pub location_url: Option<Result<ServoUrl, String>>,
876
877 #[ignore_malloc_size_of = "Defined in hyper"]
878 pub content_type: Option<Serde<ContentType>>,
880
881 pub charset: Option<String>,
883
884 #[ignore_malloc_size_of = "Defined in hyper"]
885 pub headers: Option<Serde<HeaderMap>>,
887
888 pub status: HttpStatus,
890
891 pub https_state: HttpsState,
893
894 pub referrer: Option<ServoUrl>,
896
897 pub referrer_policy: ReferrerPolicy,
899 pub timing: Option<ResourceFetchTiming>,
901 pub redirected: bool,
903}
904
905impl Metadata {
906 pub fn default(url: ServoUrl) -> Self {
908 Metadata {
909 final_url: url,
910 location_url: None,
911 content_type: None,
912 charset: None,
913 headers: None,
914 status: HttpStatus::default(),
915 https_state: HttpsState::None,
916 referrer: None,
917 referrer_policy: ReferrerPolicy::EmptyString,
918 timing: None,
919 redirected: false,
920 }
921 }
922
923 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
925 if self.headers.is_none() {
926 self.headers = Some(Serde(HeaderMap::new()));
927 }
928
929 if let Some(mime) = content_type {
930 self.headers
931 .as_mut()
932 .unwrap()
933 .typed_insert(ContentType::from(mime.clone()));
934 if let Some(charset) = mime.get_param(mime::CHARSET) {
935 self.charset = Some(charset.to_string());
936 }
937 self.content_type = Some(Serde(ContentType::from(mime.clone())));
938 }
939 }
940
941 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
943 if referrer_policy == ReferrerPolicy::EmptyString {
944 return;
945 }
946
947 if self.headers.is_none() {
948 self.headers = Some(Serde(HeaderMap::new()));
949 }
950
951 self.referrer_policy = referrer_policy;
952
953 self.headers
954 .as_mut()
955 .unwrap()
956 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
957 }
958}
959
960#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
962pub enum CookieSource {
963 HTTP,
965 NonHTTP,
967}
968
969#[derive(Clone, Debug, Deserialize, Serialize)]
970pub struct CookieChange {
971 changed: Vec<Serde<Cookie<'static>>>,
972 deleted: Vec<Serde<Cookie<'static>>>,
973}
974
975#[derive(Clone, Debug, Deserialize, Serialize)]
976pub enum CookieData {
977 Change(CookieChange),
978 Get(Option<Serde<Cookie<'static>>>),
979 GetAll(Vec<Serde<Cookie<'static>>>),
980 Set(Result<(), ()>),
981 Delete(Result<(), ()>),
982}
983
984#[derive(Clone, Debug, Deserialize, Serialize)]
985pub struct CookieAsyncResponse {
986 pub data: CookieData,
987}
988
989#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
991pub enum NetworkError {
992 Internal(String),
994 LoadCancelled,
995 SslValidation(String, Vec<u8>),
997 Crash(String),
999}
1000
1001impl NetworkError {
1002 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1003 let error_string = error.to_string();
1004 match certificate {
1005 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1006 _ => NetworkError::Internal(error_string),
1007 }
1008 }
1009
1010 pub fn from_http_error(error: &HttpError) -> Self {
1011 NetworkError::Internal(error.to_string())
1012 }
1013}
1014
1015pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1018 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1019
1020 loop {
1021 match slice.split_first() {
1022 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1023 _ => break,
1024 }
1025 }
1026
1027 loop {
1028 match slice.split_last() {
1029 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1030 _ => break,
1031 }
1032 }
1033
1034 slice
1035}
1036
1037pub fn http_percent_encode(bytes: &[u8]) -> String {
1038 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1041 .add(b' ')
1042 .add(b'"')
1043 .add(b'%')
1044 .add(b'\'')
1045 .add(b'(')
1046 .add(b')')
1047 .add(b'*')
1048 .add(b',')
1049 .add(b'/')
1050 .add(b':')
1051 .add(b';')
1052 .add(b'<')
1053 .add(b'-')
1054 .add(b'>')
1055 .add(b'?')
1056 .add(b'[')
1057 .add(b'\\')
1058 .add(b']')
1059 .add(b'{')
1060 .add(b'}');
1061
1062 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1063}
1064
1065pub fn set_default_accept_language(headers: &mut HeaderMap) {
1067 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1070 return;
1071 }
1072
1073 headers.insert(
1075 header::ACCEPT_LANGUAGE,
1076 HeaderValue::from_static("en-US,en;q=0.5"),
1077 );
1078}
1079
1080pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());