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, UNIX_EPOCH};
9
10use atomic_refcell::AtomicRefCell;
11use base64::engine::Engine;
12use base64::engine::general_purpose::STANDARD;
13use chrono::{Local, LocalResult, TimeZone};
14use devtools_traits::{HttpRequest, HttpResponse};
15use headers::{ContentLength, HeaderMapExt};
16use http::HeaderMap;
17use malloc_size_of_derive::MallocSizeOf;
18use net::cookie::ServoCookie;
19use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
20use net_traits::{CookieSource, TlsSecurityInfo};
21use serde::Serialize;
22use serde_json::{Map, Value};
23use servo_url::ServoUrl;
24
25use crate::StreamId;
26use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry};
27use crate::actors::browsing_context::BrowsingContextActor;
28use crate::actors::long_string::LongStringActor;
29use crate::actors::watcher::WatcherActor;
30use crate::network_handler::Cause;
31use crate::protocol::ClientRequest;
32
33#[derive(Default, MallocSizeOf)]
34pub(crate) struct NetworkEventActor {
35    name: String,
36    request: AtomicRefCell<Option<NetworkEventRequest>>,
37    resource_id: u64,
38    response: AtomicRefCell<Option<NetworkEventResponse>>,
39    security_info: AtomicRefCell<TlsSecurityInfo>,
40    pub watcher_name: String,
41}
42
43#[derive(Clone, Serialize)]
44#[serde(rename_all = "camelCase")]
45pub(crate) struct NetworkEventResource {
46    #[serde(rename = "browsingContextID")]
47    browsing_context_id: u32,
48    inner_window_id: u64,
49    resource_id: u64,
50    resource_updates: ResourceUpdates,
51}
52
53#[derive(Clone, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub(crate) struct NetworkEventMsg {
56    actor: String,
57    #[serde(rename = "browsingContextID")]
58    browsing_context_id: u32,
59    cause: Cause,
60    #[serde(rename = "isXHR")]
61    is_xhr: bool,
62    method: String,
63    private: bool,
64    resource_id: u64,
65    started_date_time: String,
66    time_stamp: i64,
67    url: String,
68}
69
70#[derive(Serialize)]
71#[serde(rename_all = "camelCase")]
72struct GetRequestHeadersReply {
73    from: String,
74    headers: Vec<HeaderWrapper>,
75    header_size: usize,
76    raw_headers: String,
77}
78
79#[derive(Serialize)]
80struct GetCookiesReply {
81    from: String,
82    cookies: Vec<CookieWrapper>,
83}
84
85#[derive(Serialize)]
86#[serde(rename_all = "camelCase")]
87struct GetRequestPostDataReply {
88    from: String,
89    post_data: Option<Vec<u8>>,
90    post_data_discarded: bool,
91}
92
93#[derive(Serialize)]
94#[serde(rename_all = "camelCase")]
95struct GetResponseHeadersReply {
96    from: String,
97    headers: Vec<HeaderWrapper>,
98    header_size: usize,
99    raw_headers: String,
100}
101
102#[derive(Serialize)]
103#[serde(rename_all = "camelCase")]
104struct GetResponseContentReply {
105    from: String,
106    content: Option<ResponseContent>,
107    content_discarded: bool,
108}
109
110#[derive(Serialize)]
111#[serde(rename_all = "camelCase")]
112struct GetEventTimingsReply {
113    from: String,
114    offsets: Timings,
115    server_timings: Vec<()>,
116    timings: Timings,
117    total_time: usize,
118}
119
120#[derive(Serialize)]
121#[serde(rename_all = "camelCase")]
122struct GetSecurityInfoReply {
123    from: String,
124    security_info: SecurityInfo,
125}
126
127#[derive(Clone, Serialize)]
128#[serde(rename_all = "camelCase")]
129struct RequestFields {
130    event_timings_available: bool,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    remote_address: Option<String>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    remote_port: Option<u16>,
135    request_cookies_available: bool,
136    request_headers_available: bool,
137    total_time: f64,
138}
139
140#[derive(Clone, Serialize)]
141#[serde(rename_all = "camelCase")]
142struct ResponseFields {
143    #[serde(flatten)]
144    cache_details: CacheDetails,
145    response_content_available: bool,
146    response_cookies_available: bool,
147    response_headers_available: bool,
148    response_start_available: bool,
149    status: String,
150    status_text: String,
151}
152
153#[derive(Clone, Serialize)]
154#[serde(rename_all = "camelCase")]
155struct SecurityFields {
156    security_state: String,
157    security_info_available: bool,
158}
159
160#[derive(Clone, Serialize)]
161#[serde(rename_all = "camelCase")]
162pub(crate) struct ResourceUpdates {
163    http_version: String,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    #[serde(flatten)]
166    request: Option<RequestFields>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    #[serde(flatten)]
169    response: Option<ResponseFields>,
170    #[serde(flatten)]
171    security: SecurityFields,
172}
173
174#[derive(Serialize)]
175#[serde(rename_all = "camelCase")]
176struct ResponseContent {
177    body_size: usize,
178    content_charset: String,
179    decoded_body_size: usize,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    encoding: Option<String>,
182    headers_size: usize,
183    is_content_encoded: bool,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    mime_type: Option<String>,
186    size: usize,
187    text: Value,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    transferred_size: Option<u64>,
190}
191
192#[derive(Clone, Serialize, MallocSizeOf)]
193#[serde(rename_all = "camelCase")]
194pub(crate) struct CacheDetails {
195    from_cache: bool,
196    from_service_worker: bool,
197}
198
199#[derive(Clone, Default, Serialize, MallocSizeOf)]
200pub(crate) struct Timings {
201    blocked: usize,
202    dns: usize,
203    connect: usize,
204    send: usize,
205    wait: usize,
206    receive: usize,
207}
208
209impl Timings {
210    fn total(&self) -> usize {
211        self.dns + self.connect + self.send + self.wait + self.receive
212    }
213}
214
215#[derive(Serialize, Default)]
216#[serde(rename_all = "camelCase")]
217struct CertificateIdentity {
218    #[serde(skip_serializing_if = "Option::is_none")]
219    name: Option<String>,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    common_name: Option<String>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    organization: Option<String>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    organizational_unit: Option<String>,
226}
227
228#[derive(Serialize, Default)]
229#[serde(rename_all = "camelCase")]
230struct CertificateValidity {
231    #[serde(skip_serializing_if = "Option::is_none")]
232    start: Option<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    end: Option<String>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    lifetime: Option<String>,
237    expired: bool,
238}
239
240#[derive(Serialize, Default)]
241#[serde(rename_all = "camelCase")]
242struct CertificateFingerprint {
243    #[serde(skip_serializing_if = "Option::is_none")]
244    sha256: Option<String>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    sha1: Option<String>,
247}
248
249#[derive(Serialize, Default)]
250#[serde(rename_all = "camelCase")]
251struct SecurityCertificate {
252    subject: CertificateIdentity,
253    issuer: CertificateIdentity,
254    validity: CertificateValidity,
255    fingerprint: CertificateFingerprint,
256    #[serde(skip_serializing_if = "Option::is_none")]
257    serial_number: Option<String>,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    is_built_in_root: Option<bool>,
260}
261
262#[derive(Serialize, Default)]
263#[serde(rename_all = "camelCase")]
264struct SecurityInfo {
265    state: String,
266    #[serde(skip_serializing_if = "Vec::is_empty")]
267    weakness_reasons: Vec<String>,
268    #[serde(skip_serializing_if = "Option::is_none")]
269    protocol_version: Option<String>,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    cipher_suite: Option<String>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    kea_group_name: Option<String>,
274    #[serde(skip_serializing_if = "Option::is_none")]
275    signature_scheme_name: Option<String>,
276    #[serde(skip_serializing_if = "Option::is_none")]
277    alpn_protocol: Option<String>,
278    #[serde(skip_serializing_if = "Option::is_none")]
279    certificate_transparency: Option<String>,
280    hsts: bool,
281    hpkp: bool,
282    used_ech: bool,
283    used_delegated_credentials: bool,
284    used_ocsp: bool,
285    used_private_dns: bool,
286    #[serde(skip_serializing_if = "Vec::is_empty")]
287    certificate_chain: Vec<String>,
288    cert: SecurityCertificate,
289}
290
291impl From<&TlsSecurityInfo> for SecurityInfo {
292    fn from(info: &TlsSecurityInfo) -> Self {
293        Self {
294            state: info.state.to_string(),
295            weakness_reasons: info.weakness_reasons.clone(),
296            protocol_version: info.protocol_version.clone(),
297            cipher_suite: info.cipher_suite.clone(),
298            kea_group_name: info.kea_group_name.clone(),
299            signature_scheme_name: info.signature_scheme_name.clone(),
300            alpn_protocol: info.alpn_protocol.clone(),
301            certificate_transparency: info
302                .certificate_transparency
303                .clone()
304                .or_else(|| Some("unknown".to_string())),
305            hsts: info.hsts,
306            hpkp: info.hpkp,
307            used_ech: info.used_ech,
308            used_delegated_credentials: info.used_delegated_credentials,
309            used_ocsp: info.used_ocsp,
310            used_private_dns: info.used_private_dns,
311            ..Default::default()
312        }
313    }
314}
315
316#[derive(MallocSizeOf)]
317struct NetworkEventRequest {
318    offsets: Timings,
319    timings: Timings,
320    request: HttpRequest,
321    total_time: Duration,
322}
323
324#[derive(MallocSizeOf)]
325struct NetworkEventResponse {
326    cache_details: CacheDetails,
327    response: HttpResponse,
328}
329
330#[derive(Serialize)]
331pub(crate) struct CookieWrapper {
332    name: String,
333    value: String,
334    #[serde(skip_serializing_if = "Option::is_none")]
335    path: Option<String>,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    domain: Option<String>,
338    #[serde(skip_serializing_if = "Option::is_none")]
339    expires: Option<String>,
340    #[serde(skip_serializing_if = "Option::is_none")]
341    http_only: Option<bool>,
342    #[serde(skip_serializing_if = "Option::is_none")]
343    secure: Option<bool>,
344    #[serde(skip_serializing_if = "Option::is_none")]
345    same_site: Option<String>,
346}
347
348#[derive(Serialize)]
349struct HeaderWrapper {
350    name: String,
351    value: String,
352}
353
354impl Actor for NetworkEventActor {
355    fn name(&self) -> String {
356        self.name.clone()
357    }
358
359    fn handle_message(
360        &self,
361        client_request: ClientRequest,
362        registry: &ActorRegistry,
363        msg_type: &str,
364        _msg: &Map<String, Value>,
365        _id: StreamId,
366    ) -> Result<(), ActorError> {
367        match msg_type {
368            "getRequestHeaders" => {
369                let request = self.request.borrow();
370                let request = request.as_ref().ok_or(ActorError::Internal)?;
371
372                let headers = get_header_list(&request.request.headers);
373                let raw_headers = get_raw_headers(&headers);
374
375                let msg = GetRequestHeadersReply {
376                    from: self.name(),
377                    headers,
378                    header_size: raw_headers.len(),
379                    raw_headers,
380                };
381                client_request.reply_final(&msg)?
382            },
383
384            "getRequestCookies" => {
385                let request = self.request.borrow();
386                let request = request.as_ref().ok_or(ActorError::Internal)?;
387
388                let msg = GetCookiesReply {
389                    from: self.name(),
390                    cookies: get_cookies_from_headers(
391                        &request.request.headers,
392                        &request.request.url,
393                    ),
394                };
395
396                client_request.reply_final(&msg)?
397            },
398
399            "getRequestPostData" => {
400                let request = self.request.borrow();
401                let request = request.as_ref().ok_or(ActorError::Internal)?;
402
403                let msg = GetRequestPostDataReply {
404                    from: self.name(),
405                    post_data: request.request.body.as_ref().map(|b| b.0.clone()),
406                    post_data_discarded: request.request.body.is_none(),
407                };
408                client_request.reply_final(&msg)?
409            },
410
411            "getResponseHeaders" => {
412                let response = self.response.borrow();
413                let response = response.as_ref().ok_or(ActorError::Internal)?;
414
415                let list = response
416                    .response
417                    .headers
418                    .as_ref()
419                    .map(get_header_list)
420                    .unwrap_or_default();
421                let raw_headers = get_raw_headers(&list);
422
423                let msg = GetResponseHeadersReply {
424                    from: self.name(),
425                    headers: list,
426                    header_size: raw_headers.len(),
427                    raw_headers,
428                };
429                client_request.reply_final(&msg)?;
430            },
431
432            "getResponseCookies" => {
433                let request = self.request.borrow();
434                let request = request.as_ref().ok_or(ActorError::Internal)?;
435                let response = self.response.borrow();
436                let response = response.as_ref().ok_or(ActorError::Internal)?;
437
438                let msg = GetCookiesReply {
439                    from: self.name(),
440                    cookies: get_cookies_from_headers(
441                        response
442                            .response
443                            .headers
444                            .as_ref()
445                            .ok_or(ActorError::Internal)?,
446                        &request.request.url,
447                    ),
448                };
449                client_request.reply_final(&msg)?
450            },
451
452            "getResponseContent" => {
453                let response = self.response.borrow();
454                let response = response.as_ref().ok_or(ActorError::Internal)?;
455
456                let headers = response.response.headers.as_ref();
457                let list = headers.map(get_header_list).unwrap_or_default();
458                let raw_headers = get_raw_headers(&list);
459
460                let mime_type = headers
461                    .and_then(extract_mime_type_as_dataurl_mime)
462                    .map(|url| url.to_string());
463                let transferred_size = headers
464                    .and_then(|header| header.typed_get::<ContentLength>())
465                    .map(|content_length_header| content_length_header.0);
466
467                let content = response.response.body.as_ref().map(|body| {
468                    let (encoding, text) = if mime_type.is_some() {
469                        // Queue a LongStringActor for this body
470                        let body_string = String::from_utf8_lossy(body).to_string();
471                        let long_string_name = LongStringActor::register(registry, body_string);
472                        let value = registry
473                            .find::<LongStringActor>(&long_string_name)
474                            .long_string_obj();
475                        (None, serde_json::to_value(value).unwrap())
476                    } else {
477                        let b64 = STANDARD.encode(&body.0);
478                        (Some("base64".into()), serde_json::to_value(b64).unwrap())
479                    };
480                    let is_content_encoded = encoding.is_some();
481
482                    ResponseContent {
483                        body_size: body.len(),
484                        content_charset: "".into(),
485                        decoded_body_size: body.len(),
486                        encoding,
487                        headers_size: raw_headers.len(),
488                        is_content_encoded,
489                        mime_type,
490                        size: body.len(),
491                        text,
492                        transferred_size,
493                    }
494                });
495
496                let msg = GetResponseContentReply {
497                    from: self.name(),
498                    content,
499                    content_discarded: response.response.body.is_none(),
500                };
501                client_request.reply_final(&msg)?
502            },
503
504            "getEventTimings" => {
505                let request = self.request.borrow();
506                let request = request.as_ref().ok_or(ActorError::Internal)?;
507
508                let offsets = request.offsets.clone();
509                let timings = request.timings.clone();
510                let total_time = timings.total();
511
512                let msg = GetEventTimingsReply {
513                    from: self.name(),
514                    offsets,
515                    server_timings: vec![],
516                    timings,
517                    total_time,
518                };
519                client_request.reply_final(&msg)?
520            },
521
522            "getSecurityInfo" => {
523                let security_info = &*self.security_info.borrow();
524
525                let msg = GetSecurityInfoReply {
526                    from: self.name(),
527                    security_info: security_info.into(),
528                };
529                client_request.reply_final(&msg)?
530            },
531
532            _ => return Err(ActorError::UnrecognizedPacketType),
533        };
534        Ok(())
535    }
536}
537
538impl NetworkEventActor {
539    pub fn register(registry: &ActorRegistry, resource_id: u64, watcher_name: String) -> String {
540        let name = registry.new_name::<Self>();
541        let actor = NetworkEventActor {
542            name: name.clone(),
543            resource_id,
544            watcher_name,
545            ..Default::default()
546        };
547        registry.register::<Self>(actor);
548        name
549    }
550
551    pub fn add_request(&self, request: HttpRequest) {
552        *self.request.borrow_mut() = Some(NetworkEventRequest {
553            // TODO: Fill the rest of the fields correctly for offsets and timings
554            offsets: Default::default(),
555            timings: Timings {
556                connect: request.connect_time.as_millis() as usize,
557                send: request.send_time.as_millis() as usize,
558                ..Default::default()
559            },
560            total_time: request.connect_time + request.send_time,
561            request,
562        });
563    }
564
565    pub fn add_response(&self, response: HttpResponse) {
566        if response.body.is_none() {
567            return;
568        }
569        *self.response.borrow_mut() = Some(NetworkEventResponse {
570            cache_details: CacheDetails {
571                from_cache: response.from_cache,
572                from_service_worker: false,
573            },
574            response,
575        });
576    }
577
578    pub fn add_security_info(&self, security_info: Option<TlsSecurityInfo>) {
579        *self.security_info.borrow_mut() = security_info.unwrap_or_default();
580    }
581
582    fn request_fields(&self) -> Option<RequestFields> {
583        let request = self.request.borrow();
584        let request = request.as_ref()?;
585        let url = request.request.url.as_url();
586        let cookies = get_cookies_from_headers(&request.request.headers, &request.request.url);
587
588        Some(RequestFields {
589            event_timings_available: true,
590            remote_address: url.host_str().map(|a| a.into()),
591            remote_port: url.port(),
592            request_cookies_available: !cookies.is_empty(),
593            request_headers_available: !request.request.headers.is_empty(),
594            total_time: request.total_time.as_secs_f64(),
595        })
596    }
597
598    fn response_fields(&self) -> Option<ResponseFields> {
599        let response = self.response.borrow();
600        let response = response.as_ref()?;
601        let url = self.request.borrow().as_ref()?.request.url.clone();
602        let headers = response.response.headers.as_ref();
603        let cookies = headers.map(|headers| get_cookies_from_headers(headers, &url));
604        let status = &response.response.status;
605
606        Some(ResponseFields {
607            cache_details: response.cache_details.clone(),
608            response_content_available: response
609                .response
610                .body
611                .as_ref()
612                .is_some_and(|body| !body.is_empty()),
613            response_cookies_available: cookies.is_some(),
614            response_headers_available: headers.is_some(),
615            response_start_available: true,
616            status: status.code().to_string(),
617            status_text: String::from_utf8_lossy(status.message()).to_string(),
618        })
619    }
620
621    fn security_fields(&self) -> SecurityFields {
622        let security_info = self.security_info.borrow();
623
624        SecurityFields {
625            security_state: security_info.state.to_string(),
626            security_info_available: true,
627        }
628    }
629
630    pub fn resource_updates(&self, registry: &ActorRegistry) -> NetworkEventResource {
631        let watcher_actor = registry.find::<WatcherActor>(&self.watcher_name);
632        let browsing_context_actor =
633            registry.find::<BrowsingContextActor>(&watcher_actor.browsing_context_name);
634
635        NetworkEventResource {
636            resource_id: self.resource_id,
637            resource_updates: ResourceUpdates {
638                // TODO: Set correct value
639                http_version: "HTTP/1.1".into(),
640                request: self.request_fields(),
641                response: self.response_fields(),
642                security: self.security_fields(),
643            },
644            browsing_context_id: browsing_context_actor.browsing_context_id.value(),
645            inner_window_id: 0,
646        }
647    }
648}
649
650fn get_cookies_from_headers(headers: &HeaderMap, url: &ServoUrl) -> Vec<CookieWrapper> {
651    headers
652        .get_all("set-cookie")
653        .iter()
654        .filter_map(|cookie| {
655            let cookie_str = std::str::from_utf8(cookie.as_bytes()).ok()?;
656            ServoCookie::from_cookie_string(cookie_str, url, CookieSource::HTTP)
657        })
658        .map(|cookie| {
659            let cookie = &cookie.cookie;
660            CookieWrapper {
661                name: cookie.name().into(),
662                value: cookie.value().into(),
663                path: cookie.path().map(|p| p.into()),
664                domain: cookie.domain().map(|d| d.into()),
665                expires: cookie.expires().map(|e| format!("{e:?}")),
666                http_only: cookie.http_only(),
667                secure: cookie.secure(),
668                same_site: cookie.same_site().map(|s| s.to_string()),
669            }
670        })
671        .collect()
672}
673
674fn get_header_list(headers: &HeaderMap) -> Vec<HeaderWrapper> {
675    headers
676        .iter()
677        .map(|(name, value)| HeaderWrapper {
678            name: name.as_str().into(),
679            value: value.to_str().unwrap_or_default().into(),
680        })
681        .collect()
682}
683
684fn get_raw_headers(headers: &[HeaderWrapper]) -> String {
685    headers
686        .iter()
687        .map(|header| format!("{}:{}", header.name, header.value))
688        .collect::<Vec<_>>()
689        .join("\r\n")
690}
691
692impl ActorEncode<NetworkEventMsg> for NetworkEventActor {
693    fn encode(&self, registry: &ActorRegistry) -> NetworkEventMsg {
694        let request = self.request.borrow();
695        let request = &request.as_ref().expect("There should be a request").request;
696
697        let started_datetime_rfc3339 = match Local.timestamp_millis_opt(
698            request
699                .started_date_time
700                .duration_since(UNIX_EPOCH)
701                .unwrap_or_default()
702                .as_millis() as i64,
703        ) {
704            LocalResult::None => "".to_owned(),
705            LocalResult::Single(date_time) => date_time.to_rfc3339(),
706            LocalResult::Ambiguous(date_time, _) => date_time.to_rfc3339(),
707        };
708
709        let watcher_actor = registry.find::<WatcherActor>(&self.watcher_name);
710        let browsing_context_actor =
711            registry.find::<BrowsingContextActor>(&watcher_actor.browsing_context_name);
712
713        NetworkEventMsg {
714            actor: self.name(),
715            browsing_context_id: browsing_context_actor.browsing_context_id.value(),
716            cause: Cause {
717                type_: request.destination.as_str().to_string(),
718                loading_document_uri: None, // Set if available
719            },
720            is_xhr: request.is_xhr,
721            method: format!("{}", request.method),
722            private: false,
723            resource_id: self.resource_id,
724            started_date_time: started_datetime_rfc3339,
725            time_stamp: request.time_stamp,
726            url: request.url.to_string(),
727        }
728    }
729}