4444//!
4545//! Authentication is handled through custom HTTP headers configured in the dfdaemon
4646//! configuration file or passed directly in the request headers.
47- //!
47+
4848use crate :: {
4949 Backend , Body , ExistsRequest , GetRequest , GetResponse , PutRequest , PutResponse , StatRequest ,
5050 StatResponse , DEFAULT_USER_AGENT , KEEP_ALIVE_INTERVAL , MAX_RETRY_TIMES , POOL_MAX_IDLE_PER_HOST ,
@@ -74,6 +74,7 @@ use std::time::{Duration, Instant};
7474use tokio:: sync:: Mutex ;
7575use tokio_util:: io:: StreamReader ;
7676use tracing:: { debug, error, info, instrument} ;
77+ use url:: Url ;
7778
7879/// HTTP_SCHEME is the HTTP scheme.
7980pub const HTTP_SCHEME : & str = "http" ;
@@ -311,7 +312,7 @@ impl HTTP {
311312 }
312313
313314 /// Get the cached temporary redirect URL if exists and not expired.
314- async fn get_temporary_redirect_url ( & self , url : & str ) -> String {
315+ async fn get_temporary_redirect_url ( & self , url : & str ) -> Option < String > {
315316 let mut temporary_redirects = self . temporary_redirects . lock ( ) . await ;
316317 if let Some ( entry) = temporary_redirects. get ( url) {
317318 if entry. created_at + self . cache_temporary_redirect_ttl > Instant :: now ( ) {
@@ -320,14 +321,14 @@ impl HTTP {
320321 url, entry. url
321322 ) ;
322323
323- return entry. url . clone ( ) ;
324+ return Some ( entry. url . clone ( ) ) ;
324325 } else {
325326 debug ! ( "cached temporary redirect for {} expired" , url) ;
326327 temporary_redirects. pop ( url) ;
327328 }
328329 }
329330
330- url . to_string ( )
331+ None
331332 }
332333
333334 /// Store the temporary redirect URL in the cache.
@@ -380,30 +381,56 @@ impl Backend for HTTP {
380381 self . make_request_headers ( & mut request_header, None ) ?;
381382
382383 // Check if we have a cached temporary redirect for this URL.
383- let target_url = self . get_temporary_redirect_url ( & request. url ) . await ;
384+ let ( request_url, request_header) =
385+ match self . get_temporary_redirect_url ( & request. url ) . await {
386+ Some ( redirect_url) => {
387+ let mut redirect_headers = request_header. clone ( ) ;
388+ remove_sensitive_headers (
389+ & mut redirect_headers,
390+ & redirect_url. parse ( ) ?,
391+ & request. url . parse ( ) ?,
392+ ) ;
393+
394+ ( redirect_url, redirect_headers)
395+ }
396+ None => ( request. url . clone ( ) , request_header) ,
397+ } ;
384398
385399 // The signature in the signed URL generated by the object storage client will include
386400 // the request method. Therefore, the signed URL of the GET method cannot be requested
387401 // through the HEAD method. Use GET request to replace of HEAD request
388402 // to get header and status code.
389403 let response = match self
390404 . client ( request. client_cert . clone ( ) , self . enable_hickory_dns ) ?
391- . get ( & target_url )
405+ . get ( & request_url )
392406 . headers ( request_header. clone ( ) )
393407 . timeout ( request. timeout )
394408 . send ( )
395409 . await
396410 {
397411 Ok ( response) if response. status ( ) == reqwest:: StatusCode :: TEMPORARY_REDIRECT => {
398412 if let Some ( location) = response. headers ( ) . get ( LOCATION ) {
399- let target_url = location. to_str ( ) . or_err ( ErrorType :: ParseError ) ?;
400- self . store_temporary_redirect_url ( & request. url , target_url)
413+ let redirect_url = location. to_str ( ) . or_err ( ErrorType :: ParseError ) ?;
414+ debug ! (
415+ "stat request got 307 Temporary Redirect, following redirect {} -> {}" ,
416+ request. url, redirect_url
417+ ) ;
418+
419+ self . store_temporary_redirect_url ( & request. url , redirect_url)
401420 . await ;
402421
422+ // Strips sensitive headers when following a cross-origin redirect.
423+ let mut redirect_headers = request_header. clone ( ) ;
424+ remove_sensitive_headers (
425+ & mut redirect_headers,
426+ & redirect_url. parse ( ) ?,
427+ & request. url . parse ( ) ?,
428+ ) ;
429+
403430 match self
404431 . client ( request. client_cert . clone ( ) , self . enable_hickory_dns ) ?
405- . get ( target_url )
406- . headers ( request_header . clone ( ) )
432+ . get ( redirect_url )
433+ . headers ( redirect_headers )
407434 . timeout ( request. timeout )
408435 . send ( )
409436 . await
@@ -412,7 +439,7 @@ impl Backend for HTTP {
412439 Err ( err) => {
413440 error ! (
414441 "stat request failed {} {}: {}" ,
415- request. task_id, target_url , err
442+ request. task_id, redirect_url , err
416443 ) ;
417444
418445 return Ok ( StatResponse {
@@ -428,7 +455,7 @@ impl Backend for HTTP {
428455 } else {
429456 error ! (
430457 "stat request got 307 Temporary Redirect without Location header {} {}" ,
431- request. task_id, target_url
458+ request. task_id, request_url
432459 ) ;
433460
434461 return Ok ( StatResponse {
@@ -456,7 +483,7 @@ impl Backend for HTTP {
456483
457484 match self
458485 . client ( request. client_cert . clone ( ) , self . enable_hickory_dns ) ?
459- . head ( & target_url )
486+ . head ( & request_url )
460487 . headers ( request_header. clone ( ) )
461488 . timeout ( request. timeout )
462489 . send ( )
@@ -466,7 +493,7 @@ impl Backend for HTTP {
466493 Err ( err) => {
467494 error ! (
468495 "stat request failed with HEAD {} {}: {}" ,
469- request. task_id, target_url , err
496+ request. task_id, request_url , err
470497 ) ;
471498
472499 return Ok ( StatResponse {
@@ -484,7 +511,7 @@ impl Backend for HTTP {
484511 Err ( err) => {
485512 error ! (
486513 "stat request failed with GET {} {}: {}" ,
487- request. task_id, target_url , err
514+ request. task_id, request_url , err
488515 ) ;
489516
490517 return Ok ( StatResponse {
@@ -507,7 +534,7 @@ impl Backend for HTTP {
507534
508535 debug ! (
509536 "stat response {} {}: {:?} {:?} {:?}" ,
510- request. task_id, target_url , response_status_code, content_length, response_header
537+ request. task_id, request_url , response_status_code, content_length, response_header
511538 ) ;
512539
513540 // Drop the response body to avoid reading it.
@@ -542,10 +569,24 @@ impl Backend for HTTP {
542569 self . make_request_headers ( & mut request_header, request. range ) ?;
543570
544571 // Check if we have a cached temporary redirect for this URL.
545- let target_url = self . get_temporary_redirect_url ( & request. url ) . await ;
572+ let ( request_url, request_header) =
573+ match self . get_temporary_redirect_url ( & request. url ) . await {
574+ Some ( redirect_url) => {
575+ let mut redirect_headers = request_header. clone ( ) ;
576+ remove_sensitive_headers (
577+ & mut redirect_headers,
578+ & redirect_url. parse ( ) ?,
579+ & request. url . parse ( ) ?,
580+ ) ;
581+
582+ ( redirect_url, redirect_headers)
583+ }
584+ None => ( request. url . clone ( ) , request_header) ,
585+ } ;
586+
546587 let mut response = match self
547588 . client ( request. client_cert . clone ( ) , self . enable_hickory_dns ) ?
548- . get ( & target_url )
589+ . get ( & request_url )
549590 . headers ( request_header. clone ( ) )
550591 . timeout ( request. timeout )
551592 . send ( )
@@ -555,7 +596,7 @@ impl Backend for HTTP {
555596 Err ( err) => {
556597 error ! (
557598 "get request failed {} {} {}: {}" ,
558- request. task_id, request. piece_id, target_url , err
599+ request. task_id, request. piece_id, request_url , err
559600 ) ;
560601
561602 return Ok ( GetResponse {
@@ -571,14 +612,27 @@ impl Backend for HTTP {
571612 // If the response is a 307 Temporary Redirect, follow the redirect manually.
572613 if response. status ( ) == reqwest:: StatusCode :: TEMPORARY_REDIRECT {
573614 if let Some ( location) = response. headers ( ) . get ( LOCATION ) {
574- let target_url = location. to_str ( ) . or_err ( ErrorType :: ParseError ) ?;
575- self . store_temporary_redirect_url ( & request. url , target_url)
615+ let redirect_url = location. to_str ( ) . or_err ( ErrorType :: ParseError ) ?;
616+ debug ! (
617+ "get request got 307 Temporary Redirect, following redirect {} -> {}" ,
618+ request. url, redirect_url
619+ ) ;
620+
621+ self . store_temporary_redirect_url ( & request. url , redirect_url)
576622 . await ;
577623
624+ // Strips sensitive headers when following a cross-origin redirect.
625+ let mut redirect_headers = request_header. clone ( ) ;
626+ remove_sensitive_headers (
627+ & mut redirect_headers,
628+ & redirect_url. parse ( ) ?,
629+ & request. url . parse ( ) ?,
630+ ) ;
631+
578632 response = match self
579633 . client ( request. client_cert . clone ( ) , self . enable_hickory_dns ) ?
580- . get ( target_url )
581- . headers ( request_header . clone ( ) )
634+ . get ( redirect_url )
635+ . headers ( redirect_headers )
582636 . timeout ( request. timeout )
583637 . send ( )
584638 . await
@@ -587,7 +641,7 @@ impl Backend for HTTP {
587641 Err ( err) => {
588642 error ! (
589643 "get request failed {} {} {}: {}" ,
590- request. task_id, request. piece_id, target_url , err
644+ request. task_id, request. piece_id, redirect_url , err
591645 ) ;
592646
593647 return Ok ( GetResponse {
@@ -717,6 +771,25 @@ impl Backend for HTTP {
717771 }
718772}
719773
774+ /// Strips sensitive headers when following a cross-origin redirect.
775+ ///
776+ /// This replicates the behavior of reqwest's internal `remove_sensitive_headers`:
777+ /// https://github.com/seanmonstar/reqwest/blob/v0.12.15/src/redirect.rs#L134
778+ ///
779+ /// Per RFC 9110 §15.4, user agents SHOULD strip credentials when redirecting
780+ /// to a different origin.
781+ fn remove_sensitive_headers ( headers : & mut HeaderMap , next : & Url , previous : & Url ) {
782+ if next. host ( ) != previous. host ( )
783+ || next. port_or_known_default ( ) != previous. port_or_known_default ( )
784+ {
785+ headers. remove ( reqwest:: header:: AUTHORIZATION ) ;
786+ headers. remove ( reqwest:: header:: COOKIE ) ;
787+ headers. remove ( "cookie2" ) ;
788+ headers. remove ( reqwest:: header:: PROXY_AUTHORIZATION ) ;
789+ headers. remove ( reqwest:: header:: WWW_AUTHENTICATE ) ;
790+ }
791+ }
792+
720793#[ cfg( test) ]
721794mod tests {
722795 use super :: * ;
0 commit comments