@@ -78,8 +78,19 @@ impl SendableHttpRequest {
7878 }
7979
8080 pub fn insert_header ( & mut self , header : ( String , String ) ) {
81+ if header. 0 . eq_ignore_ascii_case ( "cookie" ) {
82+ if let Some ( existing) =
83+ self . headers . iter_mut ( ) . find ( |h| h. 0 . eq_ignore_ascii_case ( "cookie" ) )
84+ {
85+ existing. 1 = format ! ( "{}; {}" , existing. 1 , header. 1 ) ;
86+ } else {
87+ self . headers . push ( header) ;
88+ }
89+ return ;
90+ }
91+
8192 if let Some ( existing) =
82- self . headers . iter_mut ( ) . find ( |h| h. 0 . to_lowercase ( ) == header. 0 . to_lowercase ( ) )
93+ self . headers . iter_mut ( ) . find ( |h| h. 0 . eq_ignore_ascii_case ( & header. 0 ) )
8394 {
8495 existing. 1 = header. 1 ;
8596 } else {
@@ -205,16 +216,23 @@ fn append_graphql_query_params(url: &str, body: &BTreeMap<String, serde_json::Va
205216}
206217
207218fn build_headers ( r : & HttpRequest ) -> Vec < ( String , String ) > {
208- r. headers
209- . iter ( )
210- . filter_map ( |h| {
211- if h. enabled && !h. name . is_empty ( ) {
212- Some ( ( h. name . clone ( ) , h. value . clone ( ) ) )
213- } else {
214- None
219+ // RFC 6265 allows only one Cookie field, so enabled Cookie rows fold into
220+ // the first one
221+ let mut headers: Vec < ( String , String ) > = Vec :: new ( ) ;
222+ for h in & r. headers {
223+ if !h. enabled || h. name . is_empty ( ) {
224+ continue ;
225+ }
226+ if h. name . eq_ignore_ascii_case ( "cookie" ) {
227+ if let Some ( existing) = headers. iter_mut ( ) . find ( |e| e. 0 . eq_ignore_ascii_case ( "cookie" ) )
228+ {
229+ existing. 1 = format ! ( "{}; {}" , existing. 1 , h. value) ;
230+ continue ;
215231 }
216- } )
217- . collect ( )
232+ }
233+ headers. push ( ( h. name . clone ( ) , h. value . clone ( ) ) ) ;
234+ }
235+ headers
218236}
219237
220238async fn build_body (
@@ -494,7 +512,114 @@ mod tests {
494512 use bytes:: Bytes ;
495513 use serde_json:: json;
496514 use std:: collections:: BTreeMap ;
497- use yaak_models:: models:: { HttpRequest , HttpUrlParameter } ;
515+ use yaak_models:: models:: { HttpRequest , HttpRequestHeader , HttpUrlParameter } ;
516+
517+ #[ tokio:: test]
518+ async fn test_sendable_request_preserves_independent_cookie_enabled_states ( ) {
519+ let request = HttpRequest {
520+ url : "https://example.com/api" . to_string ( ) ,
521+ headers : vec ! [
522+ HttpRequestHeader {
523+ enabled: true ,
524+ name: "Cookie" . to_string( ) ,
525+ value: "session=abc" . to_string( ) ,
526+ id: None ,
527+ } ,
528+ HttpRequestHeader {
529+ enabled: false ,
530+ name: "Cookie" . to_string( ) ,
531+ value: "debug=verbose" . to_string( ) ,
532+ id: None ,
533+ } ,
534+ ] ,
535+ ..Default :: default ( )
536+ } ;
537+
538+ let sendable =
539+ SendableHttpRequest :: from_http_request ( & request, SendableHttpRequestOptions :: default ( ) )
540+ . await
541+ . unwrap ( ) ;
542+
543+ assert_eq ! ( sendable. headers, vec![ ( "Cookie" . to_string( ) , "session=abc" . to_string( ) ) ] ) ;
544+ }
545+
546+ #[ tokio:: test]
547+ async fn test_sendable_request_merges_enabled_cookie_rows_into_one_field ( ) {
548+ let request = HttpRequest {
549+ url : "https://example.com/api" . to_string ( ) ,
550+ headers : vec ! [
551+ HttpRequestHeader {
552+ enabled: true ,
553+ name: "Cookie" . to_string( ) ,
554+ value: "session=abc" . to_string( ) ,
555+ id: None ,
556+ } ,
557+ HttpRequestHeader {
558+ enabled: false ,
559+ name: "Cookie" . to_string( ) ,
560+ value: "debug=verbose" . to_string( ) ,
561+ id: None ,
562+ } ,
563+ HttpRequestHeader {
564+ enabled: true ,
565+ name: "cookie" . to_string( ) ,
566+ value: "theme=dark" . to_string( ) ,
567+ id: None ,
568+ } ,
569+ ] ,
570+ ..Default :: default ( )
571+ } ;
572+
573+ let sendable =
574+ SendableHttpRequest :: from_http_request ( & request, SendableHttpRequestOptions :: default ( ) )
575+ . await
576+ . unwrap ( ) ;
577+
578+ assert_eq ! (
579+ sendable. headers,
580+ vec![ ( "Cookie" . to_string( ) , "session=abc; theme=dark" . to_string( ) ) ] ,
581+ ) ;
582+ }
583+
584+ #[ test]
585+ fn test_insert_header_appends_authentication_cookie ( ) {
586+ let mut request = SendableHttpRequest {
587+ headers : vec ! [
588+ ( "Cookie" . to_string( ) , "session=abc" . to_string( ) ) ,
589+ ( "Cookie" . to_string( ) , "theme=dark" . to_string( ) ) ,
590+ ] ,
591+ ..Default :: default ( )
592+ } ;
593+
594+ request. insert_header ( ( "cookie" . to_string ( ) , "api_key=secret" . to_string ( ) ) ) ;
595+
596+ assert_eq ! (
597+ request. headers,
598+ vec![
599+ ( "Cookie" . to_string( ) , "session=abc; api_key=secret" . to_string( ) ) ,
600+ ( "Cookie" . to_string( ) , "theme=dark" . to_string( ) ) ,
601+ ] ,
602+ ) ;
603+ }
604+
605+ #[ tokio:: test]
606+ async fn test_sendable_request_preserves_serialized_path_delimiters ( ) {
607+ let request = HttpRequest {
608+ url : "https://example.com/labels/.one%2Ftwo.three/matrix/;x=1%3Bspoof%3D2;y=2"
609+ . to_string ( ) ,
610+ ..Default :: default ( )
611+ } ;
612+
613+ let sendable =
614+ SendableHttpRequest :: from_http_request ( & request, SendableHttpRequestOptions :: default ( ) )
615+ . await
616+ . unwrap ( ) ;
617+
618+ assert_eq ! (
619+ sendable. url,
620+ "https://example.com/labels/.one%2Ftwo.three/matrix/;x=1%3Bspoof%3D2;y=2" ,
621+ ) ;
622+ }
498623
499624 #[ test]
500625 fn test_build_url_no_params ( ) {
0 commit comments