Skip to content

Commit 3788021

Browse files
committed
chore(download): add functions to fetch remote size
1 parent a0e32f5 commit 3788021

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

src/download/mod.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,27 @@ async fn download_file_(
240240
res
241241
}
242242

243+
pub(crate) async fn content_length(url: &Url, process: &Process) -> anyhow::Result<Option<u64>> {
244+
if url.scheme() == "file" {
245+
let path = url
246+
.to_file_path()
247+
.map_err(|_| anyhow::anyhow!("bogus file url: '{url}'"))?;
248+
return Ok(Some(std::fs::metadata(path)?.len()));
249+
}
250+
251+
let backend = select_backend(process)?;
252+
let timeout = timeout(process)?;
253+
254+
match backend {
255+
#[cfg(feature = "curl-backend")]
256+
Backend::Curl => debug!(url = %url, "fetching content-length with curl"),
257+
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
258+
Backend::Reqwest(_) => debug!(url = %url, "fetching content-length with reqwest"),
259+
};
260+
261+
backend.content_length(url, timeout).await
262+
}
263+
243264
/// User agent header value for HTTP request.
244265
/// See: https://github.com/rust-lang/rustup/issues/2860.
245266
#[cfg(feature = "curl-backend")]
@@ -402,6 +423,15 @@ impl Backend {
402423
Self::Reqwest(tls) => tls.download(url, resume_from, callback, timeout).await,
403424
}
404425
}
426+
427+
async fn content_length(self, url: &Url, timeout: Duration) -> anyhow::Result<Option<u64>> {
428+
match self {
429+
#[cfg(feature = "curl-backend")]
430+
Self::Curl => curl::content_length(url, timeout),
431+
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
432+
Self::Reqwest(tls) => tls.content_length(url, timeout).await,
433+
}
434+
}
405435
}
406436

407437
#[cfg(any(feature = "reqwest-rustls-tls", feature = "reqwest-native-tls"))]
@@ -431,6 +461,17 @@ impl TlsBackend {
431461

432462
reqwest_be::download(url, resume_from, callback, client).await
433463
}
464+
465+
async fn content_length(self, url: &Url, timeout: Duration) -> anyhow::Result<Option<u64>> {
466+
let client = match self {
467+
#[cfg(feature = "reqwest-rustls-tls")]
468+
Self::Rustls => reqwest_be::rustls_client(timeout)?,
469+
#[cfg(feature = "reqwest-native-tls")]
470+
Self::NativeTls => reqwest_be::native_tls_client(timeout)?,
471+
};
472+
473+
reqwest_be::content_length(url, client).await
474+
}
434475
}
435476

436477
#[derive(Debug, Copy, Clone)]
@@ -448,6 +489,7 @@ type DownloadCallback<'a> = &'a dyn Fn(Event<'_>) -> anyhow::Result<()>;
448489
/// stack via libcurl
449490
#[cfg(feature = "curl-backend")]
450491
mod curl {
492+
use std::cell::Cell;
451493
use std::cell::RefCell;
452494
use std::str;
453495
use std::time::Duration;
@@ -458,6 +500,46 @@ mod curl {
458500

459501
use super::{DownloadError, Event};
460502

503+
pub(super) fn content_length(url: &Url, timeout: Duration) -> Result<Option<u64>> {
504+
let mut handle = Easy::new();
505+
handle.url(url.as_ref())?;
506+
handle.follow_location(true)?;
507+
handle.useragent(super::CURL_USER_AGENT)?;
508+
handle.nobody(true)?;
509+
handle.connect_timeout(timeout)?;
510+
511+
let length = Cell::new(None);
512+
{
513+
let mut transfer = handle.transfer();
514+
transfer.header_function(|header| {
515+
let Ok(data) = str::from_utf8(header) else {
516+
return true;
517+
};
518+
let prefix = "content-length: ";
519+
let Some((dp, ds)) = data.split_at_checked(prefix.len()) else {
520+
return true;
521+
};
522+
if !dp.eq_ignore_ascii_case(prefix) {
523+
return true;
524+
}
525+
if let Ok(s) = ds.trim().parse::<u64>() {
526+
length.set(Some(s));
527+
}
528+
true
529+
})?;
530+
531+
transfer.perform()?;
532+
}
533+
534+
let code = handle.response_code()?;
535+
match code {
536+
0 | 200..=299 => Ok(length.get()),
537+
// Some servers do not support HEAD for file assets
538+
405 => Ok(None),
539+
_ => Err(DownloadError::HttpStatus(code).into()),
540+
}
541+
}
542+
461543
pub(super) fn download(
462544
url: &Url,
463545
resume_from: u64,
@@ -620,6 +702,22 @@ mod reqwest_be {
620702
Ok(())
621703
}
622704

705+
pub(super) async fn content_length(url: &Url, client: &Client) -> anyhow::Result<Option<u64>> {
706+
let res = client
707+
.head(url.as_str())
708+
.send()
709+
.await
710+
.context("error fetching content length")?;
711+
712+
let status = res.status().into();
713+
match status {
714+
200..=299 => Ok(res.content_length()),
715+
// Some servers do not support HEAD for file assets
716+
405 => Ok(None),
717+
_ => Err(DownloadError::HttpStatus(u32::from(status)).into()),
718+
}
719+
}
720+
623721
fn client_generic() -> ClientBuilder {
624722
Client::builder()
625723
// HACK: set `pool_max_idle_per_host` to `0` to avoid an issue in the underlying

0 commit comments

Comments
 (0)