1use std::sync::Arc;
9use std::time::{Duration, UNIX_EPOCH};
10
11use atomic_refcell::AtomicRefCell;
12use base64::engine::Engine;
13use base64::engine::general_purpose::STANDARD;
14use chrono::{Local, LocalResult, TimeZone};
15use devtools_traits::{HttpRequest, HttpResponse};
16use headers::{ContentLength, HeaderMapExt};
17use http::HeaderMap;
18use malloc_size_of_derive::MallocSizeOf;
19use net::cookie::ServoCookie;
20use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
21use net_traits::{CookieSource, TlsSecurityInfo};
22use serde::Serialize;
23use serde_json::{Map, Value};
24use servo_url::ServoUrl;
25
26use crate::StreamId;
27use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
28use crate::actors::browsing_context::BrowsingContextActor;
29use crate::actors::long_string::LongStringActor;
30use crate::network_handler::Cause;
31use crate::protocol::ClientRequest;
32
33#[derive(Default, MallocSizeOf)]
34pub(crate) struct NetworkEventActor {
35 name: String,
36 request: AtomicRefCell<Option<NetworkEventRequest>>,
37 resource_id: u64,
38 response: AtomicRefCell<Option<NetworkEventResponse>>,
39 security_info: AtomicRefCell<TlsSecurityInfo>,
40 pub browsing_context_name: String,
41}
42
43#[derive(Clone, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub(crate) struct NetworkEventResource {
46 #[serde(rename = "browsingContextID")]
47 browsing_context_id: u32,
48 inner_window_id: u64,
49 resource_id: u64,
50 resource_updates: ResourceUpdates,
51}
52
53#[derive(Clone, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub(crate) struct NetworkEventMsg {
56 actor: String,
57 #[serde(rename = "browsingContextID")]
58 browsing_context_id: u32,
59 cause: Cause,
60 #[serde(rename = "isXHR")]
61 is_xhr: bool,
62 method: String,
63 private: bool,
64 resource_id: u64,
65 started_date_time: String,
66 time_stamp: i64,
67 url: String,
68}
69
70#[derive(Serialize)]
71#[serde(rename_all = "camelCase")]
72struct GetRequestHeadersReply {
73 from: String,
74 headers: Vec<HeaderWrapper>,
75 header_size: usize,
76 raw_headers: String,
77}
78
79#[derive(Serialize)]
80struct GetCookiesReply {
81 from: String,
82 cookies: Vec<CookieWrapper>,
83}
84
85#[derive(Serialize)]
86#[serde(rename_all = "camelCase")]
87struct GetRequestPostDataReply {
88 from: String,
89 post_data: Option<Vec<u8>>,
90 post_data_discarded: bool,
91}
92
93#[derive(Serialize)]
94#[serde(rename_all = "camelCase")]
95struct GetResponseHeadersReply {
96 from: String,
97 headers: Vec<HeaderWrapper>,
98 header_size: usize,
99 raw_headers: String,
100}
101
102#[derive(Serialize)]
103#[serde(rename_all = "camelCase")]
104struct GetResponseContentReply {
105 from: String,
106 content: Option<ResponseContent>,
107 content_discarded: bool,
108}
109
110#[derive(Serialize)]
111#[serde(rename_all = "camelCase")]
112struct GetEventTimingsReply {
113 from: String,
114 offsets: Timings,
115 server_timings: Vec<()>,
116 timings: Timings,
117 total_time: usize,
118}
119
120#[derive(Serialize)]
121#[serde(rename_all = "camelCase")]
122struct GetSecurityInfoReply {
123 from: String,
124 security_info: SecurityInfo,
125}
126
127#[derive(Clone, Serialize)]
128#[serde(rename_all = "camelCase")]
129struct RequestFields {
130 event_timings_available: bool,
131 #[serde(skip_serializing_if = "Option::is_none")]
132 remote_address: Option<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 remote_port: Option<u16>,
135 request_cookies_available: bool,
136 request_headers_available: bool,
137 total_time: f64,
138}
139
140#[derive(Clone, Serialize)]
141#[serde(rename_all = "camelCase")]
142struct ResponseFields {
143 #[serde(flatten)]
144 cache_details: CacheDetails,
145 response_content_available: bool,
146 response_cookies_available: bool,
147 response_headers_available: bool,
148 response_start_available: bool,
149 status: String,
150 status_text: String,
151}
152
153#[derive(Clone, Serialize)]
154#[serde(rename_all = "camelCase")]
155struct SecurityFields {
156 security_state: String,
157 security_info_available: bool,
158}
159
160#[derive(Clone, Serialize)]
161#[serde(rename_all = "camelCase")]
162pub(crate) struct ResourceUpdates {
163 http_version: String,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 #[serde(flatten)]
166 request: Option<RequestFields>,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 #[serde(flatten)]
169 response: Option<ResponseFields>,
170 #[serde(flatten)]
171 security: SecurityFields,
172}
173
174#[derive(Serialize)]
175#[serde(rename_all = "camelCase")]
176struct ResponseContent {
177 body_size: usize,
178 content_charset: String,
179 decoded_body_size: usize,
180 #[serde(skip_serializing_if = "Option::is_none")]
181 encoding: Option<String>,
182 headers_size: usize,
183 is_content_encoded: bool,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 mime_type: Option<String>,
186 size: usize,
187 text: Value,
188 #[serde(skip_serializing_if = "Option::is_none")]
189 transferred_size: Option<u64>,
190}
191
192#[derive(Clone, Serialize, MallocSizeOf)]
193#[serde(rename_all = "camelCase")]
194pub(crate) struct CacheDetails {
195 from_cache: bool,
196 from_service_worker: bool,
197}
198
199#[derive(Clone, Default, Serialize, MallocSizeOf)]
200pub(crate) struct Timings {
201 blocked: usize,
202 dns: usize,
203 connect: usize,
204 send: usize,
205 wait: usize,
206 receive: usize,
207}
208
209impl Timings {
210 fn total(&self) -> usize {
211 self.dns + self.connect + self.send + self.wait + self.receive
212 }
213}
214
215#[derive(Serialize, Default)]
216#[serde(rename_all = "camelCase")]
217struct CertificateIdentity {
218 #[serde(skip_serializing_if = "Option::is_none")]
219 name: Option<String>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 common_name: Option<String>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 organization: Option<String>,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 organizational_unit: Option<String>,
226}
227
228#[derive(Serialize, Default)]
229#[serde(rename_all = "camelCase")]
230struct CertificateValidity {
231 #[serde(skip_serializing_if = "Option::is_none")]
232 start: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
234 end: Option<String>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 lifetime: Option<String>,
237 expired: bool,
238}
239
240#[derive(Serialize, Default)]
241#[serde(rename_all = "camelCase")]
242struct CertificateFingerprint {
243 #[serde(skip_serializing_if = "Option::is_none")]
244 sha256: Option<String>,
245 #[serde(skip_serializing_if = "Option::is_none")]
246 sha1: Option<String>,
247}
248
249#[derive(Serialize, Default)]
250#[serde(rename_all = "camelCase")]
251struct SecurityCertificate {
252 subject: CertificateIdentity,
253 issuer: CertificateIdentity,
254 validity: CertificateValidity,
255 fingerprint: CertificateFingerprint,
256 #[serde(skip_serializing_if = "Option::is_none")]
257 serial_number: Option<String>,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 is_built_in_root: Option<bool>,
260}
261
262#[derive(Serialize, Default)]
263#[serde(rename_all = "camelCase")]
264struct SecurityInfo {
265 state: String,
266 #[serde(skip_serializing_if = "Vec::is_empty")]
267 weakness_reasons: Vec<String>,
268 #[serde(skip_serializing_if = "Option::is_none")]
269 protocol_version: Option<String>,
270 #[serde(skip_serializing_if = "Option::is_none")]
271 cipher_suite: Option<String>,
272 #[serde(skip_serializing_if = "Option::is_none")]
273 kea_group_name: Option<String>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 signature_scheme_name: Option<String>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 alpn_protocol: Option<String>,
278 #[serde(skip_serializing_if = "Option::is_none")]
279 certificate_transparency: Option<String>,
280 hsts: bool,
281 hpkp: bool,
282 used_ech: bool,
283 used_delegated_credentials: bool,
284 used_ocsp: bool,
285 used_private_dns: bool,
286 #[serde(skip_serializing_if = "Vec::is_empty")]
287 certificate_chain: Vec<String>,
288 cert: SecurityCertificate,
289}
290
291impl From<&TlsSecurityInfo> for SecurityInfo {
292 fn from(info: &TlsSecurityInfo) -> Self {
293 Self {
294 state: info.state.to_string(),
295 weakness_reasons: info.weakness_reasons.clone(),
296 protocol_version: info
297 .protocol_version
298 .as_ref()
299 .map(|protocol_version| format!("{protocol_version:?}")),
300 cipher_suite: info
301 .cipher_suite
302 .as_ref()
303 .map(|cipher_suite| format!("{cipher_suite:?}")),
304 kea_group_name: info
305 .kea_group_name
306 .as_ref()
307 .map(|group_name| format!("{group_name:?}")),
308 signature_scheme_name: info.signature_scheme_name.clone(),
309 alpn_protocol: info.alpn_protocol.clone(),
310 certificate_transparency: info
311 .certificate_transparency
312 .clone()
313 .or_else(|| Some("unknown".to_string())),
314 hsts: info.hsts,
315 hpkp: info.hpkp,
316 used_ech: info.used_ech,
317 used_delegated_credentials: info.used_delegated_credentials,
318 used_ocsp: info.used_ocsp,
319 used_private_dns: info.used_private_dns,
320 ..Default::default()
321 }
322 }
323}
324
325#[derive(MallocSizeOf)]
326struct NetworkEventRequest {
327 offsets: Timings,
328 timings: Timings,
329 request: HttpRequest,
330 total_time: Duration,
331}
332
333#[derive(MallocSizeOf)]
334struct NetworkEventResponse {
335 cache_details: CacheDetails,
336 response: HttpResponse,
337}
338
339#[derive(Serialize)]
340pub(crate) struct CookieWrapper {
341 name: String,
342 value: String,
343 #[serde(skip_serializing_if = "Option::is_none")]
344 path: Option<String>,
345 #[serde(skip_serializing_if = "Option::is_none")]
346 domain: Option<String>,
347 #[serde(skip_serializing_if = "Option::is_none")]
348 expires: Option<String>,
349 #[serde(skip_serializing_if = "Option::is_none")]
350 http_only: Option<bool>,
351 #[serde(skip_serializing_if = "Option::is_none")]
352 secure: Option<bool>,
353 #[serde(skip_serializing_if = "Option::is_none")]
354 same_site: Option<String>,
355}
356
357#[derive(Serialize)]
358struct HeaderWrapper {
359 name: String,
360 value: String,
361}
362
363impl Actor for NetworkEventActor {
364 fn name(&self) -> &str {
365 &self.name
366 }
367
368 fn handle_message(
369 &self,
370 client_request: ClientRequest,
371 registry: &ActorRegistry,
372 msg_type: &str,
373 _msg: &Map<String, Value>,
374 _id: StreamId,
375 ) -> Result<(), ActorError> {
376 match msg_type {
377 "getRequestHeaders" => {
378 let request = self.request.borrow();
379 let request = request.as_ref().ok_or(ActorError::Internal)?;
380
381 let headers = get_header_list(&request.request.headers);
382 let raw_headers = get_raw_headers(&headers);
383
384 let msg = GetRequestHeadersReply {
385 from: self.name().into(),
386 headers,
387 header_size: raw_headers.len(),
388 raw_headers,
389 };
390 client_request.reply_final(&msg)?
391 },
392
393 "getRequestCookies" => {
394 let request = self.request.borrow();
395 let request = request.as_ref().ok_or(ActorError::Internal)?;
396
397 let msg = GetCookiesReply {
398 from: self.name().into(),
399 cookies: get_cookies_from_headers(
400 &request.request.headers,
401 &request.request.url,
402 ),
403 };
404
405 client_request.reply_final(&msg)?
406 },
407
408 "getRequestPostData" => {
409 let request = self.request.borrow();
410 let request = request.as_ref().ok_or(ActorError::Internal)?;
411
412 let msg = GetRequestPostDataReply {
413 from: self.name().into(),
414 post_data: request.request.body.as_ref().map(|b| b.to_vec()),
415 post_data_discarded: request.request.body.is_none(),
416 };
417 client_request.reply_final(&msg)?
418 },
419
420 "getResponseHeaders" => {
421 let response = self.response.borrow();
422 let response = response.as_ref().ok_or(ActorError::Internal)?;
423
424 let list = response
425 .response
426 .headers
427 .as_ref()
428 .map(get_header_list)
429 .unwrap_or_default();
430 let raw_headers = get_raw_headers(&list);
431
432 let msg = GetResponseHeadersReply {
433 from: self.name().into(),
434 headers: list,
435 header_size: raw_headers.len(),
436 raw_headers,
437 };
438 client_request.reply_final(&msg)?;
439 },
440
441 "getResponseCookies" => {
442 let request = self.request.borrow();
443 let request = request.as_ref().ok_or(ActorError::Internal)?;
444 let response = self.response.borrow();
445 let response = response.as_ref().ok_or(ActorError::Internal)?;
446
447 let msg = GetCookiesReply {
448 from: self.name().into(),
449 cookies: get_cookies_from_headers(
450 response
451 .response
452 .headers
453 .as_ref()
454 .ok_or(ActorError::Internal)?,
455 &request.request.url,
456 ),
457 };
458 client_request.reply_final(&msg)?
459 },
460
461 "getResponseContent" => {
462 let response = self.response.borrow();
463 let response = response.as_ref().ok_or(ActorError::Internal)?;
464
465 let headers = response.response.headers.as_ref();
466 let list = headers.map(get_header_list).unwrap_or_default();
467 let raw_headers = get_raw_headers(&list);
468
469 let mime_type = headers
470 .and_then(extract_mime_type_as_dataurl_mime)
471 .map(|url| url.to_string());
472 let transferred_size = headers
473 .and_then(|header| header.typed_get::<ContentLength>())
474 .map(|content_length_header| content_length_header.0);
475
476 let content = response.response.body.as_ref().map(|body| {
477 let (encoding, text) = if mime_type.is_some() {
478 let body_string = String::from_utf8_lossy(body).to_string();
480 let long_string_actor = LongStringActor::register(registry, body_string);
481 let value = long_string_actor.long_string_obj();
482 (None, serde_json::to_value(value).unwrap())
483 } else {
484 let b64 = STANDARD.encode(body);
485 (Some("base64".into()), serde_json::to_value(b64).unwrap())
486 };
487 let is_content_encoded = encoding.is_some();
488
489 ResponseContent {
490 body_size: body.len(),
491 content_charset: String::new(),
492 decoded_body_size: body.len(),
493 encoding,
494 headers_size: raw_headers.len(),
495 is_content_encoded,
496 mime_type,
497 size: body.len(),
498 text,
499 transferred_size,
500 }
501 });
502
503 let msg = GetResponseContentReply {
504 from: self.name().into(),
505 content,
506 content_discarded: response.response.body.is_none(),
507 };
508 client_request.reply_final(&msg)?
509 },
510
511 "getEventTimings" => {
512 let request = self.request.borrow();
513 let request = request.as_ref().ok_or(ActorError::Internal)?;
514
515 let offsets = request.offsets.clone();
516 let timings = request.timings.clone();
517 let total_time = timings.total();
518
519 let msg = GetEventTimingsReply {
520 from: self.name().into(),
521 offsets,
522 server_timings: vec![],
523 timings,
524 total_time,
525 };
526 client_request.reply_final(&msg)?
527 },
528
529 "getSecurityInfo" => {
530 let security_info = &*self.security_info.borrow();
531
532 let msg = GetSecurityInfoReply {
533 from: self.name().into(),
534 security_info: security_info.into(),
535 };
536 client_request.reply_final(&msg)?
537 },
538
539 _ => return Err(ActorError::UnrecognizedPacketType),
540 };
541 Ok(())
542 }
543}
544
545impl NetworkEventActor {
546 pub fn register(
547 registry: &ActorRegistry,
548 resource_id: u64,
549 browsing_context_name: String,
550 ) -> Arc<Self> {
551 let name = new_actor_name::<Self>();
552 let actor = NetworkEventActor {
553 name,
554 resource_id,
555 browsing_context_name,
556 ..Default::default()
557 };
558 registry.register::<Self>(actor)
559 }
560
561 pub fn add_request(&self, request: HttpRequest) {
562 *self.request.borrow_mut() = Some(NetworkEventRequest {
563 offsets: Default::default(),
565 timings: Timings {
566 connect: request.connect_time.as_millis() as usize,
567 send: request.send_time.as_millis() as usize,
568 ..Default::default()
569 },
570 total_time: request.connect_time + request.send_time,
571 request,
572 });
573 }
574
575 pub fn add_response(&self, response: HttpResponse) {
576 if response.body.is_none() {
577 return;
578 }
579 *self.response.borrow_mut() = Some(NetworkEventResponse {
580 cache_details: CacheDetails {
581 from_cache: response.from_cache,
582 from_service_worker: false,
583 },
584 response,
585 });
586 }
587
588 pub fn add_security_info(&self, security_info: Option<TlsSecurityInfo>) {
589 *self.security_info.borrow_mut() = security_info.unwrap_or_default();
590 }
591
592 fn request_fields(&self) -> Option<RequestFields> {
593 let request = self.request.borrow();
594 let request = request.as_ref()?;
595 let url = request.request.url.as_url();
596 let cookies = get_cookies_from_headers(&request.request.headers, &request.request.url);
597
598 Some(RequestFields {
599 event_timings_available: true,
600 remote_address: url.host_str().map(|a| a.into()),
601 remote_port: url.port(),
602 request_cookies_available: !cookies.is_empty(),
603 request_headers_available: !request.request.headers.is_empty(),
604 total_time: request.total_time.as_secs_f64(),
605 })
606 }
607
608 fn response_fields(&self) -> Option<ResponseFields> {
609 let response = self.response.borrow();
610 let response = response.as_ref()?;
611 let url = self.request.borrow().as_ref()?.request.url.clone();
612 let headers = response.response.headers.as_ref();
613 let cookies = headers.map(|headers| get_cookies_from_headers(headers, &url));
614 let status = &response.response.status;
615
616 Some(ResponseFields {
617 cache_details: response.cache_details.clone(),
618 response_content_available: response
619 .response
620 .body
621 .as_ref()
622 .is_some_and(|body| !body.is_empty()),
623 response_cookies_available: cookies.is_some(),
624 response_headers_available: headers.is_some(),
625 response_start_available: true,
626 status: status.code().to_string(),
627 status_text: String::from_utf8_lossy(status.message()).to_string(),
628 })
629 }
630
631 fn security_fields(&self) -> SecurityFields {
632 let security_info = self.security_info.borrow();
633
634 SecurityFields {
635 security_state: security_info.state.to_string(),
636 security_info_available: true,
637 }
638 }
639
640 pub fn resource_updates(&self, registry: &ActorRegistry) -> NetworkEventResource {
641 let browsing_context_actor =
642 registry.find::<BrowsingContextActor>(&self.browsing_context_name);
643
644 NetworkEventResource {
645 resource_id: self.resource_id,
646 resource_updates: ResourceUpdates {
647 http_version: "HTTP/1.1".into(),
649 request: self.request_fields(),
650 response: self.response_fields(),
651 security: self.security_fields(),
652 },
653 browsing_context_id: browsing_context_actor.browsing_context_id.value(),
654 inner_window_id: 0,
655 }
656 }
657}
658
659fn get_cookies_from_headers(headers: &HeaderMap, url: &ServoUrl) -> Vec<CookieWrapper> {
660 headers
661 .get_all("set-cookie")
662 .iter()
663 .filter_map(|cookie| {
664 let cookie_str = std::str::from_utf8(cookie.as_bytes()).ok()?;
665 ServoCookie::from_cookie_string(cookie_str, url, CookieSource::HTTP)
666 })
667 .map(|cookie| {
668 let cookie = &cookie.cookie;
669 CookieWrapper {
670 name: cookie.name().into(),
671 value: cookie.value().into(),
672 path: cookie.path().map(|p| p.into()),
673 domain: cookie.domain().map(|d| d.into()),
674 expires: cookie.expires().map(|e| format!("{e:?}")),
675 http_only: cookie.http_only(),
676 secure: cookie.secure(),
677 same_site: cookie.same_site().map(|s| s.to_string()),
678 }
679 })
680 .collect()
681}
682
683fn get_header_list(headers: &HeaderMap) -> Vec<HeaderWrapper> {
684 headers
685 .iter()
686 .map(|(name, value)| HeaderWrapper {
687 name: name.as_str().into(),
688 value: value.to_str().unwrap_or_default().into(),
689 })
690 .collect()
691}
692
693fn get_raw_headers(headers: &[HeaderWrapper]) -> String {
694 headers
695 .iter()
696 .map(|header| format!("{}:{}", header.name, header.value))
697 .collect::<Vec<_>>()
698 .join("\r\n")
699}
700
701impl ActorEncode<NetworkEventMsg> for NetworkEventActor {
702 fn encode(&self, registry: &ActorRegistry) -> NetworkEventMsg {
703 let request = self.request.borrow();
704 let request = &request.as_ref().expect("There should be a request").request;
705
706 let started_datetime_rfc3339 = match Local.timestamp_millis_opt(
707 request
708 .started_date_time
709 .duration_since(UNIX_EPOCH)
710 .unwrap_or_default()
711 .as_millis() as i64,
712 ) {
713 LocalResult::None => String::new(),
714 LocalResult::Single(date_time) => date_time.to_rfc3339(),
715 LocalResult::Ambiguous(date_time, _) => date_time.to_rfc3339(),
716 };
717
718 let browsing_context_actor =
719 registry.find::<BrowsingContextActor>(&self.browsing_context_name);
720
721 NetworkEventMsg {
722 actor: self.name().into(),
723 browsing_context_id: browsing_context_actor.browsing_context_id.value(),
724 cause: Cause {
725 type_: request.destination.as_str().to_string(),
726 loading_document_uri: None, },
728 is_xhr: request.is_xhr,
729 method: format!("{}", request.method),
730 private: false,
731 resource_id: self.resource_id,
732 started_date_time: started_datetime_rfc3339,
733 time_stamp: request.time_stamp,
734 url: request.url.to_string(),
735 }
736 }
737}