#
cargo/sources/registry/
download.rs

    1//! Shared download logic between [`HttpRegistry`] and [`RemoteRegistry`].
    2//!
    3//! [`HttpRegistry`]: super::http_remote::HttpRegistry
    4//! [`RemoteRegistry`]: super::remote::RemoteRegistry
    5
    6use crate::util::interning::InternedString;
    7use anyhow::Context as _;
    8use cargo_credential::Operation;
    9use cargo_util::registry::make_dep_path;
    10use cargo_util::Sha256;
    11
    12use crate::core::global_cache_tracker;
    13use crate::core::PackageId;
    14use crate::sources::registry::MaybeLock;
    15use crate::sources::registry::RegistryConfig;
    16use crate::util::auth;
    17use crate::util::cache_lock::CacheLockMode;
    18use crate::util::errors::CargoResult;
    19use crate::util::{Filesystem, GlobalContext};
    20use std::fmt::Write as FmtWrite;
    21use std::fs::{self, File, OpenOptions};
    22use std::io::prelude::*;
    23use std::io::SeekFrom;
    24use std::str;
    25
    26const CRATE_TEMPLATE: &str = "{crate}";
    27const VERSION_TEMPLATE: &str = "{version}";
    28const PREFIX_TEMPLATE: &str = "{prefix}";
    29const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
    30const CHECKSUM_TEMPLATE: &str = "{sha256-checksum}";
    31
    32/// Checks if `pkg` is downloaded and ready under the directory at `cache_path`.
    33/// If not, returns a URL to download it from.
    34///
    35/// This is primarily called by [`RegistryData::download`](super::RegistryData::download).
    36pub(super) fn download(
    37    cache_path: &Filesystem,
    38    gctx: &GlobalContext,
    39    encoded_registry_name: InternedString,
    40    pkg: PackageId,
    41    checksum: &str,
    42    registry_config: RegistryConfig,
    43) -> CargoResult<MaybeLock> {
    44    let path = cache_path.join(&pkg.tarball_name());
    45    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
    46
    47    // Attempt to open a read-only copy first to avoid an exclusive write
    48    // lock and also work with read-only filesystems. Note that we check the
    49    // length of the file like below to handle interrupted downloads.
    50    //
    51    // If this fails then we fall through to the exclusive path where we may
    52    // have to redownload the file.
    53    if let Ok(dst) = File::open(path) {
    54        let meta = dst.metadata()?;
    55        if meta.len() > 0 {
    56            gctx.deferred_global_last_use()?.mark_registry_crate_used(
    57                global_cache_tracker::RegistryCrate {
    58                    encoded_registry_name,
    59                    crate_filename: pkg.tarball_name().into(),
    60                    size: meta.len(),
    61                },
    62            );
    63            return Ok(MaybeLock::Ready(dst));
    64        }
    65    }
    66
    67    let mut url = registry_config.dl;
    68    if !url.contains(CRATE_TEMPLATE)
    69        && !url.contains(VERSION_TEMPLATE)
    70        && !url.contains(PREFIX_TEMPLATE)
    71        && !url.contains(LOWER_PREFIX_TEMPLATE)
    72        && !url.contains(CHECKSUM_TEMPLATE)
    73    {
    74        // Original format before customizing the download URL was supported.
    75        write!(
    76            url,
    77            "/{}/{}/download",
    78            pkg.name(),
    79            pkg.version().to_string()
    80        )
    81        .unwrap();
    82    } else {
    83        let prefix = make_dep_path(&pkg.name(), true);
    84        url = url
    85            .replace(CRATE_TEMPLATE, &*pkg.name())
    86            .replace(VERSION_TEMPLATE, &pkg.version().to_string())
    87            .replace(PREFIX_TEMPLATE, &prefix)
    88            .replace(LOWER_PREFIX_TEMPLATE, &prefix.to_lowercase())
    89            .replace(CHECKSUM_TEMPLATE, checksum);
    90    }
    91
    92    let authorization = if registry_config.auth_required {
    93        Some(auth::auth_token(
    94            gctx,
    95            &pkg.source_id(),
    96            None,
    97            Operation::Read,
    98            vec![],
    99            true,
    100        )?)
    101    } else {
    102        None
    103    };
    104
    105    Ok(MaybeLock::Download {
    106        url,
    107        descriptor: pkg.to_string(),
    108        authorization: authorization,
    109    })
    110}
    111
    112/// Verifies the integrity of `data` with `checksum` and persists it under the
    113/// directory at `cache_path`.
    114///
    115/// This is primarily called by [`RegistryData::finish_download`](super::RegistryData::finish_download).
    116pub(super) fn finish_download(
    117    cache_path: &Filesystem,
    118    gctx: &GlobalContext,
    119    encoded_registry_name: InternedString,
    120    pkg: PackageId,
    121    checksum: &str,
    122    data: &[u8],
    123) -> CargoResult<File> {
    124    // Verify what we just downloaded
    125    let actual = Sha256::new().update(data).finish_hex();
    126    if actual != checksum {
    127        anyhow::bail!("failed to verify the checksum of `{}`", pkg)
    128    }
    129    gctx.deferred_global_last_use()?.mark_registry_crate_used(
    130        global_cache_tracker::RegistryCrate {
    131            encoded_registry_name,
    132            crate_filename: pkg.tarball_name().into(),
    133            size: data.len() as u64,
    134        },
    135    );
    136
    137    cache_path.create_dir()?;
    138    let path = cache_path.join(&pkg.tarball_name());
    139    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
    140    let mut dst = OpenOptions::new()
    141        .create(true)
    142        .read(true)
    143        .write(true)
    144        .open(&path)
    145        .with_context(|| format!("failed to open `{}`", path.display()))?;
    146    let meta = dst.metadata()?;
    147    if meta.len() > 0 {
    148        return Ok(dst);
    149    }
    150
    151    dst.write_all(data)?;
    152    dst.seek(SeekFrom::Start(0))?;
    153    Ok(dst)
    154}
    155
    156/// Checks if a tarball of `pkg` has been already downloaded under the
    157/// directory at `cache_path`.
    158///
    159/// This is primarily called by [`RegistryData::is_crate_downloaded`](super::RegistryData::is_crate_downloaded).
    160pub(super) fn is_crate_downloaded(
    161    cache_path: &Filesystem,
    162    gctx: &GlobalContext,
    163    pkg: PackageId,
    164) -> bool {
    165    let path = cache_path.join(pkg.tarball_name());
    166    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
    167    if let Ok(meta) = fs::metadata(path) {
    168        return meta.len() > 0;
    169    }
    170    false
    171}