11//! Easy file downloading
22
33use std:: fs:: remove_file;
4+ use std:: num:: NonZeroU64 ;
45use std:: path:: Path ;
6+ use std:: str:: FromStr ;
7+ use std:: time:: Duration ;
58
69use anyhow:: Context ;
710#[ cfg( any(
@@ -194,6 +197,13 @@ async fn download_file_(
194197 _ => Backend :: Curl ,
195198 } ;
196199
200+ let timeout = Duration :: from_secs ( match process. var ( "RUSTUP_DOWNLOAD_TIMEOUT" ) {
201+ Ok ( s) => NonZeroU64 :: from_str ( & s) . context (
202+ "invalid value in RUSTUP_DOWNLOAD_TIMEOUT -- must be a natural number greater than zero" ,
203+ ) ?. get ( ) ,
204+ Err ( _) => 180 ,
205+ } ) ;
206+
197207 notify_handler ( match backend {
198208 #[ cfg( feature = "curl-backend" ) ]
199209 Backend :: Curl => Notification :: UsingCurl ,
@@ -202,7 +212,7 @@ async fn download_file_(
202212 } ) ;
203213
204214 let res = backend
205- . download_to_path ( url, path, resume_from_partial, Some ( callback) )
215+ . download_to_path ( url, path, resume_from_partial, Some ( callback) , timeout )
206216 . await ;
207217
208218 notify_handler ( Notification :: DownloadFinished ) ;
@@ -241,9 +251,10 @@ impl Backend {
241251 path : & Path ,
242252 resume_from_partial : bool ,
243253 callback : Option < DownloadCallback < ' _ > > ,
254+ timeout : Duration ,
244255 ) -> anyhow:: Result < ( ) > {
245256 let Err ( err) = self
246- . download_impl ( url, path, resume_from_partial, callback)
257+ . download_impl ( url, path, resume_from_partial, callback, timeout )
247258 . await
248259 else {
249260 return Ok ( ( ) ) ;
@@ -265,6 +276,7 @@ impl Backend {
265276 path : & Path ,
266277 resume_from_partial : bool ,
267278 callback : Option < DownloadCallback < ' _ > > ,
279+ timeout : Duration ,
268280 ) -> anyhow:: Result < ( ) > {
269281 use std:: cell:: RefCell ;
270282 use std:: fs:: OpenOptions ;
@@ -324,7 +336,7 @@ impl Backend {
324336 let file = RefCell :: new ( file) ;
325337
326338 // TODO: the sync callback will stall the async runtime if IO calls block, which is OS dependent. Rearrange.
327- self . download ( url, resume_from, & |event| {
339+ self . download ( url, resume_from, timeout , & |event| {
328340 if let Event :: DownloadDataReceived ( data) = event {
329341 file. borrow_mut ( )
330342 . write_all ( data)
@@ -356,13 +368,14 @@ impl Backend {
356368 self ,
357369 url : & Url ,
358370 resume_from : u64 ,
371+ timeout : Duration ,
359372 callback : DownloadCallback < ' _ > ,
360373 ) -> anyhow:: Result < ( ) > {
361374 match self {
362375 #[ cfg( feature = "curl-backend" ) ]
363- Self :: Curl => curl:: download ( url, resume_from, callback) ,
376+ Self :: Curl => curl:: download ( url, resume_from, callback, timeout ) ,
364377 #[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
365- Self :: Reqwest ( tls) => tls. download ( url, resume_from, callback) . await ,
378+ Self :: Reqwest ( tls) => tls. download ( url, resume_from, callback, timeout ) . await ,
366379 }
367380 }
368381}
@@ -383,12 +396,13 @@ impl TlsBackend {
383396 url : & Url ,
384397 resume_from : u64 ,
385398 callback : DownloadCallback < ' _ > ,
399+ timeout : Duration ,
386400 ) -> anyhow:: Result < ( ) > {
387401 let client = match self {
388402 #[ cfg( feature = "reqwest-rustls-tls" ) ]
389- Self :: Rustls => reqwest_be:: rustls_client ( ) ?,
403+ Self :: Rustls => reqwest_be:: rustls_client ( timeout ) ?,
390404 #[ cfg( feature = "reqwest-native-tls" ) ]
391- Self :: NativeTls => reqwest_be:: native_tls_client ( ) ?,
405+ Self :: NativeTls => reqwest_be:: native_tls_client ( timeout ) ?,
392406 } ;
393407
394408 reqwest_be:: download ( url, resume_from, callback, client) . await
@@ -424,6 +438,7 @@ mod curl {
424438 url : & Url ,
425439 resume_from : u64 ,
426440 callback : & dyn Fn ( Event < ' _ > ) -> Result < ( ) > ,
441+ timeout : Duration ,
427442 ) -> Result < ( ) > {
428443 // Fetch either a cached libcurl handle (which will preserve open
429444 // connections) or create a new one if it isn't listed.
@@ -446,8 +461,8 @@ mod curl {
446461 let _ = handle. resume_from ( 0 ) ;
447462 }
448463
449- // Take at most 30s to connect
450- handle. connect_timeout ( Duration :: new ( 30 , 0 ) ) ?;
464+ // Take at most 3m to connect if the `RUSTUP_DOWNLOAD_TIMEOUT` env var is not set.
465+ handle. connect_timeout ( timeout ) ?;
451466
452467 {
453468 let cberr = RefCell :: new ( None ) ;
@@ -584,11 +599,11 @@ mod reqwest_be {
584599 . pool_max_idle_per_host ( 0 )
585600 . gzip ( false )
586601 . proxy ( Proxy :: custom ( env_proxy) )
587- . read_timeout ( Duration :: from_secs ( 30 ) )
588602 }
589603
590604 #[ cfg( feature = "reqwest-rustls-tls" ) ]
591- pub ( super ) fn rustls_client ( ) -> Result < & ' static Client , DownloadError > {
605+ pub ( super ) fn rustls_client ( timeout : Duration ) -> Result < & ' static Client , DownloadError > {
606+ // If the client is already initialized, the passed timeout is ignored.
592607 if let Some ( client) = CLIENT_RUSTLS_TLS . get ( ) {
593608 return Ok ( client) ;
594609 }
@@ -605,6 +620,7 @@ mod reqwest_be {
605620 tls_config. alpn_protocols = vec ! [ b"h2" . to_vec( ) , b"http/1.1" . to_vec( ) ] ;
606621
607622 let client = client_generic ( )
623+ . read_timeout ( timeout)
608624 . use_preconfigured_tls ( tls_config)
609625 . user_agent ( super :: REQWEST_RUSTLS_TLS_USER_AGENT )
610626 . build ( )
@@ -620,12 +636,14 @@ mod reqwest_be {
620636 static CLIENT_RUSTLS_TLS : OnceLock < Client > = OnceLock :: new ( ) ;
621637
622638 #[ cfg( feature = "reqwest-native-tls" ) ]
623- pub ( super ) fn native_tls_client ( ) -> Result < & ' static Client , DownloadError > {
639+ pub ( super ) fn native_tls_client ( timeout : Duration ) -> Result < & ' static Client , DownloadError > {
640+ // If the client is already initialized, the passed timeout is ignored.
624641 if let Some ( client) = CLIENT_NATIVE_TLS . get ( ) {
625642 return Ok ( client) ;
626643 }
627644
628645 let client = client_generic ( )
646+ . read_timeout ( timeout)
629647 . user_agent ( super :: REQWEST_DEFAULT_TLS_USER_AGENT )
630648 . build ( )
631649 . map_err ( DownloadError :: Reqwest ) ?;
0 commit comments