Skip to main content

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::sync::Arc;
9use std::time::{Duration, UNIX_EPOCH};
10
11use atomic_refcell::AtomicRefCell;
12use base64::engine::Engine;
13use base64::engine::general_purpose::STANDARD;
14use chrono::{Local, LocalResult, TimeZone};
15use devtools_traits::{HttpRequest, HttpResponse};
16use headers::{ContentLength, HeaderMapExt};
17use http::HeaderMap;
18use malloc_size_of_derive::MallocSizeOf;
19use net::cookie::ServoCookie;
20use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
21use net_traits::{CookieSource, TlsSecurityInfo};
22use serde::Serialize;
23use serde_json::{Map, Value};
24use servo_url::ServoUrl;
25
26use crate::StreamId;
27use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
28use crate::actors::browsing_context::BrowsingContextActor;
29use crate::actors::long_string::LongStringActor;
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 browsing_context_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
297                .protocol_version
298                .as_ref()
299                .map(|protocol_version| format!("{protocol_version:?}")),
300            cipher_suite: info
301                .cipher_suite
302                .as_ref()
303                .map(|cipher_suite| format!("{cipher_suite:?}")),
304            kea_group_name: info
305                .kea_group_name
306                .as_ref()
307                .map(|group_name| format!("{group_name:?}")),
308            signature_scheme_name: info.signature_scheme_name.clone(),
309            alpn_protocol: info.alpn_protocol.clone(),
310            certificate_transparency: info
311                .certificate_transparency
312                .clone()
313                .or_else(|| Some("unknown".to_string())),
314            hsts: info.hsts,
315            hpkp: info.hpkp,
316            used_ech: info.used_ech,
317            used_delegated_credentials: info.used_delegated_credentials,
318            used_ocsp: info.used_ocsp,
319            used_private_dns: info.used_private_dns,
320            ..Default::default()
321        }
322    }
323}
324
325#[derive(MallocSizeOf)]
326struct NetworkEventRequest {
327    offsets: Timings,
328    timings: Timings,
329    request: HttpRequest,
330    total_time: Duration,
331}
332
333#[derive(MallocSizeOf)]
334struct NetworkEventResponse {
335    cache_details: CacheDetails,
336    response: HttpResponse,
337}
338
339#[derive(Serialize)]
340pub(crate) struct CookieWrapper {
341    name: String,
342    value: String,
343    #[serde(skip_serializing_if = "Option::is_none")]
344    path: Option<String>,
345    #[serde(skip_serializing_if = "Option::is_none")]
346    domain: Option<String>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    expires: Option<String>,
349    #[serde(skip_serializing_if = "Option::is_none")]
350    http_only: Option<bool>,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    secure: Option<bool>,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    same_site: Option<String>,
355}
356
357#[derive(Serialize)]
358struct HeaderWrapper {
359    name: String,
360    value: String,
361}
362
363impl Actor for NetworkEventActor {
364    fn name(&self) -> &str {
365        &self.name
366    }
367
368    fn handle_message(
369        &self,
370        client_request: ClientRequest,
371        registry: &ActorRegistry,
372        msg_type: &str,
373        _msg: &Map<String, Value>,
374        _id: StreamId,
375    ) -> Result<(), ActorError> {
376        match msg_type {
377            "getRequestHeaders" => {
378                let request = self.request.borrow();
379                let request = request.as_ref().ok_or(ActorError::Internal)?;
380
381                let headers = get_header_list(&request.request.headers);
382                let raw_headers = get_raw_headers(&headers);
383
384                let msg = GetRequestHeadersReply {
385                    from: self.name().into(),
386                    headers,
387                    header_size: raw_headers.len(),
388                    raw_headers,
389                };
390                client_request.reply_final(&msg)?
391            },
392
393            "getRequestCookies" => {
394                let request = self.request.borrow();
395                let request = request.as_ref().ok_or(ActorError::Internal)?;
396
397                let msg = GetCookiesReply {
398                    from: self.name().into(),
399                    cookies: get_cookies_from_headers(
400                        &request.request.headers,
401                        &request.request.url,
402                    ),
403                };
404
405                client_request.reply_final(&msg)?
406            },
407
408            "getRequestPostData" => {
409                let request = self.request.borrow();
410                let request = request.as_ref().ok_or(ActorError::Internal)?;
411
412                let msg = GetRequestPostDataReply {
413                    from: self.name().into(),
414                    post_data: request.request.body.as_ref().map(|b| b.to_vec()),
415                    post_data_discarded: request.request.body.is_none(),
416                };
417                client_request.reply_final(&msg)?
418            },
419
420            "getResponseHeaders" => {
421                let response = self.response.borrow();
422                let response = response.as_ref().ok_or(ActorError::Internal)?;
423
424                let list = response
425                    .response
426                    .headers
427                    .as_ref()
428                    .map(get_header_list)
429                    .unwrap_or_default();
430                let raw_headers = get_raw_headers(&list);
431
432                let msg = GetResponseHeadersReply {
433                    from: self.name().into(),
434                    headers: list,
435                    header_size: raw_headers.len(),
436                    raw_headers,
437                };
438                client_request.reply_final(&msg)?;
439            },
440
441            "getResponseCookies" => {
442                let request = self.request.borrow();
443                let request = request.as_ref().ok_or(ActorError::Internal)?;
444                let response = self.response.borrow();
445                let response = response.as_ref().ok_or(ActorError::Internal)?;
446
447                let msg = GetCookiesReply {
448                    from: self.name().into(),
449                    cookies: get_cookies_from_headers(
450                        response
451                            .response
452                            .headers
453                            .as_ref()
454                            .ok_or(ActorError::Internal)?,
455                        &request.request.url,
456                    ),
457                };
458                client_request.reply_final(&msg)?
459            },
460
461            "getResponseContent" => {
462                let response = self.response.borrow();
463                let response = response.as_ref().ok_or(ActorError::Internal)?;
464
465                let headers = response.response.headers.as_ref();
466                let list = headers.map(get_header_list).unwrap_or_default();
467                let raw_headers = get_raw_headers(&list);
468
469                let mime_type = headers
470                    .and_then(extract_mime_type_as_dataurl_mime)
471                    .map(|url| url.to_string());
472                let transferred_size = headers
473                    .and_then(|header| header.typed_get::<ContentLength>())
474                    .map(|content_length_header| content_length_header.0);
475
476                let content = response.response.body.as_ref().map(|body| {
477                    let (encoding, text) = if mime_type.is_some() {
478                        // Queue a LongStringActor for this body
479                        let body_string = String::from_utf8_lossy(body).to_string();
480                        let long_string_actor = LongStringActor::register(registry, body_string);
481                        let value = long_string_actor.long_string_obj();
482                        (None, serde_json::to_value(value).unwrap())
483                    } else {
484                        let b64 = STANDARD.encode(body);
485                        (Some("base64".into()), serde_json::to_value(b64).unwrap())
486                    };
487                    let is_content_encoded = encoding.is_some();
488
489                    ResponseContent {
490                        body_size: body.len(),
491                        content_charset: String::new(),
492                        decoded_body_size: body.len(),
493                        encoding,
494                        headers_size: raw_headers.len(),
495                        is_content_encoded,
496                        mime_type,
497                        size: body.len(),
498                        text,
499                        transferred_size,
500                    }
501                });
502
503                let msg = GetResponseContentReply {
504                    from: self.name().into(),
505                    content,
506                    content_discarded: response.response.body.is_none(),
507                };
508                client_request.reply_final(&msg)?
509            },
510
511            "getEventTimings" => {
512                let request = self.request.borrow();
513                let request = request.as_ref().ok_or(ActorError::Internal)?;
514
515                let offsets = request.offsets.clone();
516                let timings = request.timings.clone();
517                let total_time = timings.total();
518
519                let msg = GetEventTimingsReply {
520                    from: self.name().into(),
521                    offsets,
522                    server_timings: vec![],
523                    timings,
524                    total_time,
525                };
526                client_request.reply_final(&msg)?
527            },
528
529            "getSecurityInfo" => {
530                let security_info = &*self.security_info.borrow();
531
532                let msg = GetSecurityInfoReply {
533                    from: self.name().into(),
534                    security_info: security_info.into(),
535                };
536                client_request.reply_final(&msg)?
537            },
538
539            _ => return Err(ActorError::UnrecognizedPacketType),
540        };
541        Ok(())
542    }
543}
544
545impl NetworkEventActor {
546    pub fn register(
547        registry: &ActorRegistry,
548        resource_id: u64,
549        browsing_context_name: String,
550    ) -> Arc<Self> {
551        let name = new_actor_name::<Self>();
552        let actor = NetworkEventActor {
553            name,
554            resource_id,
555            browsing_context_name,
556            ..Default::default()
557        };
558        registry.register::<Self>(actor)
559    }
560
561    pub fn add_request(&self, request: HttpRequest) {
562        *self.request.borrow_mut() = Some(NetworkEventRequest {
563            // TODO: Fill the rest of the fields correctly for offsets and timings
564            offsets: Default::default(),
565            timings: Timings {
566                connect: request.connect_time.as_millis() as usize,
567                send: request.send_time.as_millis() as usize,
568                ..Default::default()
569            },
570            total_time: request.connect_time + request.send_time,
571            request,
572        });
573    }
574
575    pub fn add_response(&self, response: HttpResponse) {
576        if response.body.is_none() {
577            return;
578        }
579        *self.response.borrow_mut() = Some(NetworkEventResponse {
580            cache_details: CacheDetails {
581                from_cache: response.from_cache,
582                from_service_worker: false,
583            },
584            response,
585        });
586    }
587
588    pub fn add_security_info(&self, security_info: Option<TlsSecurityInfo>) {
589        *self.security_info.borrow_mut() = security_info.unwrap_or_default();
590    }
591
592    fn request_fields(&self) -> Option<RequestFields> {
593        let request = self.request.borrow();
594        let request = request.as_ref()?;
595        let url = request.request.url.as_url();
596        let cookies = get_cookies_from_headers(&request.request.headers, &request.request.url);
597
598        Some(RequestFields {
599            event_timings_available: true,
600            remote_address: url.host_str().map(|a| a.into()),
601            remote_port: url.port(),
602            request_cookies_available: !cookies.is_empty(),
603            request_headers_available: !request.request.headers.is_empty(),
604            total_time: request.total_time.as_secs_f64(),
605        })
606    }
607
608    fn response_fields(&self) -> Option<ResponseFields> {
609        let response = self.response.borrow();
610        let response = response.as_ref()?;
611        let url = self.request.borrow().as_ref()?.request.url.clone();
612        let headers = response.response.headers.as_ref();
613        let cookies = headers.map(|headers| get_cookies_from_headers(headers, &url));
614        let status = &response.response.status;
615
616        Some(ResponseFields {
617            cache_details: response.cache_details.clone(),
618            response_content_available: response
619                .response
620                .body
621                .as_ref()
622                .is_some_and(|body| !body.is_empty()),
623            response_cookies_available: cookies.is_some(),
624            response_headers_available: headers.is_some(),
625            response_start_available: true,
626            status: status.code().to_string(),
627            status_text: String::from_utf8_lossy(status.message()).to_string(),
628        })
629    }
630
631    fn security_fields(&self) -> SecurityFields {
632        let security_info = self.security_info.borrow();
633
634        SecurityFields {
635            security_state: security_info.state.to_string(),
636            security_info_available: true,
637        }
638    }
639
640    pub fn resource_updates(&self, registry: &ActorRegistry) -> NetworkEventResource {
641        let browsing_context_actor =
642            registry.find::<BrowsingContextActor>(&self.browsing_context_name);
643
644        NetworkEventResource {
645            resource_id: self.resource_id,
646            resource_updates: ResourceUpdates {
647                // TODO: Set correct value
648                http_version: "HTTP/1.1".into(),
649                request: self.request_fields(),
650                response: self.response_fields(),
651                security: self.security_fields(),
652            },
653            browsing_context_id: browsing_context_actor.browsing_context_id.value(),
654            inner_window_id: 0,
655        }
656    }
657}
658
659fn get_cookies_from_headers(headers: &HeaderMap, url: &ServoUrl) -> Vec<CookieWrapper> {
660    headers
661        .get_all("set-cookie")
662        .iter()
663        .filter_map(|cookie| {
664            let cookie_str = std::str::from_utf8(cookie.as_bytes()).ok()?;
665            ServoCookie::from_cookie_string(cookie_str, url, CookieSource::HTTP)
666        })
667        .map(|cookie| {
668            let cookie = &cookie.cookie;
669            CookieWrapper {
670                name: cookie.name().into(),
671                value: cookie.value().into(),
672                path: cookie.path().map(|p| p.into()),
673                domain: cookie.domain().map(|d| d.into()),
674                expires: cookie.expires().map(|e| format!("{e:?}")),
675                http_only: cookie.http_only(),
676                secure: cookie.secure(),
677                same_site: cookie.same_site().map(|s| s.to_string()),
678            }
679        })
680        .collect()
681}
682
683fn get_header_list(headers: &HeaderMap) -> Vec<HeaderWrapper> {
684    headers
685        .iter()
686        .map(|(name, value)| HeaderWrapper {
687            name: name.as_str().into(),
688            value: value.to_str().unwrap_or_default().into(),
689        })
690        .collect()
691}
692
693fn get_raw_headers(headers: &[HeaderWrapper]) -> String {
694    headers
695        .iter()
696        .map(|header| format!("{}:{}", header.name, header.value))
697        .collect::<Vec<_>>()
698        .join("\r\n")
699}
700
701impl ActorEncode<NetworkEventMsg> for NetworkEventActor {
702    fn encode(&self, registry: &ActorRegistry) -> NetworkEventMsg {
703        let request = self.request.borrow();
704        let request = &request.as_ref().expect("There should be a request").request;
705
706        let started_datetime_rfc3339 = match Local.timestamp_millis_opt(
707            request
708                .started_date_time
709                .duration_since(UNIX_EPOCH)
710                .unwrap_or_default()
711                .as_millis() as i64,
712        ) {
713            LocalResult::None => String::new(),
714            LocalResult::Single(date_time) => date_time.to_rfc3339(),
715            LocalResult::Ambiguous(date_time, _) => date_time.to_rfc3339(),
716        };
717
718        let browsing_context_actor =
719            registry.find::<BrowsingContextActor>(&self.browsing_context_name);
720
721        NetworkEventMsg {
722            actor: self.name().into(),
723            browsing_context_id: browsing_context_actor.browsing_context_id.value(),
724            cause: Cause {
725                type_: request.destination.as_str().to_string(),
726                loading_document_uri: None, // Set if available
727            },
728            is_xhr: request.is_xhr,
729            method: format!("{}", request.method),
730            private: false,
731            resource_id: self.resource_id,
732            started_date_time: started_datetime_rfc3339,
733            time_stamp: request.time_stamp,
734            url: request.url.to_string(),
735        }
736    }
737}