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}