From 1995e22e19cfb998edae5bb1124e86b0ab125456 Mon Sep 17 00:00:00 2001 From: Usman Yahaya Baba <91813795+uthmaniv@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:42:28 +0900 Subject: [PATCH] net: Send Cookies to Devtools (#38601) The current behaviour in dev tools network monitor is missing cookies in the cookies tab in Request Details, displaying "No cookies for this request" even for requests with cookies. The changes in this PR refactors the `request_cookies` and `response_cookies` to check and retrieve the cookies if available Also, the structure in which the cookies is sent to devtools is been refactored to match the format firefox's devtools client expects. With these changes, we now have the cookies displayed for requests that have one. Fixes: https://github.com/servo/servo/issues/38127 --------- Signed-off-by: uthmaniv --- Cargo.lock | 1 + components/devtools/Cargo.toml | 1 + components/devtools/actors/network_event.rs | 121 +++++++++++++------- 3 files changed, 81 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c2becba24b..1367157b91f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1923,6 +1923,7 @@ dependencies = [ "http 1.3.1", "ipc-channel", "log", + "net", "net_traits", "serde", "serde_json", diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index bc7322d9ebc..564bdd0b7fe 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -27,6 +27,7 @@ serde_json = { workspace = true } servo_config = { path = "../config" } servo_rand = { path = "../rand" } servo_url = { path = "../url" } +net = { path = "../net" } uuid = { workspace = true } [build-dependencies] diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs index 79785aa7eee..93035280e0b 100644 --- a/components/devtools/actors/network_event.rs +++ b/components/devtools/actors/network_event.rs @@ -10,10 +10,13 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use chrono::{Local, LocalResult, TimeZone}; use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse}; use headers::{ContentLength, ContentType, Cookie, HeaderMapExt}; -use http::{HeaderMap, Method, header}; +use http::{HeaderMap, Method}; +use net::cookie::ServoCookie; +use net_traits::CookieSource; use net_traits::request::Destination as RequestDestination; use serde::Serialize; use serde_json::{Map, Value}; +use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorError, ActorRegistry}; @@ -71,7 +74,7 @@ pub struct EventActor { #[derive(Serialize)] pub struct ResponseCookiesMsg { - pub cookies: usize, + pub cookies: Vec, } #[derive(Serialize)] @@ -104,7 +107,7 @@ pub struct ResponseHeadersMsg { #[derive(Serialize)] pub struct RequestCookiesMsg { - pub cookies: usize, + pub cookies: Vec, } #[derive(Serialize)] @@ -157,13 +160,32 @@ struct GetRequestPostDataReply { #[derive(Serialize)] struct GetRequestCookiesReply { from: String, - cookies: Vec, + cookies: Vec, } #[derive(Serialize)] struct GetResponseCookiesReply { from: String, - cookies: Vec, + cookies: Vec, +} +#[derive(Clone, Serialize)] +pub struct ResponseCookieObj { + pub name: String, + pub value: String, + pub path: Option, + pub domain: Option, + pub expires: Option, + #[serde(rename = "httpOnly")] + pub http_only: Option, + pub secure: Option, + #[serde(rename = "sameSite")] + pub same_site: Option, +} + +#[derive(Clone, Serialize)] +pub struct RequestCookieObj { + pub name: String, + pub value: String, } #[derive(Clone, Default, Serialize)] @@ -236,14 +258,11 @@ impl Actor for NetworkEventActor { request.reply_final(&msg)? }, "getRequestCookies" => { - let mut cookies = Vec::new(); - if let Some(ref headers) = self.request_headers_raw { - for cookie in headers.get_all(header::COOKIE) { - if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) { - cookies = cookie_value.into_bytes(); - } - } - } + let cookies = self + .request_cookies + .as_ref() + .map(|msg| msg.cookies.clone()) + .unwrap_or_default(); let msg = GetRequestCookiesReply { from: self.name(), cookies, @@ -287,15 +306,11 @@ impl Actor for NetworkEventActor { } }, "getResponseCookies" => { - let mut cookies = Vec::new(); - // TODO: This seems quite broken - if let Some(ref headers) = self.response_headers_raw { - for cookie in headers.get_all(header::SET_COOKIE) { - if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) { - cookies = cookie_value.into_bytes(); - } - } - } + let cookies = self + .response_cookies + .as_ref() + .map(|msg| msg.cookies.clone()) + .unwrap_or_default(); let msg = GetResponseCookiesReply { from: self.name(), cookies, @@ -372,7 +387,7 @@ impl NetworkEventActor { pub fn add_request(&mut self, request: DevtoolsHttpRequest) { self.is_xhr = request.is_xhr; - self.request_cookies = Some(Self::request_cookies(&request)); + self.request_cookies = Self::request_cookies(&request); self.request_headers = Some(Self::request_headers(&request)); self.total_time = Self::total_time(&request); self.event_timing = Some(Self::event_timing(&request)); @@ -387,7 +402,10 @@ impl NetworkEventActor { pub fn add_response(&mut self, response: DevtoolsHttpResponse) { self.response_headers = Some(Self::response_headers(&response)); - self.response_cookies = Some(Self::response_cookies(&response)); + self.response_cookies = ServoUrl::parse(&self.request_url) + .ok() + .as_ref() + .and_then(|url| Self::response_cookies(&response, url)); self.response_start = Some(Self::response_start(&response)); self.response_content = Self::response_content(&response); self.response_body = response.body.clone(); @@ -467,15 +485,33 @@ impl NetworkEventActor { }) } - pub fn response_cookies(response: &DevtoolsHttpResponse) -> ResponseCookiesMsg { - let cookies_size = response - .headers - .as_ref() - .map(|headers| headers.get_all("set-cookie").iter().count()) - .unwrap_or(0); - ResponseCookiesMsg { - cookies: cookies_size, - } + pub fn response_cookies( + response: &DevtoolsHttpResponse, + url: &ServoUrl, + ) -> Option { + let headers = response.headers.as_ref()?; + let cookies = headers + .get_all("set-cookie") + .iter() + .filter_map(|cookie| { + let cookie_str = String::from_utf8(cookie.as_bytes().to_vec()).ok()?; + ServoCookie::from_cookie_string(cookie_str, url, CookieSource::HTTP) + }) + .map(|servo_cookie| { + let c = &servo_cookie.cookie; + ResponseCookieObj { + name: c.name().to_string(), + value: c.value().to_string(), + path: c.path().map(|p| p.to_string()), + domain: c.domain().map(|d| d.to_string()), + expires: c.expires().map(|dt| format!("{:?}", dt)), + http_only: c.http_only(), + secure: c.secure(), + same_site: c.same_site().map(|s| s.to_string()), + } + }) + .collect::>(); + Some(ResponseCookiesMsg { cookies }) } pub fn response_headers(response: &DevtoolsHttpResponse) -> ResponseHeadersMsg { @@ -503,15 +539,16 @@ impl NetworkEventActor { } } - pub fn request_cookies(request: &DevtoolsHttpRequest) -> RequestCookiesMsg { - let cookies_size = request - .headers - .typed_get::() - .map(|c| c.len()) - .unwrap_or(0); - RequestCookiesMsg { - cookies: cookies_size, - } + pub fn request_cookies(request: &DevtoolsHttpRequest) -> Option { + let header_value = request.headers.typed_get::()?; + let cookies = header_value + .iter() + .map(|cookie| RequestCookieObj { + name: cookie.0.to_string(), + value: cookie.1.to_string(), + }) + .collect::>(); + Some(RequestCookiesMsg { cookies }) } pub fn total_time(request: &DevtoolsHttpRequest) -> Duration {