Skip to content

Commit b3f8399

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

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

src/download/mod.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)