From eaaf6f2dbfe26bf8de33387cd3ae6c31d0931a0e Mon Sep 17 00:00:00 2001 From: ldm0 Date: Wed, 7 Oct 2026 17:59:59 +0800 Subject: [PATCH] fix(cookie-cache): allow non-secure Netscape cookies over HTTPS --- moli-cookie-cache/src/netscape.rs | 4 +- moli-cookie-cache/src/tests.rs | 76 ++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/moli-cookie-cache/src/netscape.rs b/moli-cookie-cache/src/netscape.rs index a618156bec..df15cb6abd 100644 --- a/moli-cookie-cache/src/netscape.rs +++ b/moli-cookie-cache/src/netscape.rs @@ -49,10 +49,12 @@ fn stored_cookie_from_netscape_cookie( same_site: StoredCookieSameSite::Unspecified, priority: None, partition_key: None, + // Netscape files only record the Secure attribute. Keep non-Secure + // cookies unbound to a source scheme so they work over HTTP and HTTPS. source_scheme: if secure { StoredCookieSourceScheme::Secure } else { - StoredCookieSourceScheme::NonSecure + StoredCookieSourceScheme::Unset }, source_port: -1, creation_index, diff --git a/moli-cookie-cache/src/tests.rs b/moli-cookie-cache/src/tests.rs index fcbf048fe1..db06f81a9c 100644 --- a/moli-cookie-cache/src/tests.rs +++ b/moli-cookie-cache/src/tests.rs @@ -6,7 +6,8 @@ use std::{ use anyhow::Result; use moli_cookie_jar::{ - CookiePriority, StoredCookie, StoredCookiePartitionKey, StoredCookieSameSite, + BrowserCookieStore, CookiePriority, CookieSource, NetworkCookieRequestContext, StoredCookie, + StoredCookieExclusionReason, StoredCookiePartitionKey, StoredCookieSameSite, StoredCookieSourceScheme, }; use time::OffsetDateTime; @@ -180,6 +181,7 @@ fn netscape_cookie_parser_imports_httponly_tailmatch_cookie() -> Result<()> { assert!(!cookies[0].host_only); assert_eq!(cookies[0].path, "/account/"); assert!(cookies[0].secure); + assert_eq!(cookies[0].source_scheme, StoredCookieSourceScheme::Secure); assert!(cookies[0].http_only); assert_eq!(cookies[0].name, "session"); assert_eq!(cookies[0].value, "fixture"); @@ -247,12 +249,84 @@ fn netscape_cookie_parser_imports_session_cookie() -> Result<()> { assert_eq!(cookies.len(), 1); assert_eq!(cookies[0].domain, "example.com"); assert!(cookies[0].host_only); + assert!(!cookies[0].secure); + assert_eq!(cookies[0].source_scheme, StoredCookieSourceScheme::Unset); assert_eq!(cookies[0].name, "token"); assert_eq!(cookies[0].value, "value with spaces"); assert_eq!(cookies[0].expires, None); Ok(()) } +#[test] +fn netscape_cookie_import_sends_non_secure_cookie_over_http_and_https() -> Result<()> { + let cookies = parse_netscape_cookie_file(std::io::Cursor::new( + "# Netscape HTTP Cookie File\nexample.com\tTRUE\t/\tFALSE\t0\ttest_cookie\thello_val\n", + ))?; + let mut store = BrowserCookieStore::default(); + for cookie in cookies { + let report = store.upsert_with_request_url_report(cookie, None, CookieSource::Cdp); + assert!(report.is_accepted(), "report={report:?}"); + } + + for raw_url in ["http://example.com/cookies", "https://example.com/cookies"] { + let url = raw_url.parse()?; + let report = store.cookie_access_report_for_request( + &url, + NetworkCookieRequestContext::subresource("GET"), + ); + + assert_eq!( + report.included_cookies.len(), + 1, + "url={url}, report={report:?}" + ); + assert!( + report.excluded_cookies.is_empty(), + "url={url}, report={report:?}" + ); + assert_eq!(report.included_cookies[0].cookie.name, "test_cookie"); + assert_eq!(report.included_cookies[0].cookie.value, "hello_val"); + } + Ok(()) +} + +#[test] +fn netscape_cookie_import_sends_secure_cookie_only_over_https() -> Result<()> { + let cookies = parse_netscape_cookie_file(std::io::Cursor::new( + "example.com\tFALSE\t/\tTRUE\t0\ttest_cookie\thello_val\n", + ))?; + let mut store = BrowserCookieStore::default(); + for cookie in cookies { + let report = store.upsert_with_request_url_report(cookie, None, CookieSource::Cdp); + assert!(report.is_accepted(), "report={report:?}"); + } + + let https_url = "https://example.com/cookies".parse()?; + let https_report = store.cookie_access_report_for_request( + &https_url, + NetworkCookieRequestContext::subresource("GET"), + ); + assert_eq!(https_report.included_cookies.len(), 1); + assert!(https_report.excluded_cookies.is_empty()); + assert_eq!(https_report.included_cookies[0].cookie.name, "test_cookie"); + assert_eq!(https_report.included_cookies[0].cookie.value, "hello_val"); + + let http_url = "http://example.com/cookies".parse()?; + let http_report = store.cookie_access_report_for_request( + &http_url, + NetworkCookieRequestContext::subresource("GET"), + ); + assert!(http_report.included_cookies.is_empty()); + assert_eq!(http_report.excluded_cookies.len(), 1); + assert_eq!(http_report.excluded_cookies[0].cookie.name, "test_cookie"); + assert!( + http_report.excluded_cookies[0] + .exclusion_reasons + .contains(&StoredCookieExclusionReason::SecureOnly) + ); + Ok(()) +} + #[test] fn netscape_cookie_parser_skips_expired_cookie() -> Result<()> { let cookies = parse_netscape_cookie_file(std::io::Cursor::new(