1use std::time::{Duration, UNIX_EPOCH};
9
10use atomic_refcell::AtomicRefCell;
11use base64::engine::Engine;
12use base64::engine::general_purpose::STANDARD;
13use chrono::{Local, LocalResult, TimeZone};
14use devtools_traits::{HttpRequest, HttpResponse};
15use headers::{ContentLength, HeaderMapExt};
16use http::HeaderMap;
17use malloc_size_of_derive::MallocSizeOf;
18use net::cookie::ServoCookie;
19use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
20use net_traits::{CookieSource, TlsSecurityInfo};
21use serde::Serialize;
22use serde_json::{Map, Value};
23use servo_url::ServoUrl;
24
25use crate::StreamId;
26use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
27use crate::actors::browsing_context::BrowsingContextActor;
28use crate::actors::long_string::LongStringActor;
29use crate::actors::watcher::WatcherActor;
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 watcher_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.protocol_version.clone(),
297 cipher_suite: info.cipher_suite.clone(),
298 kea_group_name: info.kea_group_name.clone(),
299 signature_scheme_name: info.signature_scheme_name.clone(),
300 alpn_protocol: info.alpn_protocol.clone(),
301 certificate_transparency: info
302 .certificate_transparency
303 .clone()
304 .or_else(|| Some("unknown".to_string())),
305 hsts: info.hsts,
306 hpkp: info.hpkp,
307 used_ech: info.used_ech,
308 used_delegated_credentials: info.used_delegated_credentials,
309 used_ocsp: info.used_ocsp,
310 used_private_dns: info.used_private_dns,
311 ..Default::default()
312 }
313 }
314}
315
316#[derive(MallocSizeOf)]
317struct NetworkEventRequest {
318 offsets: Timings,
319 timings: Timings,
320 request: HttpRequest,
321 total_time: Duration,
322}
323
324#[derive(MallocSizeOf)]
325struct NetworkEventResponse {
326 cache_details: CacheDetails,
327 response: HttpResponse,
328}
329
330#[derive(Serialize)]
331pub(crate) struct CookieWrapper {
332 name: String,
333 value: String,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 path: Option<String>,
336 #[serde(skip_serializing_if = "Option::is_none")]
337 domain: Option<String>,
338 #[serde(skip_serializing_if = "Option::is_none")]
339 expires: Option<String>,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 http_only: Option<bool>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 secure: Option<bool>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 same_site: Option<String>,
346}
347
348#[derive(Serialize)]
349struct HeaderWrapper {
350 name: String,
351 value: String,
352}
353
354impl Actor for NetworkEventActor {
355 fn name(&self) -> String {
356 self.name.clone()
357 }
358
359 fn handle_message(
360 &self,
361 client_request: ClientRequest,
362 registry: &ActorRegistry,
363 msg_type: &str,
364 _msg: &Map<String, Value>,
365 _id: StreamId,
366 ) -> Result<(), ActorError> {
367 match msg_type {
368 "getRequestHeaders" => {
369 let request = self.request.borrow();
370 let request = request.as_ref().ok_or(ActorError::Internal)?;
371
372 let headers = get_header_list(&request.request.headers);
373 let raw_headers = get_raw_headers(&headers);
374
375 let msg = GetRequestHeadersReply {
376 from: self.name(),
377 headers,
378 header_size: raw_headers.len(),
379 raw_headers,
380 };
381 client_request.reply_final(&msg)?
382 },
383
384 "getRequestCookies" => {
385 let request = self.request.borrow();
386 let request = request.as_ref().ok_or(ActorError::Internal)?;
387
388 let msg = GetCookiesReply {
389 from: self.name(),
390 cookies: get_cookies_from_headers(
391 &request.request.headers,
392 &request.request.url,
393 ),
394 };
395
396 client_request.reply_final(&msg)?
397 },
398
399 "getRequestPostData" => {
400 let request = self.request.borrow();
401 let request = request.as_ref().ok_or(ActorError::Internal)?;
402
403 let msg = GetRequestPostDataReply {
404 from: self.name(),
405 post_data: request.request.body.as_ref().map(|b| b.0.clone()),
406 post_data_discarded: request.request.body.is_none(),
407 };
408 client_request.reply_final(&msg)?
409 },
410
411 "getResponseHeaders" => {
412 let response = self.response.borrow();
413 let response = response.as_ref().ok_or(ActorError::Internal)?;
414
415 let list = response
416 .response
417 .headers
418 .as_ref()
419 .map(get_header_list)
420 .unwrap_or_default();
421 let raw_headers = get_raw_headers(&list);
422
423 let msg = GetResponseHeadersReply {
424 from: self.name(),
425 headers: list,
426 header_size: raw_headers.len(),
427 raw_headers,
428 };
429 client_request.reply_final(&msg)?;
430 },
431
432 "getResponseCookies" => {
433 let request = self.request.borrow();
434 let request = request.as_ref().ok_or(ActorError::Internal)?;
435 let response = self.response.borrow();
436 let response = response.as_ref().ok_or(ActorError::Internal)?;
437
438 let msg = GetCookiesReply {
439 from: self.name(),
440 cookies: get_cookies_from_headers(
441 response
442 .response
443 .headers
444 .as_ref()
445 .ok_or(ActorError::Internal)?,
446 &request.request.url,
447 ),
448 };
449 client_request.reply_final(&msg)?
450 },
451
452 "getResponseContent" => {
453 let response = self.response.borrow();
454 let response = response.as_ref().ok_or(ActorError::Internal)?;
455
456 let headers = response.response.headers.as_ref();
457 let list = headers.map(get_header_list).unwrap_or_default();
458 let raw_headers = get_raw_headers(&list);
459
460 let mime_type = headers
461 .and_then(extract_mime_type_as_dataurl_mime)
462 .map(|url| url.to_string());
463 let transferred_size = headers
464 .and_then(|header| header.typed_get::<ContentLength>())
465 .map(|content_length_header| content_length_header.0);
466
467 let content = response.response.body.as_ref().map(|body| {
468 let (encoding, text) = if mime_type.is_some() {
469 let body_string = String::from_utf8_lossy(body).to_string();
471 let long_string_name = LongStringActor::register(registry, body_string);
472 let value = registry
473 .find::<LongStringActor>(&long_string_name)
474 .long_string_obj();
475 (None, serde_json::to_value(value).unwrap())
476 } else {
477 let b64 = STANDARD.encode(&body.0);
478 (Some("base64".into()), serde_json::to_value(b64).unwrap())
479 };
480 let is_content_encoded = encoding.is_some();
481
482 ResponseContent {
483 body_size: body.len(),
484 content_charset: "".into(),
485 decoded_body_size: body.len(),
486 encoding,
487 headers_size: raw_headers.len(),
488 is_content_encoded,
489 mime_type,
490 size: body.len(),
491 text,
492 transferred_size,
493 }
494 });
495
496 let msg = GetResponseContentReply {
497 from: self.name(),
498 content,
499 content_discarded: response.response.body.is_none(),
500 };
501 client_request.reply_final(&msg)?
502 },
503
504 "getEventTimings" => {
505 let request = self.request.borrow();
506 let request = request.as_ref().ok_or(ActorError::Internal)?;
507
508 let offsets = request.offsets.clone();
509 let timings = request.timings.clone();
510 let total_time = timings.total();
511
512 let msg = GetEventTimingsReply {
513 from: self.name(),
514 offsets,
515 server_timings: vec![],
516 timings,
517 total_time,
518 };
519 client_request.reply_final(&msg)?
520 },
521
522 "getSecurityInfo" => {
523 let security_info = &*self.security_info.borrow();
524
525 let msg = GetSecurityInfoReply {
526 from: self.name(),
527 security_info: security_info.into(),
528 };
529 client_request.reply_final(&msg)?
530 },
531
532 _ => return Err(ActorError::UnrecognizedPacketType),
533 };
534 Ok(())
535 }
536}
537
538impl NetworkEventActor {
539 pub fn register(registry: &ActorRegistry, resource_id: u64, watcher_name: String) -> String {
540 let name = registry.new_name::<Self>();
541 let actor = NetworkEventActor {
542 name: name.clone(),
543 resource_id,
544 watcher_name,
545 ..Default::default()
546 };
547 registry.register::<Self>(actor);
548 name
549 }
550
551 pub fn add_request(&self, request: HttpRequest) {
552 *self.request.borrow_mut() = Some(NetworkEventRequest {
553 offsets: Default::default(),
555 timings: Timings {
556 connect: request.connect_time.as_millis() as usize,
557 send: request.send_time.as_millis() as usize,
558 ..Default::default()
559 },
560 total_time: request.connect_time + request.send_time,
561 request,
562 });
563 }
564
565 pub fn add_response(&self, response: HttpResponse) {
566 if response.body.is_none() {
567 return;
568 }
569 *self.response.borrow_mut() = Some(NetworkEventResponse {
570 cache_details: CacheDetails {
571 from_cache: response.from_cache,
572 from_service_worker: false,
573 },
574 response,
575 });
576 }
577
578 pub fn add_security_info(&self, security_info: Option<TlsSecurityInfo>) {
579 *self.security_info.borrow_mut() = security_info.unwrap_or_default();
580 }
581
582 fn request_fields(&self) -> Option<RequestFields> {
583 let request = self.request.borrow();
584 let request = request.as_ref()?;
585 let url = request.request.url.as_url();
586 let cookies = get_cookies_from_headers(&request.request.headers, &request.request.url);
587
588 Some(RequestFields {
589 event_timings_available: true,
590 remote_address: url.host_str().map(|a| a.into()),
591 remote_port: url.port(),
592 request_cookies_available: !cookies.is_empty(),
593 request_headers_available: !request.request.headers.is_empty(),
594 total_time: request.total_time.as_secs_f64(),
595 })
596 }
597
598 fn response_fields(&self) -> Option<ResponseFields> {
599 let response = self.response.borrow();
600 let response = response.as_ref()?;
601 let url = self.request.borrow().as_ref()?.request.url.clone();
602 let headers = response.response.headers.as_ref();
603 let cookies = headers.map(|headers| get_cookies_from_headers(headers, &url));
604 let status = &response.response.status;
605
606 Some(ResponseFields {
607 cache_details: response.cache_details.clone(),
608 response_content_available: response
609 .response
610 .body
611 .as_ref()
612 .is_some_and(|body| !body.is_empty()),
613 response_cookies_available: cookies.is_some(),
614 response_headers_available: headers.is_some(),
615 response_start_available: true,
616 status: status.code().to_string(),
617 status_text: String::from_utf8_lossy(status.message()).to_string(),
618 })
619 }
620
621 fn security_fields(&self) -> SecurityFields {
622 let security_info = self.security_info.borrow();
623
624 SecurityFields {
625 security_state: security_info.state.to_string(),
626 security_info_available: true,
627 }
628 }
629
630 pub fn resource_updates(&self, registry: &ActorRegistry) -> NetworkEventResource {
631 let watcher_actor = registry.find::<WatcherActor>(&self.watcher_name);
632 let browsing_context_actor =
633 registry.find::<BrowsingContextActor>(&watcher_actor.browsing_context_name);
634
635 NetworkEventResource {
636 resource_id: self.resource_id,
637 resource_updates: ResourceUpdates {
638 http_version: "HTTP/1.1".into(),
640 request: self.request_fields(),
641 response: self.response_fields(),
642 security: self.security_fields(),
643 },
644 browsing_context_id: browsing_context_actor.browsing_context_id.value(),
645 inner_window_id: 0,
646 }
647 }
648}
649
650fn get_cookies_from_headers(headers: &HeaderMap, url: &ServoUrl) -> Vec<CookieWrapper> {
651 headers
652 .get_all("set-cookie")
653 .iter()
654 .filter_map(|cookie| {
655 let cookie_str = std::str::from_utf8(cookie.as_bytes()).ok()?;
656 ServoCookie::from_cookie_string(cookie_str, url, CookieSource::HTTP)
657 })
658 .map(|cookie| {
659 let cookie = &cookie.cookie;
660 CookieWrapper {
661 name: cookie.name().into(),
662 value: cookie.value().into(),
663 path: cookie.path().map(|p| p.into()),
664 domain: cookie.domain().map(|d| d.into()),
665 expires: cookie.expires().map(|e| format!("{e:?}")),
666 http_only: cookie.http_only(),
667 secure: cookie.secure(),
668 same_site: cookie.same_site().map(|s| s.to_string()),
669 }
670 })
671 .collect()
672}
673
674fn get_header_list(headers: &HeaderMap) -> Vec<HeaderWrapper> {
675 headers
676 .iter()
677 .map(|(name, value)| HeaderWrapper {
678 name: name.as_str().into(),
679 value: value.to_str().unwrap_or_default().into(),
680 })
681 .collect()
682}
683
684fn get_raw_headers(headers: &[HeaderWrapper]) -> String {
685 headers
686 .iter()
687 .map(|header| format!("{}:{}", header.name, header.value))
688 .collect::<Vec<_>>()
689 .join("\r\n")
690}
691
692impl ActorEncode<NetworkEventMsg> for NetworkEventActor {
693 fn encode(&self, registry: &ActorRegistry) -> NetworkEventMsg {
694 let request = self.request.borrow();
695 let request = &request.as_ref().expect("There should be a request").request;
696
697 let started_datetime_rfc3339 = match Local.timestamp_millis_opt(
698 request
699 .started_date_time
700 .duration_since(UNIX_EPOCH)
701 .unwrap_or_default()
702 .as_millis() as i64,
703 ) {
704 LocalResult::None => "".to_owned(),
705 LocalResult::Single(date_time) => date_time.to_rfc3339(),
706 LocalResult::Ambiguous(date_time, _) => date_time.to_rfc3339(),
707 };
708
709 let watcher_actor = registry.find::<WatcherActor>(&self.watcher_name);
710 let browsing_context_actor =
711 registry.find::<BrowsingContextActor>(&watcher_actor.browsing_context_name);
712
713 NetworkEventMsg {
714 actor: self.name(),
715 browsing_context_id: browsing_context_actor.browsing_context_id.value(),
716 cause: Cause {
717 type_: request.destination.as_str().to_string(),
718 loading_document_uri: None, },
720 is_xhr: request.is_xhr,
721 method: format!("{}", request.method),
722 private: false,
723 resource_id: self.resource_id,
724 started_date_time: started_datetime_rfc3339,
725 time_stamp: request.time_stamp,
726 url: request.url.to_string(),
727 }
728 }
729}