1
1
//! Easy file downloading
2
2
3
3
use std:: fs:: remove_file;
4
+ use std:: num:: NonZeroU64 ;
4
5
use std:: path:: Path ;
6
+ use std:: time:: Duration ;
5
7
6
8
use anyhow:: Context ;
7
9
#[ cfg( any(
@@ -194,6 +196,13 @@ async fn download_file_(
194
196
_ => Backend :: Curl ,
195
197
} ;
196
198
199
+ let timeout = Duration :: from_secs ( match process. var ( "RUSTUP_DOWNLOAD_TIMEOUT" ) {
200
+ Ok ( s) => s. parse :: < NonZeroU64 > ( ) . context (
201
+ "invalid value in RUSTUP_DOWNLOAD_TIMEOUT -- must be a natural number greater than zero" ,
202
+ ) ?. get ( ) ,
203
+ Err ( _) => 180 ,
204
+ } ) ;
205
+
197
206
notify_handler ( match backend {
198
207
#[ cfg( feature = "curl-backend" ) ]
199
208
Backend :: Curl => Notification :: UsingCurl ,
@@ -202,7 +211,7 @@ async fn download_file_(
202
211
} ) ;
203
212
204
213
let res = backend
205
- . download_to_path ( url, path, resume_from_partial, Some ( callback) )
214
+ . download_to_path ( url, path, resume_from_partial, Some ( callback) , timeout )
206
215
. await ;
207
216
208
217
notify_handler ( Notification :: DownloadFinished ) ;
@@ -241,9 +250,10 @@ impl Backend {
241
250
path : & Path ,
242
251
resume_from_partial : bool ,
243
252
callback : Option < DownloadCallback < ' _ > > ,
253
+ timeout : Duration ,
244
254
) -> anyhow:: Result < ( ) > {
245
255
let Err ( err) = self
246
- . download_impl ( url, path, resume_from_partial, callback)
256
+ . download_impl ( url, path, resume_from_partial, callback, timeout )
247
257
. await
248
258
else {
249
259
return Ok ( ( ) ) ;
@@ -265,6 +275,7 @@ impl Backend {
265
275
path : & Path ,
266
276
resume_from_partial : bool ,
267
277
callback : Option < DownloadCallback < ' _ > > ,
278
+ timeout : Duration ,
268
279
) -> anyhow:: Result < ( ) > {
269
280
use std:: cell:: RefCell ;
270
281
use std:: fs:: OpenOptions ;
@@ -324,7 +335,7 @@ impl Backend {
324
335
let file = RefCell :: new ( file) ;
325
336
326
337
// 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| {
338
+ self . download ( url, resume_from, timeout , & |event| {
328
339
if let Event :: DownloadDataReceived ( data) = event {
329
340
file. borrow_mut ( )
330
341
. write_all ( data)
@@ -356,13 +367,14 @@ impl Backend {
356
367
self ,
357
368
url : & Url ,
358
369
resume_from : u64 ,
370
+ timeout : Duration ,
359
371
callback : DownloadCallback < ' _ > ,
360
372
) -> anyhow:: Result < ( ) > {
361
373
match self {
362
374
#[ cfg( feature = "curl-backend" ) ]
363
- Self :: Curl => curl:: download ( url, resume_from, callback) ,
375
+ Self :: Curl => curl:: download ( url, resume_from, callback, timeout ) ,
364
376
#[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
365
- Self :: Reqwest ( tls) => tls. download ( url, resume_from, callback) . await ,
377
+ Self :: Reqwest ( tls) => tls. download ( url, resume_from, callback, timeout ) . await ,
366
378
}
367
379
}
368
380
}
@@ -383,12 +395,13 @@ impl TlsBackend {
383
395
url : & Url ,
384
396
resume_from : u64 ,
385
397
callback : DownloadCallback < ' _ > ,
398
+ timeout : Duration ,
386
399
) -> anyhow:: Result < ( ) > {
387
400
let client = match self {
388
401
#[ cfg( feature = "reqwest-rustls-tls" ) ]
389
- Self :: Rustls => reqwest_be:: rustls_client ( ) ?,
402
+ Self :: Rustls => reqwest_be:: rustls_client ( timeout ) ?,
390
403
#[ cfg( feature = "reqwest-native-tls" ) ]
391
- Self :: NativeTls => & reqwest_be:: CLIENT_NATIVE_TLS ,
404
+ Self :: NativeTls => reqwest_be:: native_tls_client ( timeout ) ? ,
392
405
} ;
393
406
394
407
reqwest_be:: download ( url, resume_from, callback, client) . await
@@ -424,6 +437,7 @@ mod curl {
424
437
url : & Url ,
425
438
resume_from : u64 ,
426
439
callback : & dyn Fn ( Event < ' _ > ) -> Result < ( ) > ,
440
+ timeout : Duration ,
427
441
) -> Result < ( ) > {
428
442
// Fetch either a cached libcurl handle (which will preserve open
429
443
// connections) or create a new one if it isn't listed.
@@ -446,8 +460,8 @@ mod curl {
446
460
let _ = handle. resume_from ( 0 ) ;
447
461
}
448
462
449
- // Take at most 30s to connect
450
- handle. connect_timeout ( Duration :: new ( 30 , 0 ) ) ?;
463
+ // Take at most 3m to connect if the `RUSTUP_DOWNLOAD_TIMEOUT` env var is not set.
464
+ handle. connect_timeout ( timeout ) ?;
451
465
452
466
{
453
467
let cberr = RefCell :: new ( None ) ;
@@ -526,9 +540,7 @@ mod curl {
526
540
#[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
527
541
mod reqwest_be {
528
542
use std:: io;
529
- #[ cfg( feature = "reqwest-native-tls" ) ]
530
- use std:: sync:: LazyLock ;
531
- #[ cfg( feature = "reqwest-rustls-tls" ) ]
543
+ #[ cfg( any( feature = "reqwest-rustls-tls" , feature = "reqwest-native-tls" ) ) ]
532
544
use std:: sync:: { Arc , OnceLock } ;
533
545
use std:: time:: Duration ;
534
546
@@ -586,11 +598,10 @@ mod reqwest_be {
586
598
. pool_max_idle_per_host ( 0 )
587
599
. gzip ( false )
588
600
. proxy ( Proxy :: custom ( env_proxy) )
589
- . read_timeout ( Duration :: from_secs ( 30 ) )
590
601
}
591
602
592
603
#[ cfg( feature = "reqwest-rustls-tls" ) ]
593
- pub ( super ) fn rustls_client ( ) -> Result < & ' static Client , DownloadError > {
604
+ pub ( super ) fn rustls_client ( timeout : Duration ) -> Result < & ' static Client , DownloadError > {
594
605
if let Some ( client) = CLIENT_RUSTLS_TLS . get ( ) {
595
606
return Ok ( client) ;
596
607
}
@@ -607,6 +618,7 @@ mod reqwest_be {
607
618
tls_config. alpn_protocols = vec ! [ b"h2" . to_vec( ) , b"http/1.1" . to_vec( ) ] ;
608
619
609
620
let client = client_generic ( )
621
+ . read_timeout ( timeout)
610
622
. use_preconfigured_tls ( tls_config)
611
623
. user_agent ( super :: REQWEST_RUSTLS_TLS_USER_AGENT )
612
624
. build ( )
@@ -622,21 +634,24 @@ mod reqwest_be {
622
634
static CLIENT_RUSTLS_TLS : OnceLock < Client > = OnceLock :: new ( ) ;
623
635
624
636
#[ cfg( feature = "reqwest-native-tls" ) ]
625
- pub ( super ) static CLIENT_NATIVE_TLS : LazyLock < Client > = LazyLock :: new ( || {
626
- let catcher = || {
627
- client_generic ( )
628
- . user_agent ( super :: REQWEST_DEFAULT_TLS_USER_AGENT )
629
- . build ( )
630
- } ;
637
+ pub ( super ) fn native_tls_client ( timeout : Duration ) -> Result < & ' static Client , DownloadError > {
638
+ if let Some ( client) = CLIENT_NATIVE_TLS . get ( ) {
639
+ return Ok ( client) ;
640
+ }
631
641
632
- // woah, an unwrap?!
633
- // It's OK. This is the same as what is happening in curl.
634
- //
635
- // The curl::Easy::new() internally assert!s that the initialized
636
- // Easy is not null. Inside reqwest, the errors here would be from
637
- // the TLS library returning a null pointer as well.
638
- catcher ( ) . unwrap ( )
639
- } ) ;
642
+ let client = client_generic ( )
643
+ . read_timeout ( timeout)
644
+ . user_agent ( super :: REQWEST_DEFAULT_TLS_USER_AGENT )
645
+ . build ( )
646
+ . map_err ( DownloadError :: Reqwest ) ?;
647
+
648
+ let _ = CLIENT_NATIVE_TLS . set ( client) ;
649
+
650
+ Ok ( CLIENT_NATIVE_TLS . get ( ) . unwrap ( ) )
651
+ }
652
+
653
+ #[ cfg( feature = "reqwest-native-tls" ) ]
654
+ static CLIENT_NATIVE_TLS : OnceLock < Client > = OnceLock :: new ( ) ;
640
655
641
656
fn env_proxy ( url : & Url ) -> Option < Url > {
642
657
env_proxy:: for_url ( url) . to_url ( )
0 commit comments