@@ -240,6 +240,28 @@ async fn download_file_(
240240 res
241241}
242242
243+ #[ allow( dead_code) ]
244+ pub ( crate ) async fn content_length ( url : & Url , process : & Process ) -> anyhow:: Result < Option < u64 > > {
245+ if url. scheme ( ) == "file" {
246+ let path = url
247+ . to_file_path ( )
248+ . map_err ( |_| anyhow:: anyhow!( "bogus file url: '{url}'" ) ) ?;
249+ return Ok ( Some ( std:: fs:: metadata ( path) ?. len ( ) ) ) ;
250+ }
251+
252+ let backend = select_backend ( process) ?;
253+ let timeout = timeout ( process) ?;
254+
255+ match backend {
256+ #[ cfg( feature = "curl-backend" ) ]
257+ Backend :: Curl => debug ! ( url = %url, "fetching content-length with curl" ) ,
258+ #[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
259+ Backend :: Reqwest ( _) => debug ! ( url = %url, "fetching content-length with reqwest" ) ,
260+ } ;
261+
262+ backend. content_length ( url, timeout) . await
263+ }
264+
243265/// User agent header value for HTTP request.
244266/// See: https://github.com/rust-lang/rustup/issues/2860.
245267#[ cfg( feature = "curl-backend" ) ]
@@ -402,6 +424,16 @@ impl Backend {
402424 Self :: Reqwest ( tls) => tls. download ( url, resume_from, callback, timeout) . await ,
403425 }
404426 }
427+
428+ #[ allow( dead_code) ]
429+ async fn content_length ( self , url : & Url , timeout : Duration ) -> anyhow:: Result < Option < u64 > > {
430+ match self {
431+ #[ cfg( feature = "curl-backend" ) ]
432+ Self :: Curl => curl:: content_length ( url, timeout) ,
433+ #[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
434+ Self :: Reqwest ( tls) => tls. content_length ( url, timeout) . await ,
435+ }
436+ }
405437}
406438
407439#[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
@@ -431,6 +463,18 @@ impl TlsBackend {
431463
432464 reqwest_be:: download ( url, resume_from, callback, client) . await
433465 }
466+
467+ #[ allow( dead_code) ]
468+ async fn content_length ( self , url : & Url , timeout : Duration ) -> anyhow:: Result < Option < u64 > > {
469+ let client = match self {
470+ #[ cfg( feature = "reqwest-rustls-tls" ) ]
471+ Self :: Rustls => reqwest_be:: rustls_client ( timeout) ?,
472+ #[ cfg( feature = "reqwest-native-tls" ) ]
473+ Self :: NativeTls => reqwest_be:: native_tls_client ( timeout) ?,
474+ } ;
475+
476+ reqwest_be:: content_length ( url, client) . await
477+ }
434478}
435479
436480#[ derive( Debug , Copy , Clone ) ]
@@ -458,6 +502,47 @@ mod curl {
458502
459503 use super :: { DownloadError , Event } ;
460504
505+ #[ allow( dead_code) ]
506+ pub ( super ) fn content_length ( url : & Url , timeout : Duration ) -> Result < Option < u64 > > {
507+ let mut handle = Easy :: new ( ) ;
508+ handle. url ( url. as_ref ( ) ) ?;
509+ handle. follow_location ( true ) ?;
510+ handle. useragent ( super :: CURL_USER_AGENT ) ?;
511+ handle. nobody ( true ) ?;
512+ handle. connect_timeout ( timeout) ?;
513+
514+ let length = std:: cell:: Cell :: new ( None ) ;
515+ {
516+ let mut transfer = handle. transfer ( ) ;
517+ transfer. header_function ( |header| {
518+ let Ok ( data) = str:: from_utf8 ( header) else {
519+ return true ;
520+ } ;
521+ let prefix = "content-length: " ;
522+ let Some ( ( dp, ds) ) = data. split_at_checked ( prefix. len ( ) ) else {
523+ return true ;
524+ } ;
525+ if !dp. eq_ignore_ascii_case ( prefix) {
526+ return true ;
527+ }
528+ if let Ok ( s) = ds. trim ( ) . parse :: < u64 > ( ) {
529+ length. set ( Some ( s) ) ;
530+ }
531+ true
532+ } ) ?;
533+
534+ transfer. perform ( ) ?;
535+ }
536+
537+ let code = handle. response_code ( ) ?;
538+ match code {
539+ 0 | 200 ..=299 => Ok ( length. get ( ) ) ,
540+ // Some servers do not support HEAD for file assets
541+ 405 => Ok ( None ) ,
542+ _ => Err ( DownloadError :: HttpStatus ( code) . into ( ) ) ,
543+ }
544+ }
545+
461546 pub ( super ) fn download (
462547 url : & Url ,
463548 resume_from : u64 ,
@@ -620,6 +705,23 @@ mod reqwest_be {
620705 Ok ( ( ) )
621706 }
622707
708+ #[ allow( dead_code) ]
709+ pub ( super ) async fn content_length ( url : & Url , client : & Client ) -> anyhow:: Result < Option < u64 > > {
710+ let res = client
711+ . head ( url. as_str ( ) )
712+ . send ( )
713+ . await
714+ . context ( "error fetching content length" ) ?;
715+
716+ let status = res. status ( ) . into ( ) ;
717+ match status {
718+ 200 ..=299 => Ok ( res. content_length ( ) ) ,
719+ // Some servers do not support HEAD for file assets
720+ 405 => Ok ( None ) ,
721+ _ => Err ( DownloadError :: HttpStatus ( u32:: from ( status) ) . into ( ) ) ,
722+ }
723+ }
724+
623725 fn client_generic ( ) -> ClientBuilder {
624726 Client :: builder ( )
625727 // HACK: set `pool_max_idle_per_host` to `0` to avoid an issue in the underlying
0 commit comments