@@ -124,6 +124,30 @@ impl CookieStore {
124124 }
125125}
126126
127+ /// Get a stored cookie value by name, optionally scoped to an exact stored domain.
128+ pub fn get_cookie_value_from_jar (
129+ cookies : impl IntoIterator < Item = Cookie > ,
130+ name : & str ,
131+ domain : Option < & str > ,
132+ ) -> Option < String > {
133+ let domain = domain. and_then ( normalize_cookie_domain_filter) ;
134+
135+ cookies. into_iter ( ) . find_map ( |cookie| {
136+ let ( cookie_name, value) = parse_cookie_name_value ( & cookie. raw_cookie ) ?;
137+ if cookie_name != name {
138+ return None ;
139+ }
140+
141+ if let Some ( domain) = domain. as_deref ( ) {
142+ if !cookie_domain_matches_filter ( & cookie. domain , domain) {
143+ return None ;
144+ }
145+ }
146+
147+ Some ( value)
148+ } )
149+ }
150+
127151/// Parse name=value from a cookie string (raw_cookie format)
128152fn parse_cookie_name_value ( raw_cookie : & str ) -> Option < ( String , String ) > {
129153 // The raw_cookie typically looks like "name=value" or "name=value; attr1; attr2=..."
@@ -135,6 +159,20 @@ fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
135159 if name. is_empty ( ) { None } else { Some ( ( name, value) ) }
136160}
137161
162+ fn normalize_cookie_domain_filter ( domain : & str ) -> Option < String > {
163+ let domain = domain. trim ( ) . trim_start_matches ( '.' ) . to_lowercase ( ) ;
164+ if domain. is_empty ( ) { None } else { Some ( domain) }
165+ }
166+
167+ fn cookie_domain_matches_filter ( cookie_domain : & CookieDomain , domain : & str ) -> bool {
168+ match cookie_domain {
169+ CookieDomain :: HostOnly ( cookie_domain) | CookieDomain :: Suffix ( cookie_domain) => {
170+ normalize_cookie_domain_filter ( cookie_domain) . is_some_and ( |d| d == domain)
171+ }
172+ CookieDomain :: NotPresent | CookieDomain :: Empty => false ,
173+ }
174+ }
175+
138176/// Parse a Set-Cookie header into a Cookie
139177fn parse_set_cookie ( header_value : & str , request_url : & Url ) -> Option < Cookie > {
140178 let parsed = cookie:: Cookie :: parse ( header_value) . ok ( ) ?;
@@ -278,6 +316,15 @@ fn is_localhost(domain: &str) -> bool {
278316mod tests {
279317 use super :: * ;
280318
319+ fn cookie ( raw_cookie : & str , domain : CookieDomain ) -> Cookie {
320+ Cookie {
321+ raw_cookie : raw_cookie. to_string ( ) ,
322+ domain,
323+ expires : CookieExpires :: SessionEnd ,
324+ path : ( "/" . to_string ( ) , false ) ,
325+ }
326+ }
327+
281328 #[ test]
282329 fn test_parse_cookie_name_value ( ) {
283330 assert_eq ! (
@@ -387,6 +434,52 @@ mod tests {
387434 assert_eq ! ( store. get_all_cookies( ) . len( ) , 1 ) ;
388435 }
389436
437+ #[ test]
438+ fn test_get_cookie_value_preserves_name_only_first_match ( ) {
439+ let cookies = vec ! [
440+ cookie( "co-auth=" , CookieDomain :: HostOnly ( "foo.example.com" . to_string( ) ) ) ,
441+ cookie( "co-auth=token" , CookieDomain :: Suffix ( "example.com" . to_string( ) ) ) ,
442+ ] ;
443+
444+ assert_eq ! ( get_cookie_value_from_jar( cookies, "co-auth" , None ) , Some ( "" . to_string( ) ) ) ;
445+ }
446+
447+ #[ test]
448+ fn test_get_cookie_value_matches_domain ( ) {
449+ let cookies = vec ! [
450+ cookie( "co-auth=" , CookieDomain :: HostOnly ( "foo.example.com" . to_string( ) ) ) ,
451+ cookie( "co-auth=token" , CookieDomain :: Suffix ( "example.com" . to_string( ) ) ) ,
452+ ] ;
453+
454+ assert_eq ! (
455+ get_cookie_value_from_jar( cookies, "co-auth" , Some ( "example.com" ) ) ,
456+ Some ( "token" . to_string( ) )
457+ ) ;
458+ }
459+
460+ #[ test]
461+ fn test_get_cookie_value_normalizes_domain_filter ( ) {
462+ let cookies = vec ! [ cookie(
463+ "co-auth=token" ,
464+ CookieDomain :: Suffix ( "Example.COM" . to_string( ) ) ,
465+ ) ] ;
466+
467+ assert_eq ! (
468+ get_cookie_value_from_jar( cookies, "co-auth" , Some ( " .example.com " ) ) ,
469+ Some ( "token" . to_string( ) )
470+ ) ;
471+ }
472+
473+ #[ test]
474+ fn test_get_cookie_value_requires_exact_stored_domain_match ( ) {
475+ let cookies = vec ! [ cookie(
476+ "co-auth=token" ,
477+ CookieDomain :: HostOnly ( "foo.example.com" . to_string( ) ) ,
478+ ) ] ;
479+
480+ assert_eq ! ( get_cookie_value_from_jar( cookies, "co-auth" , Some ( "example.com" ) ) , None ) ;
481+ }
482+
390483 #[ test]
391484 fn test_is_single_component_domain ( ) {
392485 // Single-component domains (TLDs)
0 commit comments