55
66use std:: borrow:: Cow ;
77
8+ use base64:: { engine:: general_purpose:: STANDARD , Engine as _} ;
89use cookie:: { Cookie , CookieJar } ;
910use edgezero_core:: body:: Body as EdgeBody ;
1011use error_stack:: { Report , ResultExt } ;
@@ -13,7 +14,8 @@ use http::Request;
1314use http:: Response ;
1415
1516use crate :: constants:: {
16- COOKIE_EUCONSENT_V2 , COOKIE_GPP , COOKIE_GPP_SID , COOKIE_TS_EC , COOKIE_US_PRIVACY ,
17+ COOKIE_EUCONSENT_V2 , COOKIE_GPP , COOKIE_GPP_SID , COOKIE_TS_EC , COOKIE_TS_EIDS ,
18+ COOKIE_US_PRIVACY ,
1719} ;
1820use crate :: error:: TrustedServerError ;
1921use crate :: settings:: Settings ;
@@ -119,6 +121,35 @@ pub fn handle_request_cookies(
119121 }
120122}
121123
124+ /// Parse Extended User IDs from the [`COOKIE_TS_EIDS`] cookie.
125+ ///
126+ /// The cookie value is a standard-base64-encoded JSON array of
127+ /// [`crate::openrtb::Eid`] objects written by the Trusted Server JS SDK via
128+ /// `btoa(JSON.stringify(eids))`.
129+ ///
130+ /// Returns `None` if the cookie is absent, base64-malformed, JSON-malformed,
131+ /// or the decoded array is empty. Parse failures are logged at `debug` level
132+ /// so operators can diagnose JS SDK / server mismatches.
133+ #[ must_use]
134+ pub ( crate ) fn parse_ts_eids_cookie ( jar : Option < & CookieJar > ) -> Option < Vec < crate :: openrtb:: Eid > > {
135+ let value = jar?. get ( COOKIE_TS_EIDS ) ?. value ( ) . to_owned ( ) ;
136+ let decoded = match STANDARD . decode ( & value) {
137+ Ok ( b) => b,
138+ Err ( e) => {
139+ log:: debug!( "ts-eids cookie: base64 decode failed: {e}" ) ;
140+ return None ;
141+ }
142+ } ;
143+ match serde_json:: from_slice :: < Vec < crate :: openrtb:: Eid > > ( & decoded) {
144+ Ok ( eids) if !eids. is_empty ( ) => Some ( eids) ,
145+ Ok ( _) => None ,
146+ Err ( e) => {
147+ log:: debug!( "ts-eids cookie: JSON parse failed: {e}" ) ;
148+ None
149+ }
150+ }
151+ }
152+
122153/// Strips named cookies from a `Cookie` header value string.
123154///
124155/// Parses the semicolon-separated cookie pairs, filters out any whose name
@@ -759,4 +790,73 @@ mod tests {
759790 let stripped = strip_cookies ( header, CONSENT_COOKIE_NAMES ) ;
760791 assert_eq ! ( stripped, "session=abc=123=def" ) ;
761792 }
793+
794+ fn make_jar_with ( name : & str , value : & str ) -> CookieJar {
795+ parse_cookies_to_jar ( & format ! ( "{name}={value}" ) )
796+ }
797+
798+ fn encode_eids ( eids : & [ serde_json:: Value ] ) -> String {
799+ use base64:: { engine:: general_purpose:: STANDARD , Engine as _} ;
800+ STANDARD . encode ( serde_json:: to_string ( eids) . expect ( "should serialize eids" ) )
801+ }
802+
803+ #[ test]
804+ fn parse_ts_eids_cookie_returns_eids_for_valid_input ( ) {
805+ let encoded = encode_eids ( & [ serde_json:: json!( {
806+ "source" : "id5-sync.com" ,
807+ "uids" : [ { "id" : "abc123" , "atype" : 1 } ]
808+ } ) ] ) ;
809+ let jar = make_jar_with ( COOKIE_TS_EIDS , & encoded) ;
810+ let eids = parse_ts_eids_cookie ( Some ( & jar) ) . expect ( "should parse valid ts-eids cookie" ) ;
811+ assert_eq ! ( eids. len( ) , 1 , "should return one EID" ) ;
812+ assert_eq ! ( eids[ 0 ] . source, "id5-sync.com" , "should preserve source" ) ;
813+ assert_eq ! ( eids[ 0 ] . uids[ 0 ] . id, "abc123" , "should preserve uid" ) ;
814+ }
815+
816+ #[ test]
817+ fn parse_ts_eids_cookie_returns_none_when_cookie_absent ( ) {
818+ let jar = CookieJar :: new ( ) ;
819+ assert ! (
820+ parse_ts_eids_cookie( Some ( & jar) ) . is_none( ) ,
821+ "should return None when cookie absent"
822+ ) ;
823+ }
824+
825+ #[ test]
826+ fn parse_ts_eids_cookie_returns_none_for_empty_array ( ) {
827+ let encoded = encode_eids ( & [ ] ) ;
828+ let jar = make_jar_with ( COOKIE_TS_EIDS , & encoded) ;
829+ assert ! (
830+ parse_ts_eids_cookie( Some ( & jar) ) . is_none( ) ,
831+ "should return None for empty EID array"
832+ ) ;
833+ }
834+
835+ #[ test]
836+ fn parse_ts_eids_cookie_returns_none_for_corrupt_base64 ( ) {
837+ let jar = make_jar_with ( COOKIE_TS_EIDS , "not!!valid!!base64" ) ;
838+ assert ! (
839+ parse_ts_eids_cookie( Some ( & jar) ) . is_none( ) ,
840+ "should return None for corrupt base64"
841+ ) ;
842+ }
843+
844+ #[ test]
845+ fn parse_ts_eids_cookie_returns_none_for_invalid_json ( ) {
846+ use base64:: { engine:: general_purpose:: STANDARD , Engine as _} ;
847+ let encoded = STANDARD . encode ( b"this is not json" ) ;
848+ let jar = make_jar_with ( COOKIE_TS_EIDS , & encoded) ;
849+ assert ! (
850+ parse_ts_eids_cookie( Some ( & jar) ) . is_none( ) ,
851+ "should return None for invalid JSON"
852+ ) ;
853+ }
854+
855+ #[ test]
856+ fn parse_ts_eids_cookie_returns_none_for_none_jar ( ) {
857+ assert ! (
858+ parse_ts_eids_cookie( None ) . is_none( ) ,
859+ "should return None when jar is None"
860+ ) ;
861+ }
762862}
0 commit comments