1use std::time::{Duration, SystemTime, UNIX_EPOCH};
9
10use base64::engine::Engine;
11use base64::engine::general_purpose::STANDARD;
12use chrono::{Local, LocalResult, TimeZone};
13use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse};
14use headers::{ContentLength, ContentType, Cookie, HeaderMapExt};
15use http::{HeaderMap, Method};
16use net::cookie::ServoCookie;
17use net_traits::CookieSource;
18use net_traits::request::Destination as RequestDestination;
19use serde::Serialize;
20use serde_json::{Map, Value};
21use servo_url::ServoUrl;
22
23use crate::StreamId;
24use crate::actor::{Actor, ActorError, ActorRegistry};
25use crate::actors::long_string::LongStringActor;
26use crate::network_handler::Cause;
27use crate::protocol::ClientRequest;
28
29pub struct NetworkEventActor {
30 pub name: String,
31 pub resource_id: u64,
32 pub is_xhr: bool,
33 pub request_url: String,
34 pub request_method: Method,
35 pub request_started: SystemTime,
36 pub request_time_stamp: i64,
37 pub request_destination: RequestDestination,
38 pub request_headers_raw: Option<HeaderMap>,
39 pub request_body: Option<Vec<u8>>,
40 pub request_cookies: Option<RequestCookiesMsg>,
41 pub request_headers: Option<RequestHeadersMsg>,
42 pub response_headers_raw: Option<HeaderMap>,
43 pub response_body: Option<Vec<u8>>,
44 pub response_content: Option<ResponseContentMsg>,
45 pub response_start: Option<ResponseStartMsg>,
46 pub response_cookies: Option<ResponseCookiesMsg>,
47 pub response_headers: Option<ResponseHeadersMsg>,
48 pub cache_details: Option<CacheDetails>,
49 pub total_time: Duration,
50 pub security_state: String,
51 pub event_timing: Option<Timings>,
52 pub watcher_name: String,
53}
54
55#[derive(Clone, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct NetworkEventResource {
58 pub resource_id: u64,
59 pub resource_updates: Map<String, Value>,
60 pub browsing_context_id: u64,
61 pub inner_window_id: u64,
62}
63
64#[derive(Clone, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct EventActor {
67 pub actor: String,
68 pub resource_id: u64,
69 pub url: String,
70 pub method: String,
71 pub started_date_time: String,
72 pub time_stamp: i64,
73 #[serde(rename = "isXHR")]
74 pub is_xhr: bool,
75 pub private: bool,
76 pub cause: Cause,
77}
78
79#[derive(Serialize)]
80pub struct ResponseCookiesMsg {
81 pub cookies: Vec<ResponseCookieObj>,
82}
83
84#[derive(Serialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ResponseStartMsg {
87 pub http_version: String,
88 pub remote_address: String,
89 pub remote_port: u32,
90 pub status: String,
91 pub status_text: String,
92 pub headers_size: usize,
93 pub discard_response_body: bool,
94}
95
96#[derive(Serialize)]
97#[serde(rename_all = "camelCase")]
98pub struct ResponseContentMsg {
99 pub mime_type: String,
100 pub content_size: u32,
101 pub transferred_size: u32,
102 pub discard_response_body: bool,
103}
104
105#[derive(Serialize)]
106#[serde(rename_all = "camelCase")]
107pub struct ResponseHeadersMsg {
108 pub headers: usize,
109 pub headers_size: usize,
110}
111
112#[derive(Serialize)]
113#[serde(rename_all = "camelCase")]
114pub struct CacheDetails {
115 from_cache: bool,
116 from_service_worker: bool,
117}
118
119#[derive(Serialize)]
120pub struct RequestCookiesMsg {
121 pub cookies: Vec<RequestCookieObj>,
122}
123
124#[derive(Serialize)]
125#[serde(rename_all = "camelCase")]
126pub struct RequestHeadersMsg {
127 headers: usize,
128 headers_size: usize,
129}
130
131#[derive(Serialize)]
132#[serde(rename_all = "camelCase")]
133struct GetRequestHeadersReply {
134 from: String,
135 headers: Vec<Header>,
136 header_size: usize,
137 raw_headers: String,
138}
139
140#[derive(Serialize)]
141struct Header {
142 name: String,
143 value: String,
144}
145
146#[derive(Serialize)]
147#[serde(rename_all = "camelCase")]
148struct GetResponseHeadersReply {
149 from: String,
150 headers: Vec<Header>,
151 header_size: usize,
152 raw_headers: String,
153}
154
155#[derive(Serialize)]
156#[serde(rename_all = "camelCase")]
157struct GetResponseContentReply {
158 from: String,
159 content: Option<ResponseContentObj>,
160 content_discarded: bool,
161}
162
163#[derive(Serialize)]
164#[serde(rename_all = "camelCase")]
165struct GetRequestPostDataReply {
166 from: String,
167 post_data: Option<Vec<u8>>,
168 post_data_discarded: bool,
169}
170
171#[derive(Serialize)]
172struct GetRequestCookiesReply {
173 from: String,
174 cookies: Vec<RequestCookieObj>,
175}
176
177#[derive(Serialize)]
178struct GetResponseCookiesReply {
179 from: String,
180 cookies: Vec<ResponseCookieObj>,
181}
182#[derive(Clone, Serialize)]
183pub struct ResponseCookieObj {
184 pub name: String,
185 pub value: String,
186 pub path: Option<String>,
187 pub domain: Option<String>,
188 pub expires: Option<String>,
189 #[serde(rename = "httpOnly")]
190 pub http_only: Option<bool>,
191 pub secure: Option<bool>,
192 #[serde(rename = "sameSite")]
193 pub same_site: Option<String>,
194}
195
196#[derive(Serialize)]
197#[serde(rename_all = "camelCase")]
198struct ResponseContentObj {
199 mime_type: String,
200 text: Value,
201 body_size: usize,
202 decoded_body_size: usize,
203 size: usize,
204 headers_size: usize,
205 transferred_size: usize,
206 #[serde(skip_serializing_if = "Option::is_none")]
207 encoding: Option<String>,
208}
209
210#[derive(Clone, Serialize)]
211pub struct RequestCookieObj {
212 pub name: String,
213 pub value: String,
214}
215
216#[derive(Clone, Default, Serialize)]
217pub struct Timings {
218 blocked: u32,
219 dns: u32,
220 connect: u64,
221 send: u64,
222 wait: u32,
223 receive: u32,
224}
225
226#[derive(Serialize)]
227#[serde(rename_all = "camelCase")]
228struct GetEventTimingsReply {
229 from: String,
230 timings: Timings,
231 total_time: u64,
232}
233
234#[derive(Serialize)]
235struct SecurityInfo {
236 state: String,
237}
238
239#[derive(Serialize)]
240#[serde(rename_all = "camelCase")]
241struct GetSecurityInfoReply {
242 from: String,
243 security_info: SecurityInfo,
244}
245
246impl Actor for NetworkEventActor {
247 fn name(&self) -> String {
248 self.name.clone()
249 }
250
251 fn handle_message(
252 &self,
253 request: ClientRequest,
254 registry: &ActorRegistry,
255 msg_type: &str,
256 _msg: &Map<String, Value>,
257 _id: StreamId,
258 ) -> Result<(), ActorError> {
259 match msg_type {
260 "getRequestHeaders" => {
261 let mut headers = Vec::new();
262 let mut raw_headers_string = "".to_owned();
263 let mut headers_size = 0;
264 if let Some(ref headers_map) = self.request_headers_raw {
265 for (name, value) in headers_map.iter() {
266 let value = &value.to_str().unwrap().to_string();
267 raw_headers_string =
268 raw_headers_string + name.as_str() + ":" + value + "\r\n";
269 headers_size += name.as_str().len() + value.len();
270 headers.push(Header {
271 name: name.as_str().to_owned(),
272 value: value.to_owned(),
273 });
274 }
275 }
276
277 let msg = GetRequestHeadersReply {
278 from: self.name(),
279 headers,
280 header_size: headers_size,
281 raw_headers: raw_headers_string,
282 };
283 request.reply_final(&msg)?
284 },
285 "getRequestCookies" => {
286 let cookies = self
287 .request_cookies
288 .as_ref()
289 .map(|msg| msg.cookies.clone())
290 .unwrap_or_default();
291 let msg = GetRequestCookiesReply {
292 from: self.name(),
293 cookies,
294 };
295 request.reply_final(&msg)?
296 },
297 "getRequestPostData" => {
298 let msg = GetRequestPostDataReply {
299 from: self.name(),
300 post_data: self.request_body.clone(),
301 post_data_discarded: self.request_body.is_none(),
302 };
303 request.reply_final(&msg)?
304 },
305 "getResponseHeaders" => {
306 if let Some(ref response_headers) = self.response_headers_raw {
307 let mut headers = vec![];
308 let mut raw_headers_string = "".to_owned();
309 let mut headers_size = 0;
310 for (name, value) in response_headers.iter() {
311 headers.push(Header {
312 name: name.as_str().to_owned(),
313 value: value.to_str().unwrap().to_owned(),
314 });
315 headers_size += name.as_str().len() + value.len();
316 raw_headers_string.push_str(name.as_str());
317 raw_headers_string.push(':');
318 raw_headers_string.push_str(value.to_str().unwrap());
319 raw_headers_string.push_str("\r\n");
320 }
321 let msg = GetResponseHeadersReply {
322 from: self.name(),
323 headers,
324 header_size: headers_size,
325 raw_headers: raw_headers_string,
326 };
327 request.reply_final(&msg)?;
328 } else {
329 return Err(ActorError::Internal);
331 }
332 },
333 "getResponseCookies" => {
334 let cookies = self
335 .response_cookies
336 .as_ref()
337 .map(|msg| msg.cookies.clone())
338 .unwrap_or_default();
339 let msg = GetResponseCookiesReply {
340 from: self.name(),
341 cookies,
342 };
343 request.reply_final(&msg)?
344 },
345 "getResponseContent" => {
346 let content_obj = self.response_body.as_ref().map(|body| {
347 let mime_type = self
348 .response_content
349 .as_ref()
350 .map(|c| c.mime_type.clone())
351 .unwrap_or_default();
352 let headers_size = self
353 .response_headers
354 .as_ref()
355 .map(|h| h.headers_size)
356 .unwrap_or(0);
357 let transferred_size = self
358 .response_content
359 .as_ref()
360 .map(|c| c.transferred_size as usize)
361 .unwrap_or(0);
362 let body_size = body.len();
363 let decoded_body_size = body.len();
364 let size = body.len();
365
366 if Self::is_text_mime(&mime_type) {
367 let full_str = String::from_utf8_lossy(body).to_string();
368
369 let long_string_actor = LongStringActor::new(registry, full_str);
371 let long_string_obj = long_string_actor.long_string_obj();
372 registry.register_later(Box::new(long_string_actor));
373
374 ResponseContentObj {
375 mime_type,
376 text: serde_json::to_value(long_string_obj).unwrap(),
377 body_size,
378 decoded_body_size,
379 size,
380 headers_size,
381 transferred_size,
382 encoding: None,
383 }
384 } else {
385 let b64 = STANDARD.encode(body);
386 ResponseContentObj {
387 mime_type,
388 text: serde_json::to_value(b64).unwrap(),
389 body_size,
390 decoded_body_size,
391 size,
392 headers_size,
393 transferred_size,
394 encoding: Some("base64".to_string()),
395 }
396 }
397 });
398 let msg = GetResponseContentReply {
399 from: self.name(),
400 content: content_obj,
401 content_discarded: self.response_body.is_none(),
402 };
403 request.reply_final(&msg)?
404 },
405 "getEventTimings" => {
406 let timings_obj = self.event_timing.clone().unwrap_or_default();
408 let total = timings_obj.connect + timings_obj.send;
410 let msg = GetEventTimingsReply {
412 from: self.name(),
413 timings: timings_obj,
414 total_time: total,
415 };
416 request.reply_final(&msg)?
417 },
418 "getSecurityInfo" => {
419 let msg = GetSecurityInfoReply {
421 from: self.name(),
422 security_info: SecurityInfo {
423 state: "insecure".to_owned(),
424 },
425 };
426 request.reply_final(&msg)?
427 },
428 _ => return Err(ActorError::UnrecognizedPacketType),
429 };
430 Ok(())
431 }
432}
433
434impl NetworkEventActor {
435 pub fn new(name: String, resource_id: u64, watcher_name: String) -> NetworkEventActor {
436 NetworkEventActor {
437 name,
438 resource_id,
439 is_xhr: false,
440 request_url: String::new(),
441 request_method: Method::GET,
442 request_started: SystemTime::now(),
443 request_time_stamp: SystemTime::now()
444 .duration_since(UNIX_EPOCH)
445 .unwrap_or_default()
446 .as_secs() as i64,
447 request_destination: RequestDestination::None,
448 request_headers_raw: None,
449 request_body: None,
450 request_cookies: None,
451 request_headers: None,
452 response_headers_raw: None,
453 response_body: None,
454 response_content: None,
455 response_start: None,
456 response_cookies: None,
457 response_headers: None,
458 cache_details: None,
459 total_time: Duration::ZERO,
460 security_state: "insecure".to_owned(),
461 event_timing: None,
462 watcher_name,
463 }
464 }
465
466 pub fn add_request(&mut self, request: DevtoolsHttpRequest) {
467 self.is_xhr = request.is_xhr;
468 self.request_cookies = Self::request_cookies(&request);
469 self.request_headers = Some(Self::request_headers(&request));
470 self.total_time = Self::total_time(&request);
471 self.event_timing = Some(Self::event_timing(&request));
472 self.request_url = request.url.to_string();
473 self.request_method = request.method;
474 self.request_started = request.started_date_time;
475 self.request_time_stamp = request.time_stamp;
476 self.request_destination = request.destination;
477 self.request_body = request.body.clone();
478 self.request_headers_raw = Some(request.headers.clone());
479 }
480
481 pub fn add_response(&mut self, response: DevtoolsHttpResponse) {
482 self.response_headers = Some(Self::response_headers(&response));
483 self.response_cookies = ServoUrl::parse(&self.request_url)
484 .ok()
485 .as_ref()
486 .and_then(|url| Self::response_cookies(&response, url));
487 self.response_start = Some(Self::response_start(&response));
488 if let Some(response_content) = Self::response_content(self, &response) {
489 self.response_content = Some(response_content);
490 }
491 self.response_headers_raw = response.headers.clone();
492 self.cache_details = Some(Self::cache_details(&response));
493 }
494
495 pub fn event_actor(&self) -> EventActor {
496 let started_datetime_rfc3339 = match Local.timestamp_millis_opt(
499 self.request_started
500 .duration_since(UNIX_EPOCH)
501 .unwrap_or_default()
502 .as_millis() as i64,
503 ) {
504 LocalResult::None => "".to_owned(),
505 LocalResult::Single(date_time) => date_time.to_rfc3339().to_string(),
506 LocalResult::Ambiguous(date_time, _) => date_time.to_rfc3339().to_string(),
507 };
508
509 EventActor {
510 actor: self.name(),
511 resource_id: self.resource_id,
512 url: self.request_url.clone(),
513 method: format!("{}", self.request_method),
514 started_date_time: started_datetime_rfc3339,
515 time_stamp: self.request_time_stamp,
516 is_xhr: self.is_xhr,
517 private: false,
518 cause: Cause {
519 type_: self.request_destination.as_str().to_string(),
520 loading_document_uri: None, },
522 }
523 }
524
525 pub fn response_start(response: &DevtoolsHttpResponse) -> ResponseStartMsg {
526 let h_size = response.headers.as_ref().map(|h| h.len()).unwrap_or(0);
528 let status = &response.status;
529
530 ResponseStartMsg {
532 http_version: "HTTP/1.1".to_owned(),
533 remote_address: "63.245.217.43".to_owned(),
534 remote_port: 443,
535 status: status.code().to_string(),
536 status_text: String::from_utf8_lossy(status.message()).to_string(),
537 headers_size: h_size,
538 discard_response_body: false,
539 }
540 }
541
542 pub fn response_content(
543 &mut self,
544 response: &DevtoolsHttpResponse,
545 ) -> Option<ResponseContentMsg> {
546 let body = response.body.as_ref()?;
547 self.response_body = Some(body.clone());
548
549 let mime_type = response
550 .headers
551 .as_ref()
552 .and_then(|h| h.typed_get::<ContentType>())
553 .map(|ct| ct.to_string())
554 .unwrap_or_default();
555
556 let transferred_size = response
557 .headers
558 .as_ref()
559 .and_then(|hdrs| hdrs.typed_get::<ContentLength>())
560 .map(|cl| cl.0);
561
562 let content_size = response.body.as_ref().map(|body| body.len() as u64);
563
564 Some(ResponseContentMsg {
565 mime_type,
566 content_size: content_size.unwrap_or(0) as u32,
567 transferred_size: transferred_size.unwrap_or(0) as u32,
568 discard_response_body: false,
569 })
570 }
571
572 pub fn response_cookies(
573 response: &DevtoolsHttpResponse,
574 url: &ServoUrl,
575 ) -> Option<ResponseCookiesMsg> {
576 let headers = response.headers.as_ref()?;
577 let cookies = headers
578 .get_all("set-cookie")
579 .iter()
580 .filter_map(|cookie| {
581 let cookie_str = String::from_utf8(cookie.as_bytes().to_vec()).ok()?;
582 ServoCookie::from_cookie_string(cookie_str, url, CookieSource::HTTP)
583 })
584 .map(|servo_cookie| {
585 let c = &servo_cookie.cookie;
586 ResponseCookieObj {
587 name: c.name().to_string(),
588 value: c.value().to_string(),
589 path: c.path().map(|p| p.to_string()),
590 domain: c.domain().map(|d| d.to_string()),
591 expires: c.expires().map(|dt| format!("{:?}", dt)),
592 http_only: c.http_only(),
593 secure: c.secure(),
594 same_site: c.same_site().map(|s| s.to_string()),
595 }
596 })
597 .collect::<Vec<_>>();
598 Some(ResponseCookiesMsg { cookies })
599 }
600
601 pub fn response_headers(response: &DevtoolsHttpResponse) -> ResponseHeadersMsg {
602 let mut header_size = 0;
603 let mut headers_byte_count = 0;
604 if let Some(ref headers) = response.headers {
605 for (name, value) in headers.iter() {
606 header_size += 1;
607 headers_byte_count += name.as_str().len() + value.len();
608 }
609 }
610 ResponseHeadersMsg {
611 headers: header_size,
612 headers_size: headers_byte_count,
613 }
614 }
615
616 pub fn request_headers(request: &DevtoolsHttpRequest) -> RequestHeadersMsg {
617 let size = request.headers.iter().fold(0, |acc, (name, value)| {
618 acc + name.as_str().len() + value.len()
619 });
620 RequestHeadersMsg {
621 headers: request.headers.len(),
622 headers_size: size,
623 }
624 }
625
626 pub fn request_cookies(request: &DevtoolsHttpRequest) -> Option<RequestCookiesMsg> {
627 let header_value = request.headers.typed_get::<Cookie>()?;
628 let cookies = header_value
629 .iter()
630 .map(|cookie| RequestCookieObj {
631 name: cookie.0.to_string(),
632 value: cookie.1.to_string(),
633 })
634 .collect::<Vec<_>>();
635 Some(RequestCookiesMsg { cookies })
636 }
637
638 pub fn cache_details(response: &DevtoolsHttpResponse) -> CacheDetails {
639 CacheDetails {
640 from_cache: response.from_cache,
641 from_service_worker: false,
642 }
643 }
644
645 pub fn total_time(request: &DevtoolsHttpRequest) -> Duration {
646 request.connect_time + request.send_time
647 }
648
649 pub fn event_timing(request: &DevtoolsHttpRequest) -> Timings {
650 Timings {
651 blocked: 0,
652 dns: 0,
653 connect: request.connect_time.as_millis() as u64,
654 send: request.send_time.as_millis() as u64,
655 wait: 0,
656 receive: 0,
657 }
658 }
659
660 pub fn is_text_mime(mime: &str) -> bool {
661 let lower = mime.to_ascii_lowercase();
662 lower.starts_with("text/") ||
663 lower.contains("json") ||
664 lower.contains("javascript") ||
665 lower.contains("xml") ||
666 lower.contains("csv") ||
667 lower.contains("html")
668 }
669
670 fn insert_serialized_map<T: Serialize>(map: &mut Map<String, Value>, obj: &Option<T>) {
671 if let Some(value) = obj {
672 if let Ok(Value::Object(serialized)) = serde_json::to_value(value) {
673 for (key, val) in serialized {
674 map.insert(key, val);
675 }
676 }
677 }
678 }
679
680 pub fn resource_updates(&self) -> NetworkEventResource {
681 let mut resource_updates = Map::new();
682
683 resource_updates.insert(
684 "requestCookiesAvailable".to_owned(),
685 Value::Bool(self.request_cookies.is_some()),
686 );
687
688 resource_updates.insert(
689 "requestHeadersAvailable".to_owned(),
690 Value::Bool(self.request_headers.is_some()),
691 );
692
693 resource_updates.insert(
694 "responseHeadersAvailable".to_owned(),
695 Value::Bool(self.response_headers.is_some()),
696 );
697 resource_updates.insert(
698 "responseCookiesAvailable".to_owned(),
699 Value::Bool(self.response_cookies.is_some()),
700 );
701 resource_updates.insert(
702 "responseStartAvailable".to_owned(),
703 Value::Bool(self.response_start.is_some()),
704 );
705 resource_updates.insert(
706 "responseContentAvailable".to_owned(),
707 Value::Bool(self.response_content.is_some()),
708 );
709
710 resource_updates.insert(
711 "totalTime".to_string(),
712 Value::from(self.total_time.as_secs_f64()),
713 );
714
715 resource_updates.insert(
716 "securityState".to_string(),
717 Value::String(self.security_state.clone()),
718 );
719 resource_updates.insert(
720 "eventTimingsAvailable".to_owned(),
721 Value::Bool(self.event_timing.is_some()),
722 );
723
724 Self::insert_serialized_map(&mut resource_updates, &self.response_content);
725 Self::insert_serialized_map(&mut resource_updates, &self.response_headers);
726 Self::insert_serialized_map(&mut resource_updates, &self.response_cookies);
727 Self::insert_serialized_map(&mut resource_updates, &self.request_headers);
728 Self::insert_serialized_map(&mut resource_updates, &self.request_cookies);
729 Self::insert_serialized_map(&mut resource_updates, &self.response_start);
730 Self::insert_serialized_map(&mut resource_updates, &self.cache_details);
731 Self::insert_serialized_map(&mut resource_updates, &self.event_timing);
732
733 NetworkEventResource {
735 resource_id: self.resource_id,
736 resource_updates,
737 browsing_context_id: 0,
738 inner_window_id: 0,
739 }
740 }
741}