@@ -22,7 +22,7 @@ use talon_core::{
2222 BackendStore , Error , ListPage , ListedObject , ObjectId , ObjectStat , Result , Version ,
2323} ;
2424
25- use crate :: http:: { HttpClient , HttpRequest , Method } ;
25+ use crate :: http:: { HttpClient , HttpRequest , HttpResponse , HttpStreamResponse , Method } ;
2626
2727/// Percent-encode a query value.
2828///
@@ -280,6 +280,77 @@ impl S3Backend {
280280 req
281281 }
282282
283+ fn session_headers ( & self ) -> Vec < ( String , String ) > {
284+ self . creds
285+ . session_token
286+ . as_ref ( )
287+ . map ( |token| vec ! [ ( "x-amz-security-token" . to_string( ) , token. clone( ) ) ] )
288+ . unwrap_or_default ( )
289+ }
290+
291+ /// Execute a raw metadata request while forwarding only validated
292+ /// conditional headers supplied by a protocol adapter.
293+ pub async fn execute_head_raw (
294+ & self ,
295+ obj : & ObjectId ,
296+ conditions : & [ ( String , String ) ] ,
297+ ) -> std:: result:: Result < HttpResponse , String > {
298+ let mut request = self . build_head ( obj) ;
299+ request. headers . extend_from_slice ( conditions) ;
300+ self . http . execute ( self . signed ( request) ) . await
301+ }
302+
303+ /// Execute a whole or ranged GET without buffering its response body.
304+ pub async fn execute_get_stream_raw (
305+ & self ,
306+ obj : & ObjectId ,
307+ range : Option < ( u64 , u64 ) > ,
308+ conditions : & [ ( String , String ) ] ,
309+ ) -> std:: result:: Result < HttpStreamResponse , String > {
310+ let mut headers = self . session_headers ( ) ;
311+ if let Some ( ( start, end) ) = range {
312+ headers. push ( ( "range" . into ( ) , format ! ( "bytes={start}-{end}" ) ) ) ;
313+ }
314+ headers. extend_from_slice ( conditions) ;
315+ let request = HttpRequest :: new ( Method :: Get , self . object_url ( obj) , headers) ;
316+ self . http . execute_stream ( self . signed ( request) ) . await
317+ }
318+
319+ /// Execute one raw ListObjectsV2 page.
320+ pub async fn execute_list_raw (
321+ & self ,
322+ bucket : & str ,
323+ prefix : & str ,
324+ delimiter : Option < & str > ,
325+ continuation_token : Option < & str > ,
326+ max_keys : u32 ,
327+ encoding_type : Option < & str > ,
328+ ) -> std:: result:: Result < HttpResponse , String > {
329+ let mut query = format ! ( "list-type=2&max-keys={}" , max_keys. min( 1000 ) ) ;
330+ if !prefix. is_empty ( ) {
331+ query. push_str ( "&prefix=" ) ;
332+ query. push_str ( & encode_query_value ( prefix) ) ;
333+ }
334+ if let Some ( delimiter) = delimiter {
335+ query. push_str ( "&delimiter=" ) ;
336+ query. push_str ( & encode_query_value ( delimiter) ) ;
337+ }
338+ if let Some ( token) = continuation_token {
339+ query. push_str ( "&continuation-token=" ) ;
340+ query. push_str ( & encode_query_value ( token) ) ;
341+ }
342+ if let Some ( encoding_type) = encoding_type {
343+ query. push_str ( "&encoding-type=" ) ;
344+ query. push_str ( & encode_query_value ( encoding_type) ) ;
345+ }
346+ let request = HttpRequest :: new (
347+ Method :: Get ,
348+ format ! ( "{}?{query}" , self . bucket_url( bucket) ) ,
349+ self . session_headers ( ) ,
350+ ) ;
351+ self . http . execute ( self . signed ( request) ) . await
352+ }
353+
283354 fn build_streamed_put (
284355 & self ,
285356 obj : & ObjectId ,
@@ -636,6 +707,39 @@ mod tests {
636707 ) ;
637708 }
638709
710+ #[ tokio:: test]
711+ async fn raw_list_v2_encodes_and_signs_gateway_parameters ( ) {
712+ let http = MockHttp :: new ( HttpResponse {
713+ status : 200 ,
714+ headers : vec ! [ ] ,
715+ body : bytes:: Bytes :: new ( ) ,
716+ } ) ;
717+ let mut config = S3Config :: aws ( "us-east-1" ) ;
718+ config. path_style = true ;
719+ config. tls = false ;
720+ config. endpoint = "localhost:4566" . into ( ) ;
721+ let s3 = S3Backend :: new ( config, creds ( ) , http. clone ( ) ) ;
722+
723+ s3. execute_list_raw (
724+ "my-bucket" ,
725+ "a/b" ,
726+ Some ( "/" ) ,
727+ Some ( "next token" ) ,
728+ 7 ,
729+ Some ( "url" ) ,
730+ )
731+ . await
732+ . unwrap ( ) ;
733+
734+ let request = http. last . lock ( ) . unwrap ( ) . clone ( ) . unwrap ( ) ;
735+ assert_eq ! ( request. method, Method :: Get ) ;
736+ assert_eq ! (
737+ request. url,
738+ "http://localhost:4566/my-bucket?list-type=2&max-keys=7&prefix=a%2Fb&delimiter=%2F&continuation-token=next%20token&encoding-type=url"
739+ ) ;
740+ assert ! ( request. header( "authorization" ) . is_some( ) ) ;
741+ }
742+
639743 #[ tokio:: test]
640744 async fn fetch_range_returns_body_on_206 ( ) {
641745 let http = MockHttp :: new ( HttpResponse {
0 commit comments