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