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 request::RequestId;
27use rustc_hash::FxHashMap;
28use rustls_pki_types::CertificateDer;
29use serde::{Deserialize, Serialize};
30use servo_rand::RngCore;
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, Vec<u8>),
246 ProcessResponseEOF(RequestId, Result<ResourceFetchTiming, NetworkError>),
247 ProcessCspViolations(RequestId, Vec<csp::Violation>),
248}
249
250impl FetchResponseMsg {
251 pub fn request_id(&self) -> RequestId {
252 match self {
253 FetchResponseMsg::ProcessRequestBody(id) |
254 FetchResponseMsg::ProcessRequestEOF(id) |
255 FetchResponseMsg::ProcessResponse(id, ..) |
256 FetchResponseMsg::ProcessResponseChunk(id, ..) |
257 FetchResponseMsg::ProcessResponseEOF(id, ..) |
258 FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
259 }
260 }
261}
262
263pub trait FetchTaskTarget {
264 fn process_request_body(&mut self, request: &Request);
268
269 fn process_request_eof(&mut self, request: &Request);
273
274 fn process_response(&mut self, request: &Request, response: &Response);
278
279 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
281
282 fn process_response_eof(&mut self, request: &Request, response: &Response);
286
287 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
288}
289
290#[derive(Clone, Debug, Deserialize, Serialize)]
291pub enum FilteredMetadata {
292 Basic(Metadata),
293 Cors(Metadata),
294 Opaque,
295 OpaqueRedirect(ServoUrl),
296}
297
298#[expect(clippy::large_enum_variant)]
300#[derive(Clone, Debug, Deserialize, Serialize)]
301pub enum FetchMetadata {
302 Unfiltered(Metadata),
303 Filtered {
304 filtered: FilteredMetadata,
305 unsafe_: Metadata,
306 },
307}
308
309impl FetchMetadata {
310 pub fn metadata(&self) -> &Metadata {
311 match self {
312 Self::Unfiltered(metadata) => metadata,
313 Self::Filtered { unsafe_, .. } => unsafe_,
314 }
315 }
316}
317
318pub trait FetchResponseListener {
319 fn process_request_body(&mut self, request_id: RequestId);
320 fn process_request_eof(&mut self, request_id: RequestId);
321 fn process_response(
322 &mut self,
323 request_id: RequestId,
324 metadata: Result<FetchMetadata, NetworkError>,
325 );
326 fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>);
327 fn process_response_eof(
328 &mut self,
329 request_id: RequestId,
330 response: Result<ResourceFetchTiming, NetworkError>,
331 );
332 fn resource_timing(&self) -> &ResourceFetchTiming;
333 fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming;
334 fn submit_resource_timing(&mut self);
335 fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec<csp::Violation>);
336}
337
338impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
339 fn process_request_body(&mut self, request: &Request) {
340 let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
341 }
342
343 fn process_request_eof(&mut self, request: &Request) {
344 let _ = self.send(FetchResponseMsg::ProcessRequestEOF(request.id));
345 }
346
347 fn process_response(&mut self, request: &Request, response: &Response) {
348 let _ = self.send(FetchResponseMsg::ProcessResponse(
349 request.id,
350 response.metadata(),
351 ));
352 }
353
354 fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
355 let _ = self.send(FetchResponseMsg::ProcessResponseChunk(request.id, chunk));
356 }
357
358 fn process_response_eof(&mut self, request: &Request, response: &Response) {
359 let payload = if let Some(network_error) = response.get_network_error() {
360 Err(network_error.clone())
361 } else {
362 Ok(response.get_resource_timing().lock().unwrap().clone())
363 };
364
365 let _ = self.send(FetchResponseMsg::ProcessResponseEOF(request.id, payload));
366 }
367
368 fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
369 let _ = self.send(FetchResponseMsg::ProcessCspViolations(
370 request.id, violations,
371 ));
372 }
373}
374
375pub struct DiscardFetch;
379
380impl FetchTaskTarget for DiscardFetch {
381 fn process_request_body(&mut self, _: &Request) {}
382 fn process_request_eof(&mut self, _: &Request) {}
383 fn process_response(&mut self, _: &Request, _: &Response) {}
384 fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
385 fn process_response_eof(&mut self, _: &Request, _: &Response) {}
386 fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
387}
388
389pub trait Action<Listener> {
390 fn process(self, listener: &mut Listener);
391}
392
393impl<T: FetchResponseListener> Action<T> for FetchResponseMsg {
394 fn process(self, listener: &mut T) {
396 match self {
397 FetchResponseMsg::ProcessRequestBody(request_id) => {
398 listener.process_request_body(request_id)
399 },
400 FetchResponseMsg::ProcessRequestEOF(request_id) => {
401 listener.process_request_eof(request_id)
402 },
403 FetchResponseMsg::ProcessResponse(request_id, meta) => {
404 listener.process_response(request_id, meta)
405 },
406 FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
407 listener.process_response_chunk(request_id, data)
408 },
409 FetchResponseMsg::ProcessResponseEOF(request_id, data) => {
410 match data {
411 Ok(ref response_resource_timing) => {
412 *listener.resource_timing_mut() = response_resource_timing.clone();
414 listener
415 .process_response_eof(request_id, Ok(response_resource_timing.clone()));
416 listener.submit_resource_timing();
419 },
420 Err(e) => listener.process_response_eof(request_id, Err(e)),
425 }
426 },
427 FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
428 listener.process_csp_violations(request_id, violations)
429 },
430 }
431 }
432}
433
434pub trait AsyncRuntime: Send {
437 fn shutdown(&mut self);
438}
439
440pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
442
443#[derive(Clone, Debug, Deserialize, Serialize)]
449pub struct ResourceThreads {
450 pub core_thread: CoreResourceThread,
451}
452
453impl ResourceThreads {
454 pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
455 ResourceThreads { core_thread }
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).map_err(IpcError::Bincode)
466 }
467
468 fn sender(&self) -> IpcSender<CoreResourceMsg> {
469 self.core_thread.clone()
470 }
471}
472
473malloc_size_of_is_0!(ResourceThreads);
475
476#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
477pub enum IncludeSubdomains {
478 Included,
479 NotIncluded,
480}
481
482#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
483pub enum MessageData {
484 Text(String),
485 Binary(Vec<u8>),
486}
487
488#[derive(Debug, Deserialize, Serialize)]
489pub enum WebSocketDomAction {
490 SendMessage(MessageData),
491 Close(Option<u16>, Option<String>),
492}
493
494#[derive(Debug, Deserialize, Serialize)]
495pub enum WebSocketNetworkEvent {
496 ReportCSPViolations(Vec<csp::Violation>),
497 ConnectionEstablished { protocol_in_use: Option<String> },
498 MessageReceived(MessageData),
499 Close(Option<u16>, String),
500 Fail,
501}
502
503#[derive(Debug, Deserialize, Serialize)]
504pub enum FetchChannels {
506 ResponseMsg(IpcSender<FetchResponseMsg>),
507 WebSocket {
508 event_sender: IpcSender<WebSocketNetworkEvent>,
509 action_receiver: IpcReceiver<WebSocketDomAction>,
510 },
511 Prefetch,
514}
515
516#[derive(Debug, Deserialize, Serialize)]
517pub enum CoreResourceMsg {
518 Fetch(RequestBuilder, FetchChannels),
519 Cancel(Vec<RequestId>),
520 FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
522 SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
524 SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
526 SetCookieForUrlAsync(
527 CookieStoreId,
528 ServoUrl,
529 Serde<Cookie<'static>>,
530 CookieSource,
531 ),
532 GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
534 GetCookiesDataForUrl(
536 ServoUrl,
537 IpcSender<Vec<Serde<Cookie<'static>>>>,
538 CookieSource,
539 ),
540 GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
541 GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
542 DeleteCookies(ServoUrl),
543 DeleteCookie(ServoUrl, String),
544 DeleteCookieAsync(CookieStoreId, ServoUrl, String),
545 NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
546 RemoveCookieListener(CookieStoreId),
547 GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
549 SetHistoryState(HistoryStateId, Vec<u8>),
551 RemoveHistoryStates(Vec<HistoryStateId>),
553 ClearCache,
555 NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
557 ToFileManager(FileManagerThreadMsg),
559 Exit(IpcSender<()>),
562}
563
564#[expect(clippy::large_enum_variant)]
566enum ToFetchThreadMessage {
567 Cancel(Vec<RequestId>, CoreResourceThread),
568 StartFetch(
569 RequestBuilder,
570 Option<ResponseInit>,
571 BoxedFetchCallback,
572 CoreResourceThread,
573 ),
574 FetchResponse(FetchResponseMsg),
575 Exit,
577}
578
579pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
580
581struct FetchThread {
585 active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
588 receiver: Receiver<ToFetchThreadMessage>,
592 to_fetch_sender: IpcSender<FetchResponseMsg>,
595}
596
597impl FetchThread {
598 fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
599 let (sender, receiver) = unbounded();
600 let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
601
602 let sender_clone = sender.clone();
603 ROUTER.add_typed_route(
604 from_fetch_sender,
605 Box::new(move |message| {
606 let message: FetchResponseMsg = message.unwrap();
607 let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
608 }),
609 );
610 let join_handle = thread::Builder::new()
611 .name("FetchThread".to_owned())
612 .spawn(move || {
613 let mut fetch_thread = FetchThread {
614 active_fetches: FxHashMap::default(),
615 receiver,
616 to_fetch_sender,
617 };
618 fetch_thread.run();
619 })
620 .expect("Thread spawning failed");
621 (sender, join_handle)
622 }
623
624 fn run(&mut self) {
625 loop {
626 match self.receiver.recv().unwrap() {
627 ToFetchThreadMessage::StartFetch(
628 request_builder,
629 response_init,
630 callback,
631 core_resource_thread,
632 ) => {
633 let request_builder_id = request_builder.id;
634
635 let message = match response_init {
637 Some(response_init) => CoreResourceMsg::FetchRedirect(
638 request_builder,
639 response_init,
640 self.to_fetch_sender.clone(),
641 ),
642 None => CoreResourceMsg::Fetch(
643 request_builder,
644 FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
645 ),
646 };
647
648 core_resource_thread.send(message).unwrap();
649
650 self.active_fetches.insert(request_builder_id, callback);
651 },
652 ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
653 let request_id = fetch_response_msg.request_id();
654 let fetch_finished =
655 matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
656
657 self.active_fetches
658 .get_mut(&request_id)
659 .expect("Got fetch response for unknown fetch")(
660 fetch_response_msg
661 );
662
663 if fetch_finished {
664 self.active_fetches.remove(&request_id);
665 }
666 },
667 ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
668 let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
672 },
673 ToFetchThreadMessage::Exit => break,
674 }
675 }
676 }
677}
678
679static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
680
681pub fn start_fetch_thread() -> JoinHandle<()> {
684 let (sender, join_handle) = FetchThread::spawn();
685 FETCH_THREAD
686 .set(sender)
687 .expect("Fetch thread should be set only once on start-up");
688 join_handle
689}
690
691pub fn exit_fetch_thread() {
696 let _ = FETCH_THREAD
697 .get()
698 .expect("Fetch thread should always be initialized on start-up")
699 .send(ToFetchThreadMessage::Exit);
700}
701
702pub fn fetch_async(
704 core_resource_thread: &CoreResourceThread,
705 request: RequestBuilder,
706 response_init: Option<ResponseInit>,
707 callback: BoxedFetchCallback,
708) {
709 let _ = FETCH_THREAD
710 .get()
711 .expect("Fetch thread should always be initialized on start-up")
712 .send(ToFetchThreadMessage::StartFetch(
713 request,
714 response_init,
715 callback,
716 core_resource_thread.clone(),
717 ));
718}
719
720pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
723 let _ = FETCH_THREAD
724 .get()
725 .expect("Fetch thread should always be initialized on start-up")
726 .send(ToFetchThreadMessage::Cancel(
727 request_ids,
728 core_resource_thread.clone(),
729 ));
730}
731
732#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
733pub struct ResourceCorsData {
734 pub preflight: bool,
736 pub origin: ServoUrl,
738}
739
740#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
741pub struct ResourceFetchTiming {
742 pub domain_lookup_start: Option<CrossProcessInstant>,
743 pub timing_check_passed: bool,
744 pub timing_type: ResourceTimingType,
745 pub redirect_count: u16,
747 pub request_start: Option<CrossProcessInstant>,
748 pub secure_connection_start: Option<CrossProcessInstant>,
749 pub response_start: Option<CrossProcessInstant>,
750 pub fetch_start: Option<CrossProcessInstant>,
751 pub response_end: Option<CrossProcessInstant>,
752 pub redirect_start: Option<CrossProcessInstant>,
753 pub redirect_end: Option<CrossProcessInstant>,
754 pub connect_start: Option<CrossProcessInstant>,
755 pub connect_end: Option<CrossProcessInstant>,
756 pub start_time: Option<CrossProcessInstant>,
757}
758
759pub enum RedirectStartValue {
760 #[allow(dead_code)]
761 Zero,
762 FetchStart,
763}
764
765pub enum RedirectEndValue {
766 Zero,
767 ResponseEnd,
768}
769
770pub enum ResourceTimeValue {
773 Zero,
774 Now,
775 FetchStart,
776 RedirectStart,
777}
778
779pub enum ResourceAttribute {
780 RedirectCount(u16),
781 DomainLookupStart,
782 RequestStart,
783 ResponseStart,
784 RedirectStart(RedirectStartValue),
785 RedirectEnd(RedirectEndValue),
786 FetchStart,
787 ConnectStart(CrossProcessInstant),
788 ConnectEnd(CrossProcessInstant),
789 SecureConnectionStart,
790 ResponseEnd,
791 StartTime(ResourceTimeValue),
792}
793
794#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
795pub enum ResourceTimingType {
796 Resource,
797 Navigation,
798 Error,
799 None,
800}
801
802impl ResourceFetchTiming {
803 pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
804 ResourceFetchTiming {
805 timing_type,
806 timing_check_passed: true,
807 domain_lookup_start: None,
808 redirect_count: 0,
809 secure_connection_start: None,
810 request_start: None,
811 response_start: None,
812 fetch_start: None,
813 redirect_start: None,
814 redirect_end: None,
815 connect_start: None,
816 connect_end: None,
817 response_end: None,
818 start_time: None,
819 }
820 }
821
822 pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
825 let should_attribute_always_be_updated = matches!(
826 attribute,
827 ResourceAttribute::FetchStart |
828 ResourceAttribute::ResponseEnd |
829 ResourceAttribute::StartTime(_)
830 );
831 if !self.timing_check_passed && !should_attribute_always_be_updated {
832 return;
833 }
834 let now = Some(CrossProcessInstant::now());
835 match attribute {
836 ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
837 ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
838 ResourceAttribute::RequestStart => self.request_start = now,
839 ResourceAttribute::ResponseStart => self.response_start = now,
840 ResourceAttribute::RedirectStart(val) => match val {
841 RedirectStartValue::Zero => self.redirect_start = None,
842 RedirectStartValue::FetchStart => {
843 if self.redirect_start.is_none() {
844 self.redirect_start = self.fetch_start
845 }
846 },
847 },
848 ResourceAttribute::RedirectEnd(val) => match val {
849 RedirectEndValue::Zero => self.redirect_end = None,
850 RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
851 },
852 ResourceAttribute::FetchStart => self.fetch_start = now,
853 ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
854 ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
855 ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
856 ResourceAttribute::ResponseEnd => self.response_end = now,
857 ResourceAttribute::StartTime(val) => match val {
858 ResourceTimeValue::RedirectStart
859 if self.redirect_start.is_none() || !self.timing_check_passed => {},
860 _ => self.start_time = self.get_time_value(val),
861 },
862 }
863 }
864
865 fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
866 match time {
867 ResourceTimeValue::Zero => None,
868 ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
869 ResourceTimeValue::FetchStart => self.fetch_start,
870 ResourceTimeValue::RedirectStart => self.redirect_start,
871 }
872 }
873
874 pub fn mark_timing_check_failed(&mut self) {
875 self.timing_check_passed = false;
876 self.domain_lookup_start = None;
877 self.redirect_count = 0;
878 self.request_start = None;
879 self.response_start = None;
880 self.redirect_start = None;
881 self.connect_start = None;
882 self.connect_end = None;
883 }
884}
885
886#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
888pub struct Metadata {
889 pub final_url: ServoUrl,
891
892 pub location_url: Option<Result<ServoUrl, String>>,
894
895 #[ignore_malloc_size_of = "Defined in hyper"]
896 pub content_type: Option<Serde<ContentType>>,
898
899 pub charset: Option<String>,
901
902 #[ignore_malloc_size_of = "Defined in hyper"]
903 pub headers: Option<Serde<HeaderMap>>,
905
906 pub status: HttpStatus,
908
909 pub https_state: HttpsState,
911
912 pub referrer: Option<ServoUrl>,
914
915 pub referrer_policy: ReferrerPolicy,
917 pub timing: Option<ResourceFetchTiming>,
919 pub redirected: bool,
921}
922
923impl Metadata {
924 pub fn default(url: ServoUrl) -> Self {
926 Metadata {
927 final_url: url,
928 location_url: None,
929 content_type: None,
930 charset: None,
931 headers: None,
932 status: HttpStatus::default(),
933 https_state: HttpsState::None,
934 referrer: None,
935 referrer_policy: ReferrerPolicy::EmptyString,
936 timing: None,
937 redirected: false,
938 }
939 }
940
941 pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
943 if self.headers.is_none() {
944 self.headers = Some(Serde(HeaderMap::new()));
945 }
946
947 if let Some(mime) = content_type {
948 self.headers
949 .as_mut()
950 .unwrap()
951 .typed_insert(ContentType::from(mime.clone()));
952 if let Some(charset) = mime.get_param(mime::CHARSET) {
953 self.charset = Some(charset.to_string());
954 }
955 self.content_type = Some(Serde(ContentType::from(mime.clone())));
956 }
957 }
958
959 pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
961 if referrer_policy == ReferrerPolicy::EmptyString {
962 return;
963 }
964
965 if self.headers.is_none() {
966 self.headers = Some(Serde(HeaderMap::new()));
967 }
968
969 self.referrer_policy = referrer_policy;
970
971 self.headers
972 .as_mut()
973 .unwrap()
974 .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
975 }
976}
977
978#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
980pub enum CookieSource {
981 HTTP,
983 NonHTTP,
985}
986
987#[derive(Clone, Debug, Deserialize, Serialize)]
988pub struct CookieChange {
989 changed: Vec<Serde<Cookie<'static>>>,
990 deleted: Vec<Serde<Cookie<'static>>>,
991}
992
993#[derive(Clone, Debug, Deserialize, Serialize)]
994pub enum CookieData {
995 Change(CookieChange),
996 Get(Option<Serde<Cookie<'static>>>),
997 GetAll(Vec<Serde<Cookie<'static>>>),
998 Set(Result<(), ()>),
999 Delete(Result<(), ()>),
1000}
1001
1002#[derive(Clone, Debug, Deserialize, Serialize)]
1003pub struct CookieAsyncResponse {
1004 pub data: CookieData,
1005}
1006
1007#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1009pub enum NetworkError {
1010 Internal(String),
1012 LoadCancelled,
1013 SslValidation(String, Vec<u8>),
1015 Crash(String),
1017}
1018
1019impl NetworkError {
1020 pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1021 let error_string = error.to_string();
1022 match certificate {
1023 Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1024 _ => NetworkError::Internal(error_string),
1025 }
1026 }
1027
1028 pub fn from_http_error(error: &HttpError) -> Self {
1029 NetworkError::Internal(error.to_string())
1030 }
1031}
1032
1033pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1036 const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1037
1038 loop {
1039 match slice.split_first() {
1040 Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1041 _ => break,
1042 }
1043 }
1044
1045 loop {
1046 match slice.split_last() {
1047 Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1048 _ => break,
1049 }
1050 }
1051
1052 slice
1053}
1054
1055pub fn http_percent_encode(bytes: &[u8]) -> String {
1056 const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1059 .add(b' ')
1060 .add(b'"')
1061 .add(b'%')
1062 .add(b'\'')
1063 .add(b'(')
1064 .add(b')')
1065 .add(b'*')
1066 .add(b',')
1067 .add(b'/')
1068 .add(b':')
1069 .add(b';')
1070 .add(b'<')
1071 .add(b'-')
1072 .add(b'>')
1073 .add(b'?')
1074 .add(b'[')
1075 .add(b'\\')
1076 .add(b']')
1077 .add(b'{')
1078 .add(b'}');
1079
1080 percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1081}
1082
1083pub fn set_default_accept_language(headers: &mut HeaderMap) {
1084 if headers.contains_key(header::ACCEPT_LANGUAGE) {
1085 return;
1086 }
1087
1088 headers.insert(
1090 header::ACCEPT_LANGUAGE,
1091 HeaderValue::from_static("en-US,en;q=0.5"),
1092 );
1093}
1094
1095pub static PRIVILEGED_SECRET: LazyLock<u32> =
1096 LazyLock::new(|| servo_rand::ServoRng::default().next_u32());