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 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                    // FIXME: what happens when there are no response headers?
330                    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                        // Queue a LongStringActor for this body
370                        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                // TODO: This is a fake timings msg
407                let timings_obj = self.event_timing.clone().unwrap_or_default();
408                // Might use the one on self
409                let total = timings_obj.connect + timings_obj.send;
410                // TODO: Send the correct values for all these fields.
411                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                // TODO: Send the correct values for securityInfo.
420                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        // TODO: Send the correct values for startedDateTime, isXHR, private
497
498        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, // Set if available
521            },
522        }
523    }
524
525    pub fn response_start(response: &DevtoolsHttpResponse) -> ResponseStartMsg {
526        // TODO: Send the correct values for all these fields.
527        let h_size = response.headers.as_ref().map(|h| h.len()).unwrap_or(0);
528        let status = &response.status;
529
530        // TODO: Send the correct values for remoteAddress and remotePort and http_version
531        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        // TODO: Set the correct values for these fields
734        NetworkEventResource {
735            resource_id: self.resource_id,
736            resource_updates,
737            browsing_context_id: 0,
738            inner_window_id: 0,
739        }
740    }
741}