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().unwrap().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
438impl IpcSend<CoreResourceMsg> for ResourceThreads {
439 fn send(&self, msg: CoreResourceMsg) -> IpcSendResult {
440 self.core_thread.send(msg).map_err(IpcError::Bincode)
441 }
442
443 fn sender(&self) -> IpcSender<CoreResourceMsg> {
444 self.core_thread.clone()
445 }
446}
447
448malloc_size_of_is_0!(ResourceThreads);
450
451#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
452pub enum IncludeSubdomains {
453 Included,
454 NotIncluded,
455}
456
457#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
458pub enum MessageData {
459 Text(String),
460 Binary(Vec<u8>),
461}
462
463#[derive(Debug, Deserialize, Serialize)]
464pub enum WebSocketDomAction {
465 SendMessage(MessageData),
466 Close(Option<u16>, Option<String>),
467}
468
469#[derive(Debug, Deserialize, Serialize)]
470pub enum WebSocketNetworkEvent {
471 ReportCSPViolations(Vec<csp::Violation>),
472 ConnectionEstablished { protocol_in_use: Option<String> },
473 MessageReceived(MessageData),
474 Close(Option<u16>, String),
475 Fail,
476}
477
478#[derive(Debug, Deserialize, Serialize)]
479pub enum FetchChannels {
481 ResponseMsg(IpcSender<FetchResponseMsg>),
482 WebSocket {
483 event_sender: IpcSender<WebSocketNetworkEvent>,
484 action_receiver: IpcReceiver<WebSocketDomAction>,
485 },
486 Prefetch,
489}
490
491#[derive(Debug, Deserialize, Serialize)]
492pub enum CoreResourceMsg {
493 Fetch(RequestBuilder, FetchChannels),
494 Cancel(Vec<RequestId>),
495 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
497 SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
499 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
501 SetCookieForUrlAsync(
502 CookieStoreId,
503 ServoUrl,
504 Serde<Cookie<'static>>,
505 CookieSource,
506 ),
507 GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
509 GetCookiesDataForUrl(
511 ServoUrl,
512 IpcSender<Vec<Serde<Cookie<'static>>>>,
513 CookieSource,
514 ),
515 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
516 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
517 DeleteCookies(ServoUrl),
518 DeleteCookie(ServoUrl, String),
519 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
520 NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
521 RemoveCookieListener(CookieStoreId),
522 GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
524 SetHistoryState(HistoryStateId, Vec<u8>),
526 RemoveHistoryStates(Vec<HistoryStateId>),
528 ClearCache,
530 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
532 ToFileManager(FileManagerThreadMsg),
534 Exit(IpcSender<()>),
537}
538
539#[expect(clippy::large_enum_variant)]
541enum ToFetchThreadMessage {
542 Cancel(Vec<RequestId>, CoreResourceThread),
543 StartFetch(
544 RequestBuilder,
545 Option<ResponseInit>,
546 BoxedFetchCallback,
547 CoreResourceThread,
548 ),
549 FetchResponse(FetchResponseMsg),
550 Exit,
552}
553
554pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
555
556struct FetchThread {
560 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
563 receiver: Receiver<ToFetchThreadMessage>,
567 to_fetch_sender: IpcSender<FetchResponseMsg>,
570}
571
572impl FetchThread {
573 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
574 let (sender, receiver) = unbounded();
575 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
576
577 let sender_clone = sender.clone();
578 ROUTER.add_typed_route(
579 from_fetch_sender,
580 Box::new(move |message| {
581 let message: FetchResponseMsg = message.unwrap();
582 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
583 }),
584 );
585 let join_handle = thread::Builder::new()
586 .name("FetchThread".to_owned())
587 .spawn(move || {
588 let mut fetch_thread = FetchThread {
589 active_fetches: FxHashMap::default(),
590 receiver,
591 to_fetch_sender,
592 };
593 fetch_thread.run();
594 })
595 .expect("Thread spawning failed");
596 (sender, join_handle)
597 }
598
599 fn run(&mut self) {
600 loop {
601 match self.receiver.recv().unwrap() {
602 ToFetchThreadMessage::StartFetch(
603 request_builder,
604 response_init,
605 callback,
606 core_resource_thread,
607 ) => {
608 let request_builder_id = request_builder.id;
609
610 let message = match response_init {
612 Some(response_init) => CoreResourceMsg::FetchRedirect(
613 request_builder,
614 response_init,
615 self.to_fetch_sender.clone(),
616 ),
617 None => CoreResourceMsg::Fetch(
618 request_builder,
619 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
620 ),
621 };
622
623 core_resource_thread.send(message).unwrap();
624
625 self.active_fetches.insert(request_builder_id, callback);
626 },
627 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
628 let request_id = fetch_response_msg.request_id();
629 let fetch_finished =
630 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
631
632 self.active_fetches
633 .get_mut(&request_id)
634 .expect("Got fetch response for unknown fetch")(
635 fetch_response_msg
636 );
637
638 if fetch_finished {
639 self.active_fetches.remove(&request_id);
640 }
641 },
642 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
643 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
647 },
648 ToFetchThreadMessage::Exit => break,
649 }
650 }
651 }
652}
653
654static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
655
656pub fn start_fetch_thread() -> JoinHandle<()> {
659 let (sender, join_handle) = FetchThread::spawn();
660 FETCH_THREAD
661 .set(sender)
662 .expect("Fetch thread should be set only once on start-up");
663 join_handle
664}
665
666pub fn exit_fetch_thread() {
671 let _ = FETCH_THREAD
672 .get()
673 .expect("Fetch thread should always be initialized on start-up")
674 .send(ToFetchThreadMessage::Exit);
675}
676
677pub fn fetch_async(
679 core_resource_thread: &CoreResourceThread,
680 request: RequestBuilder,
681 response_init: Option<ResponseInit>,
682 callback: BoxedFetchCallback,
683) {
684 let _ = FETCH_THREAD
685 .get()
686 .expect("Fetch thread should always be initialized on start-up")
687 .send(ToFetchThreadMessage::StartFetch(
688 request,
689 response_init,
690 callback,
691 core_resource_thread.clone(),
692 ));
693}
694
695pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
698 let _ = FETCH_THREAD
699 .get()
700 .expect("Fetch thread should always be initialized on start-up")
701 .send(ToFetchThreadMessage::Cancel(
702 request_ids,
703 core_resource_thread.clone(),
704 ));
705}
706
707#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
708pub struct ResourceCorsData {
709 pub preflight: bool,
711 pub origin: ServoUrl,
713}
714
715#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
716pub struct ResourceFetchTiming {
717 pub domain_lookup_start: Option<CrossProcessInstant>,
718 pub timing_check_passed: bool,
719 pub timing_type: ResourceTimingType,
720 pub redirect_count: u16,
722 pub request_start: Option<CrossProcessInstant>,
723 pub secure_connection_start: Option<CrossProcessInstant>,
724 pub response_start: Option<CrossProcessInstant>,
725 pub fetch_start: Option<CrossProcessInstant>,
726 pub response_end: Option<CrossProcessInstant>,
727 pub redirect_start: Option<CrossProcessInstant>,
728 pub redirect_end: Option<CrossProcessInstant>,
729 pub connect_start: Option<CrossProcessInstant>,
730 pub connect_end: Option<CrossProcessInstant>,
731 pub start_time: Option<CrossProcessInstant>,
732}
733
734pub enum RedirectStartValue {
735 Zero,
736 FetchStart,
737}
738
739pub enum RedirectEndValue {
740 Zero,
741 ResponseEnd,
742}
743
744pub enum ResourceTimeValue {
747 Zero,
748 Now,
749 FetchStart,
750 RedirectStart,
751}
752
753pub enum ResourceAttribute {
754 RedirectCount(u16),
755 DomainLookupStart,
756 RequestStart,
757 ResponseStart,
758 RedirectStart(RedirectStartValue),
759 RedirectEnd(RedirectEndValue),
760 FetchStart,
761 ConnectStart(CrossProcessInstant),
762 ConnectEnd(CrossProcessInstant),
763 SecureConnectionStart,
764 ResponseEnd,
765 StartTime(ResourceTimeValue),
766}
767
768#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
769pub enum ResourceTimingType {
770 Resource,
771 Navigation,
772 Error,
773 None,
774}
775
776impl ResourceFetchTiming {
777 pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
778 ResourceFetchTiming {
779 timing_type,
780 timing_check_passed: true,
781 domain_lookup_start: None,
782 redirect_count: 0,
783 secure_connection_start: None,
784 request_start: None,
785 response_start: None,
786 fetch_start: None,
787 redirect_start: None,
788 redirect_end: None,
789 connect_start: None,
790 connect_end: None,
791 response_end: None,
792 start_time: None,
793 }
794 }
795
796 pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
799 let should_attribute_always_be_updated = matches!(
800 attribute,
801 ResourceAttribute::FetchStart |
802 ResourceAttribute::ResponseEnd |
803 ResourceAttribute::StartTime(_)
804 );
805 if !self.timing_check_passed && !should_attribute_always_be_updated {
806 return;
807 }
808 let now = Some(CrossProcessInstant::now());
809 match attribute {
810 ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
811 ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
812 ResourceAttribute::RequestStart => self.request_start = now,
813 ResourceAttribute::ResponseStart => self.response_start = now,
814 ResourceAttribute::RedirectStart(val) => match val {
815 RedirectStartValue::Zero => self.redirect_start = None,
816 RedirectStartValue::FetchStart => {
817 if self.redirect_start.is_none() {
818 self.redirect_start = self.fetch_start
819 }
820 },
821 },
822 ResourceAttribute::RedirectEnd(val) => match val {
823 RedirectEndValue::Zero => self.redirect_end = None,
824 RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
825 },
826 ResourceAttribute::FetchStart => self.fetch_start = now,
827 ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
828 ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
829 ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
830 ResourceAttribute::ResponseEnd => self.response_end = now,
831 ResourceAttribute::StartTime(val) => match val {
832 ResourceTimeValue::RedirectStart
833 if self.redirect_start.is_none() || !self.timing_check_passed => {},
834 _ => self.start_time = self.get_time_value(val),
835 },
836 }
837 }
838
839 fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
840 match time {
841 ResourceTimeValue::Zero => None,
842 ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
843 ResourceTimeValue::FetchStart => self.fetch_start,
844 ResourceTimeValue::RedirectStart => self.redirect_start,
845 }
846 }
847
848 pub fn mark_timing_check_failed(&mut self) {
849 self.timing_check_passed = false;
850 self.domain_lookup_start = None;
851 self.redirect_count = 0;
852 self.request_start = None;
853 self.response_start = None;
854 self.redirect_start = None;
855 self.connect_start = None;
856 self.connect_end = None;
857 }
858}
859
860#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
862pub struct Metadata {
863 pub final_url: ServoUrl,
865
866 pub location_url: Option<Result<ServoUrl, String>>,
868
869 #[ignore_malloc_size_of = "Defined in hyper"]
870 pub content_type: Option<Serde<ContentType>>,
872
873 pub charset: Option<String>,
875
876 #[ignore_malloc_size_of = "Defined in hyper"]
877 pub headers: Option<Serde<HeaderMap>>,
879
880 pub status: HttpStatus,
882
883 pub https_state: HttpsState,
885
886 pub referrer: Option<ServoUrl>,
888
889 pub referrer_policy: ReferrerPolicy,
891 pub timing: Option<ResourceFetchTiming>,
893 pub redirected: bool,
895}
896
897impl Metadata {
898 pub fn default(url: ServoUrl) -> Self {
900 Metadata {
901 final_url: url,
902 location_url: None,
903 content_type: None,
904 charset: None,
905 headers: None,
906 status: HttpStatus::default(),
907 https_state: HttpsState::None,
908 referrer: None,
909 referrer_policy: ReferrerPolicy::EmptyString,
910 timing: None,
911 redirected: false,
912 }
913 }
914
915 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
917 if self.headers.is_none() {
918 self.headers = Some(Serde(HeaderMap::new()));
919 }
920
921 if let Some(mime) = content_type {
922 self.headers
923 .as_mut()
924 .unwrap()
925 .typed_insert(ContentType::from(mime.clone()));
926 if let Some(charset) = mime.get_param(mime::CHARSET) {
927 self.charset = Some(charset.to_string());
928 }
929 self.content_type = Some(Serde(ContentType::from(mime.clone())));
930 }
931 }
932
933 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
935 if referrer_policy == ReferrerPolicy::EmptyString {
936 return;
937 }
938
939 if self.headers.is_none() {
940 self.headers = Some(Serde(HeaderMap::new()));
941 }
942
943 self.referrer_policy = referrer_policy;
944
945 self.headers
946 .as_mut()
947 .unwrap()
948 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
949 }
950}
951
952#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
954pub enum CookieSource {
955 HTTP,
957 NonHTTP,
959}
960
961#[derive(Clone, Debug, Deserialize, Serialize)]
962pub struct CookieChange {
963 changed: Vec<Serde<Cookie<'static>>>,
964 deleted: Vec<Serde<Cookie<'static>>>,
965}
966
967#[derive(Clone, Debug, Deserialize, Serialize)]
968pub enum CookieData {
969 Change(CookieChange),
970 Get(Option<Serde<Cookie<'static>>>),
971 GetAll(Vec<Serde<Cookie<'static>>>),
972 Set(Result<(), ()>),
973 Delete(Result<(), ()>),
974}
975
976#[derive(Clone, Debug, Deserialize, Serialize)]
977pub struct CookieAsyncResponse {
978 pub data: CookieData,
979}
980
981#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
983pub enum NetworkError {
984 Internal(String),
986 LoadCancelled,
987 SslValidation(String, Vec<u8>),
989 Crash(String),
991}
992
993impl NetworkError {
994 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
995 let error_string = error.to_string();
996 match certificate {
997 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
998 _ => NetworkError::Internal(error_string),
999 }
1000 }
1001
1002 pub fn from_http_error(error: &HttpError) -> Self {
1003 NetworkError::Internal(error.to_string())
1004 }
1005}
1006
1007pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1010 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1011
1012 loop {
1013 match slice.split_first() {
1014 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1015 _ => break,
1016 }
1017 }
1018
1019 loop {
1020 match slice.split_last() {
1021 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1022 _ => break,
1023 }
1024 }
1025
1026 slice
1027}
1028
1029pub fn http_percent_encode(bytes: &[u8]) -> String {
1030 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1033 .add(b' ')
1034 .add(b'"')
1035 .add(b'%')
1036 .add(b'\'')
1037 .add(b'(')
1038 .add(b')')
1039 .add(b'*')
1040 .add(b',')
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
1054 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1055}
1056
1057pub fn set_default_accept_language(headers: &mut HeaderMap) {
1059 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1062 return;
1063 }
1064
1065 headers.insert(
1067 header::ACCEPT_LANGUAGE,
1068 HeaderValue::from_static("en-US,en;q=0.5"),
1069 );
1070}
1071
1072pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());