Skip to main content

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::Sha256;
10use cargo_util::registry::crate_url;
11
12use crate::core::PackageId;
13use crate::core::global_cache_tracker;
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::fs::{self, File, OpenOptions};
21use std::io::SeekFrom;
22use std::io::prelude::*;
23use std::str;
24
25/// Checks if `pkg` is downloaded and ready under the directory at `cache_path`.
26/// If not, returns a URL to download it from.
27///
28/// This is primarily called by [`RegistryData::download`](super::RegistryData::download).
29pub(super) fn download(
30    cache_path: &Filesystem,
31    gctx: &GlobalContext,
32    encoded_registry_name: InternedString,
33    pkg: PackageId,
34    checksum: &str,
35    registry_config: RegistryConfig,
36) -> CargoResult<MaybeLock> {
37    let path = cache_path.join(&pkg.tarball_name());
38    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
39
40    // Attempt to open a read-only copy first to avoid an exclusive write
41    // lock and also work with read-only filesystems. Note that we check the
42    // length of the file like below to handle interrupted downloads.
43    //
44    // If this fails then we fall through to the exclusive path where we may
45    // have to redownload the file.
46    if let Ok(dst) = File::open(path) {
47        let meta = dst.metadata()?;
48        if meta.len() > 0 {
49            gctx.deferred_global_last_use()?.mark_registry_crate_used(
50                global_cache_tracker::RegistryCrate {
51                    encoded_registry_name,
52                    crate_filename: pkg.tarball_name().into(),
53                    size: meta.len(),
54                },
55            );
56            return Ok(MaybeLock::Ready(dst));
57        }
58    }
59
60    let url = crate_url(
61        &registry_config.dl,
62        &*pkg.name(),
63        &pkg.version().to_string(),
64        checksum,
65    );
66
67    let authorization = if registry_config.auth_required {
68        Some(auth::auth_token(
69            gctx,
70            &pkg.source_id(),
71            None,
72            Operation::Read,
73            vec![],
74            true,
75        )?)
76    } else {
77        None
78    };
79
80    Ok(MaybeLock::Download {
81        url,
82        descriptor: pkg.to_string(),
83        authorization: authorization,
84    })
85}
86
87/// Verifies the integrity of `data` with `checksum` and persists it under the
88/// directory at `cache_path`.
89///
90/// This is primarily called by [`RegistryData::finish_download`](super::RegistryData::finish_download).
91pub(super) fn finish_download(
92    cache_path: &Filesystem,
93    gctx: &GlobalContext,
94    encoded_registry_name: InternedString,
95    pkg: PackageId,
96    checksum: &str,
97    data: &[u8],
98) -> CargoResult<File> {
99    // Verify what we just downloaded
100    let actual = Sha256::new().update(data).finish_hex();
101    if actual != checksum {
102        anyhow::bail!("failed to verify the checksum of `{}`", pkg)
103    }
104    gctx.deferred_global_last_use()?.mark_registry_crate_used(
105        global_cache_tracker::RegistryCrate {
106            encoded_registry_name,
107            crate_filename: pkg.tarball_name().into(),
108            size: data.len() as u64,
109        },
110    );
111
112    cache_path.create_dir()?;
113    let path = cache_path.join(&pkg.tarball_name());
114    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
115    let mut dst = OpenOptions::new()
116        .create(true)
117        .read(true)
118        .write(true)
119        .open(&path)
120        .with_context(|| format!("failed to open `{}`", path.display()))?;
121    let meta = dst.metadata()?;
122    if meta.len() > 0 {
123        return Ok(dst);
124    }
125
126    dst.write_all(data)?;
127    dst.seek(SeekFrom::Start(0))?;
128    Ok(dst)
129}
130
131/// Checks if a tarball of `pkg` has been already downloaded under the
132/// directory at `cache_path`.
133///
134/// This is primarily called by [`RegistryData::is_crate_downloaded`](super::RegistryData::is_crate_downloaded).
135pub(super) fn is_crate_downloaded(
136    cache_path: &Filesystem,
137    gctx: &GlobalContext,
138    pkg: PackageId,
139) -> bool {
140    let path = cache_path.join(pkg.tarball_name());
141    let path = gctx.assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
142    if let Ok(meta) = fs::metadata(path) {
143        return meta.len() > 0;
144    }
145    false
146}