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!(url, "/{}/{}/download", pkg.name(), pkg.version()).unwrap();
76    } else {
77        let prefix = make_dep_path(&pkg.name(), true);
78        url = url
79            .replace(CRATE_TEMPLATE, &*pkg.name())
80            .replace(VERSION_TEMPLATE, &pkg.version().to_string())
81            .replace(PREFIX_TEMPLATE, &prefix)
82            .replace(LOWER_PREFIX_TEMPLATE, &prefix.to_lowercase())
83            .replace(CHECKSUM_TEMPLATE, checksum);
84    }
85
86    let authorization = if registry_config.auth_required {
87        Some(auth::auth_token(
88            gctx,
89            &pkg.source_id(),
90            None,
91            Operation::Read,
92            vec![],
93            true,
94        )?)
95    } else {
96        None
97    };
98
99    Ok(MaybeLock::Download {
100        url,
101        descriptor: pkg.to_string(),
102        authorization: authorization,
103    })
104}
105
106/// Verifies the integrity of `data` with `checksum` and persists it under the
107/// directory at `cache_path`.
108///
109/// This is primarily called by [`RegistryData::finish_download`](super::RegistryData::finish_download).
110pub(super) fn finish_download(
111    cache_path: &Filesystem,
112    gctx: &GlobalContext,
113    encoded_registry_name: InternedString,
114    pkg: PackageId,
115    checksum: &str,
116    data: &[u8],
117) -> CargoResult<File> {
118    // Verify what we just downloaded
119    let actual = Sha256::new().update(data).finish_hex();
120    if actual != checksum {
121        anyhow::bail!("failed to verify the checksum of `{}`", pkg)
122    }
123    gctx.deferred_global_last_use()?.mark_registry_crate_used(
124        global_cache_tracker::RegistryCrate {
125            encoded_registry_name,
126            crate_filename: pkg.tarball_name().into(),
127            size: data.len() as u64,
128        },
129    );
130
131    cache_path.create_dir()?;
132    let path = cache_path.join(&pkg.tarball_name());
133    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
134    let mut dst = OpenOptions::new()
135        .create(true)
136        .read(true)
137        .write(true)
138        .open(&path)
139        .with_context(|| format!("failed to open `{}`", path.display()))?;
140    let meta = dst.metadata()?;
141    if meta.len() > 0 {
142        return Ok(dst);
143    }
144
145    dst.write_all(data)?;
146    dst.seek(SeekFrom::Start(0))?;
147    Ok(dst)
148}
149
150/// Checks if a tarball of `pkg` has been already downloaded under the
151/// directory at `cache_path`.
152///
153/// This is primarily called by [`RegistryData::is_crate_downloaded`](super::RegistryData::is_crate_downloaded).
154pub(super) fn is_crate_downloaded(
155    cache_path: &Filesystem,
156    gctx: &GlobalContext,
157    pkg: PackageId,
158) -> bool {
159    let path = cache_path.join(pkg.tarball_name());
160    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
161    if let Ok(meta) = fs::metadata(path) {
162        return meta.len() > 0;
163    }
164    false
165}