devtools/actors/
network_event.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Liberally derived from the [Firefox JS implementation](http://mxr.mozilla.org/mozilla-central/source/toolkit/devtools/server/actors/webconsole.js).
6//! Handles interaction with the remote web console on network events (HTTP requests, responses) in Servo.
7
8use 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                    // FIXME: what happens when there are no response headers?
322                    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                        // Queue a LongStringActor for this body
362                        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                // TODO: This is a fake timings msg
399                let timings_obj = self.event_timing.clone().unwrap_or_default();
400                // Might use the one on self
401                let total = timings_obj.connect + timings_obj.send;
402                // TODO: Send the correct values for all these fields.
403                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                // TODO: Send the correct values for securityInfo.
412                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        // TODO: Send the correct values for startedDateTime, isXHR, private
487
488        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, // Set if available
511            },
512        }
513    }
514
515    pub fn response_start(response: &DevtoolsHttpResponse) -> ResponseStartMsg {
516        // TODO: Send the correct values for all these fields.
517        let h_size = response.headers.as_ref().map(|h| h.len()).unwrap_or(0);
518        let status = &response.status;
519
520        // TODO: Send the correct values for remoteAddress and remotePort and http_version
521        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        // TODO: Set the correct values for these fields
716        NetworkEventResource {
717            resource_id: self.resource_id,
718            resource_updates,
719            browsing_context_id: 0,
720            inner_window_id: 0,
721        }
722    }
723}