blob: 7864328354f44bd1962b0d4bd1e13ecb8e495d9e [file] [log] [blame]
//! Shared download logic between [`HttpRegistry`] and [`RemoteRegistry`].
//!
//! [`HttpRegistry`]: super::http_remote::HttpRegistry
//! [`RemoteRegistry`]: super::remote::RemoteRegistry
use anyhow::Context;
use cargo_credential::Operation;
use cargo_util::registry::make_dep_path;
use cargo_util::Sha256;
use crate::core::PackageId;
use crate::sources::registry::MaybeLock;
use crate::sources::registry::RegistryConfig;
use crate::util::auth;
use crate::util::cache_lock::CacheLockMode;
use crate::util::errors::CargoResult;
use crate::util::{Config, Filesystem};
use std::fmt::Write as FmtWrite;
use std::fs::{self, File, OpenOptions};
use std::io::prelude::*;
use std::io::SeekFrom;
use std::str;
const CRATE_TEMPLATE: &str = "{crate}";
const VERSION_TEMPLATE: &str = "{version}";
const PREFIX_TEMPLATE: &str = "{prefix}";
const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}";
/// Checks if `pkg` is downloaded and ready under the directory at `cache_path`.
/// If not, returns a URL to download it from.
///
/// This is primarily called by [`RegistryData::download`](super::RegistryData::download).
pub(super) fn download(
cache_path: &Filesystem,
config: &Config,
pkg: PackageId,
checksum: &str,
registry_config: RegistryConfig,
) -> CargoResult<MaybeLock> {
let path = cache_path.join(&pkg.tarball_name());
let path = config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
// Attempt to open a read-only copy first to avoid an exclusive write
// lock and also work with read-only filesystems. Note that we check the
// length of the file like below to handle interrupted downloads.
//
// If this fails then we fall through to the exclusive path where we may
// have to redownload the file.
if let Ok(dst) = File::open(path) {
let meta = dst.metadata()?;
if meta.len() > 0 {
return Ok(MaybeLock::Ready(dst));
}
}
let mut url = registry_config.dl;
if !url.contains(CRATE_TEMPLATE)
&& !url.contains(VERSION_TEMPLATE)
&& !url.contains(PREFIX_TEMPLATE)
&& !url.contains(LOWER_PREFIX_TEMPLATE)
&& !url.contains(CHECKSUM_TEMPLATE)
{
// Original format before customizing the download URL was supported.
write!(
url,
"/{}/{}/download",
pkg.name(),
pkg.version().to_string()
)
.unwrap();
} else {
let prefix = make_dep_path(&pkg.name(), true);
url = url
.replace(CRATE_TEMPLATE, &*pkg.name())
.replace(VERSION_TEMPLATE, &pkg.version().to_string())
.replace(PREFIX_TEMPLATE, &prefix)
.replace(LOWER_PREFIX_TEMPLATE, &prefix.to_lowercase())
.replace(CHECKSUM_TEMPLATE, checksum);
}
let authorization = if registry_config.auth_required {
Some(auth::auth_token(
config,
&pkg.source_id(),
None,
Operation::Read,
vec![],
true,
)?)
} else {
None
};
Ok(MaybeLock::Download {
url,
descriptor: pkg.to_string(),
authorization: authorization,
})
}
/// Verifies the integrity of `data` with `checksum` and persists it under the
/// directory at `cache_path`.
///
/// This is primarily called by [`RegistryData::finish_download`](super::RegistryData::finish_download).
pub(super) fn finish_download(
cache_path: &Filesystem,
config: &Config,
pkg: PackageId,
checksum: &str,
data: &[u8],
) -> CargoResult<File> {
// Verify what we just downloaded
let actual = Sha256::new().update(data).finish_hex();
if actual != checksum {
anyhow::bail!("failed to verify the checksum of `{}`", pkg)
}
cache_path.create_dir()?;
let path = cache_path.join(&pkg.tarball_name());
let path = config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
let mut dst = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&path)
.with_context(|| format!("failed to open `{}`", path.display()))?;
let meta = dst.metadata()?;
if meta.len() > 0 {
return Ok(dst);
}
dst.write_all(data)?;
dst.seek(SeekFrom::Start(0))?;
Ok(dst)
}
/// Checks if a tarball of `pkg` has been already downloaded under the
/// directory at `cache_path`.
///
/// This is primarily called by [`RegistryData::is_crate_downloaded`](super::RegistryData::is_crate_downloaded).
pub(super) fn is_crate_downloaded(
cache_path: &Filesystem,
config: &Config,
pkg: PackageId,
) -> bool {
let path = cache_path.join(pkg.tarball_name());
let path = config.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
if let Ok(meta) = fs::metadata(path) {
return meta.len() > 0;
}
false
}