Skip to main content

cargo/sources/registry/
mod.rs

1//! A `Source` for registry-based packages.
2//!
3//! # What's a Registry?
4//!
5//! [Registries] are central locations where packages can be uploaded to,
6//! discovered, and searched for. The purpose of a registry is to have a
7//! location that serves as permanent storage for versions of a crate over time.
8//!
9//! Compared to git sources (see [`GitSource`]), a registry provides many
10//! packages as well as many versions simultaneously. Git sources can also
11//! have commits deleted through rebasings where registries cannot have their
12//! versions deleted.
13//!
14//! In Cargo, [`RegistryData`] is an abstraction over each kind of actual
15//! registry, and [`RegistrySource`] connects those implementations to
16//! [`Source`] trait. Two prominent features these abstractions provide are
17//!
18//! * A way to query the metadata of a package from a registry. The metadata
19//!   comes from the index.
20//! * A way to download package contents (a.k.a source files) that are required
21//!   when building the package itself.
22//!
23//! We'll cover each functionality later.
24//!
25//! [Registries]: https://doc.rust-lang.org/nightly/cargo/reference/registries.html
26//! [`GitSource`]: super::GitSource
27//!
28//! # Different Kinds of Registries
29//!
30//! Cargo provides multiple kinds of registries. Each of them serves the index
31//! and package contents in a slightly different way. Namely,
32//!
33//! * [`LocalRegistry`] --- Serves the index and package contents entirely on
34//!   a local filesystem.
35//! * [`GitRegistry`] --- Serves the index ahead of time from a Git
36//!   repository, and package contents are downloaded as needed.
37//! * [`HttpRegistry`] --- Serves both the index and package contents on demand
38//!   over a HTTP-based registry API. This is the default starting from 1.70.0.
39//!
40//! Each registry has its own [`RegistryData`] implementation, and can be
41//! created from either [`RegistrySource::local`] or [`RegistrySource::remote`].
42//!
43//! [`LocalRegistry`]: local::LocalRegistry
44//! [`GitRegistry`]: git_remote::GitRegistry
45//! [`HttpRegistry`]: http_remote::HttpRegistry
46//!
47//! # The Index of a Registry
48//!
49//! One of the major difficulties with a registry is that hosting so many
50//! packages may quickly run into performance problems when dealing with
51//! dependency graphs. It's infeasible for cargo to download the entire contents
52//! of the registry just to resolve one package's dependencies, for example. As
53//! a result, cargo needs some efficient method of querying what packages are
54//! available on a registry, what versions are available, and what the
55//! dependencies for each version is.
56//!
57//! To solve the problem, a registry must provide an index of package metadata.
58//! The index of a registry is essentially an easily query-able version of the
59//! registry's database for a list of versions of a package as well as a list
60//! of dependencies for each version. The exact format of the index is
61//! described later.
62//!
63//! See the [`index`] module for topics about the management, parsing, caching,
64//! and versioning for the on-disk index.
65//!
66//! ## The Format of The Index
67//!
68//! The index is a store for the list of versions for all packages known, so its
69//! format on disk is optimized slightly to ensure that `ls registry` doesn't
70//! produce a list of all packages ever known. The index also wants to ensure
71//! that there's not a million files which may actually end up hitting
72//! filesystem limits at some point. To this end, a few decisions were made
73//! about the format of the registry:
74//!
75//! 1. Each crate will have one file corresponding to it. Each version for a
76//!    crate will just be a line in this file (see [`cargo_util_schemas::index::IndexPackage`] for its
77//!    representation).
78//! 2. There will be two tiers of directories for crate names, under which
79//!    crates corresponding to those tiers will be located.
80//!    (See [`cargo_util::registry::make_dep_path`] for the implementation of
81//!    this layout hierarchy.)
82//!
83//! As an example, this is an example hierarchy of an index:
84//!
85//! ```notrust
86//! .
87//! ├── 3
88//! │   └── u
89//! │       └── url
90//! ├── bz
91//! │   └── ip
92//! │       └── bzip2
93//! ├── config.json
94//! ├── en
95//! │   └── co
96//! │       └── encoding
97//! └── li
98//!     ├── bg
99//!     │   └── libgit2
100//!     └── nk
101//!         └── link-config
102//! ```
103//!
104//! The root of the index contains a `config.json` file with a few entries
105//! corresponding to the registry (see [`RegistryConfig`] below).
106//!
107//! Otherwise, there are three numbered directories (1, 2, 3) for crates with
108//! names 1, 2, and 3 characters in length. The 1/2 directories simply have the
109//! crate files underneath them, while the 3 directory is sharded by the first
110//! letter of the crate name.
111//!
112//! Otherwise the top-level directory contains many two-letter directory names,
113//! each of which has many sub-folders with two letters. At the end of all these
114//! are the actual crate files themselves.
115//!
116//! The purpose of this layout is to hopefully cut down on `ls` sizes as well as
117//! efficient lookup based on the crate name itself.
118//!
119//! See [The Cargo Book: Registry Index][registry-index] for the public
120//! interface on the index format.
121//!
122//! [registry-index]: https://doc.rust-lang.org/nightly/cargo/reference/registry-index.html
123//!
124//! ## The Index Files
125//!
126//! Each file in the index is the history of one crate over time. Each line in
127//! the file corresponds to one version of a crate, stored in JSON format (see
128//! the [`cargo_util_schemas::index::IndexPackage`] structure).
129//!
130//! As new versions are published, new lines are appended to this file. **The
131//! only modifications to this file that should happen over time are yanks of a
132//! particular version.**
133//!
134//! # Downloading Packages
135//!
136//! The purpose of the index was to provide an efficient method to resolve the
137//! dependency graph for a package. After resolution has been performed, we need
138//! to download the contents of packages so we can read the full manifest and
139//! build the source code.
140//!
141//! To accomplish this, [`RegistryData::download`] will "make" an HTTP request
142//! per-package requested to download tarballs into a local cache. These
143//! tarballs will then be unpacked into a destination folder.
144//!
145//! Note that because versions uploaded to the registry are frozen forever that
146//! the HTTP download and unpacking can all be skipped if the version has
147//! already been downloaded and unpacked. This caching allows us to only
148//! download a package when absolutely necessary.
149//!
150//! # Filesystem Hierarchy
151//!
152//! Overall, the `$HOME/.cargo` looks like this when talking about the registry
153//! (remote registries, specifically):
154//!
155//! ```notrust
156//! # A folder under which all registry metadata is hosted (similar to
157//! # $HOME/.cargo/git)
158//! $HOME/.cargo/registry/
159//!
160//!     # For each registry that cargo knows about (keyed by hostname + hash)
161//!     # there is a folder which is the checked out version of the index for
162//!     # the registry in this location. Note that this is done so cargo can
163//!     # support multiple registries simultaneously
164//!     index/
165//!         registry1-<hash>/
166//!         registry2-<hash>/
167//!         ...
168//!
169//!     # This folder is a cache for all downloaded tarballs (`.crate` file)
170//!     # from a registry. Once downloaded and verified, a tarball never changes.
171//!     cache/
172//!         registry1-<hash>/<pkg>-<version>.crate
173//!         ...
174//!
175//!     # Location in which all tarballs are unpacked. Each tarball is known to
176//!     # be frozen after downloading, so transitively this folder is also
177//!     # frozen once its unpacked (it's never unpacked again)
178//!     # CAVEAT: They are not read-only. See rust-lang/cargo#9455.
179//!     src/
180//!         registry1-<hash>/<pkg>-<version>/...
181//!         ...
182//! ```
183//!
184
185use crate::util::data_structures::HashSet;
186use std::cell::RefCell;
187use std::fs;
188use std::fs::{File, OpenOptions};
189use std::io;
190use std::io::Read;
191use std::io::Write;
192use std::path::{Path, PathBuf};
193
194use anyhow::Context as _;
195use cargo_util::paths;
196use cargo_util_terminal::report::Level;
197use flate2::read::GzDecoder;
198use futures::FutureExt as _;
199use serde::Deserialize;
200use serde::Serialize;
201use tar::{Archive, EntryType};
202use tracing::debug;
203
204use crate::sources::PathSource;
205use crate::sources::source::MaybePackage;
206use crate::sources::source::QueryKind;
207use crate::sources::source::Source;
208use crate::util::VersionReqMatchMode;
209use crate::util::cache_lock::CacheLockMode;
210use crate::util::interning::InternedString;
211use crate::util::{CargoResult, Filesystem, GlobalContext, LimitErrorReader, restricted_names};
212use crate::util::{VersionExt, hex};
213use crate::workspace::dependency::Dependency;
214use crate::workspace::global_cache_tracker;
215use crate::workspace::{Package, PackageId, SourceId};
216
217pub use cargo_util_schemas::index::RegistryConfig;
218
219/// The `.cargo-ok` file is used to track if the source is already unpacked.
220/// See [`RegistrySource::unpack_package`] for more.
221///
222/// Not to be confused with `.cargo-ok` file in git sources.
223const PACKAGE_SOURCE_LOCK: &str = ".cargo-ok";
224
225pub const CRATES_IO_INDEX: &str = "https://github.com/rust-lang/crates.io-index";
226pub const CRATES_IO_HTTP_INDEX: &str = "sparse+https://index.crates.io/";
227pub const CRATES_IO_REGISTRY: &str = "crates-io";
228pub const CRATES_IO_DOMAIN: &str = "crates.io";
229
230/// The content inside `.cargo-ok`.
231/// See [`RegistrySource::unpack_package`] for more.
232#[derive(Deserialize, Serialize)]
233#[serde(rename_all = "kebab-case")]
234struct LockMetadata {
235    /// The version of `.cargo-ok` file
236    v: u32,
237}
238
239/// A [`Source`] implementation for a local or a remote registry.
240///
241/// This contains common functionality that is shared between each registry
242/// kind, with the registry-specific logic implemented as part of the
243/// [`RegistryData`] trait referenced via the `ops` field.
244///
245/// For general concepts of registries, see the [module-level documentation](crate::sources::registry).
246pub struct RegistrySource<'gctx> {
247    /// A unique name of the source (typically used as the directory name
248    /// where its cached content is stored).
249    name: InternedString,
250    /// The unique identifier of this source.
251    source_id: SourceId,
252    /// The path where crate files are extracted (`$CARGO_HOME/registry/src/$REG-HASH`).
253    src_path: Filesystem,
254    /// Local reference to [`GlobalContext`] for convenience.
255    gctx: &'gctx GlobalContext,
256    /// Abstraction for interfacing to the different registry kinds.
257    ops: Box<dyn RegistryData + 'gctx>,
258    /// Interface for managing the on-disk index.
259    index: index::RegistryIndex<'gctx>,
260    /// Yanked versions that have already been selected during queries.
261    ///
262    /// As of this writing, this is for not emitting the `--precise <yanked>`
263    /// warning twice, with the assumption of (`dep.package_name()` + `--precise`
264    /// version) being sufficient to uniquely identify the same query result.
265    selected_precise_yanked: RefCell<HashSet<(InternedString, semver::Version)>>,
266}
267
268/// Result from loading data from a registry.
269#[derive(Debug, Clone)]
270pub enum LoadResponse {
271    /// The cache is valid. The cached data should be used.
272    CacheValid,
273
274    /// The cache is out of date. Returned data should be used.
275    Data {
276        raw_data: Vec<u8>,
277        /// Version of this data to determine whether it is out of date.
278        index_version: Option<String>,
279    },
280
281    /// The requested crate was found.
282    NotFound,
283}
284
285/// An abstract interface to handle both a local and remote registry.
286///
287/// This allows [`RegistrySource`] to abstractly handle each registry kind.
288///
289/// For general concepts of registries, see the [module-level documentation](crate::sources::registry).
290#[async_trait::async_trait(?Send)]
291pub trait RegistryData {
292    /// Performs initialization for the registry.
293    ///
294    /// This should be safe to call multiple times, the implementation is
295    /// expected to not do any work if it is already prepared.
296    fn prepare(&self) -> CargoResult<()>;
297
298    /// Returns the path to the index.
299    ///
300    /// Note that different registries store the index in different formats
301    /// (remote = git, http & local = files).
302    fn index_path(&self) -> &Filesystem;
303
304    /// Returns the path of the directory that stores the cache of `.crate` files.
305    ///
306    /// The directory is currently expected to contain a flat list of all `.crate` files,
307    /// named `<package-name>-<version>.crate`.
308    fn cache_path(&self) -> &Filesystem;
309
310    /// Loads the JSON for a specific named package from the index.
311    ///
312    /// * `root` is the root path to the index.
313    /// * `path` is the relative path to the package to load (like `ca/rg/cargo`).
314    /// * `index_version` is the version of the requested crate data currently
315    ///    in cache. This is useful for checking if a local cache is outdated.
316    async fn load(
317        &self,
318        root: &Path,
319        path: &Path,
320        index_version: Option<&str>,
321    ) -> CargoResult<LoadResponse>;
322
323    /// Loads the `config.json` file and returns it.
324    ///
325    /// Local registries don't have a config, and return `None`.
326    async fn config(&self) -> CargoResult<Option<RegistryConfig>>;
327
328    /// Invalidates locally cached data.
329    fn invalidate_cache(&self);
330
331    /// If quiet, the source should not display any progress or status messages.
332    fn set_quiet(&mut self, quiet: bool);
333
334    /// Is the local cached data up-to-date?
335    fn is_updated(&self) -> bool;
336
337    /// Prepare to start downloading a `.crate` file.
338    ///
339    /// Despite the name, this doesn't actually download anything. If the
340    /// `.crate` is already downloaded, then it returns [`MaybeLock::Ready`].
341    /// If it hasn't been downloaded, then it returns [`MaybeLock::Download`]
342    /// which contains the URL to download. The [`crate::workspace::package::Downloads`]
343    /// system handles the actual download process. After downloading, it
344    /// calls [`Self::finish_download`] to save the downloaded file.
345    ///
346    /// `checksum` is currently only used by local registries to verify the
347    /// file contents (because local registries never actually download
348    /// anything). Remote registries will validate the checksum in
349    /// `finish_download`. For already downloaded `.crate` files, it does not
350    /// validate the checksum, assuming the filesystem does not suffer from
351    /// corruption or manipulation.
352    async fn download(&self, pkg: PackageId, checksum: &str) -> CargoResult<MaybeLock>;
353
354    /// Finish a download by saving a `.crate` file to disk.
355    ///
356    /// After [`crate::workspace::package::Downloads`] has finished a download,
357    /// it will call this to save the `.crate` file. This is only relevant
358    /// for remote registries. This should validate the checksum and save
359    /// the given data to the on-disk cache.
360    ///
361    /// Returns a [`File`] handle to the `.crate` file, positioned at the start.
362    async fn finish_download(
363        &self,
364        pkg: PackageId,
365        checksum: &str,
366        data: &[u8],
367    ) -> CargoResult<File>;
368
369    /// Returns whether or not the `.crate` file is already downloaded.
370    fn is_crate_downloaded(&self, _pkg: PackageId) -> bool {
371        true
372    }
373
374    /// Validates that the global package cache lock is held.
375    ///
376    /// Given the [`Filesystem`], this will make sure that the package cache
377    /// lock is held. If not, it will panic. See
378    /// [`GlobalContext::acquire_package_cache_lock`] for acquiring the global lock.
379    ///
380    /// Returns the [`Path`] to the [`Filesystem`].
381    fn assert_index_locked<'a>(&self, path: &'a Filesystem) -> &'a Path;
382}
383
384/// The status of [`RegistryData::download`] which indicates if a `.crate`
385/// file has already been downloaded, or if not then the URL to download.
386pub enum MaybeLock {
387    /// The `.crate` file is already downloaded. [`File`] is a handle to the
388    /// opened `.crate` file on the filesystem.
389    Ready(File),
390    /// The `.crate` file is not downloaded, here's the URL to download it from.
391    ///
392    /// `descriptor` is just a text string to display to the user of what is
393    /// being downloaded.
394    Download {
395        url: String,
396        descriptor: String,
397        authorization: Option<String>,
398    },
399}
400
401mod download;
402mod http_remote;
403pub(crate) mod index;
404pub use index::IndexSummary;
405mod git_remote;
406mod local;
407
408/// Generates a unique name for [`SourceId`] to have a unique path to put their
409/// index files.
410fn short_name(id: SourceId, is_shallow: bool) -> String {
411    // CAUTION: This should not change between versions. If you change how
412    // this is computed, it will orphan previously cached data, forcing the
413    // cache to be rebuilt and potentially wasting significant disk space. If
414    // you change it, be cautious of the impact. See `test_cratesio_hash` for
415    // a similar discussion.
416    let hash = hex::short_hash(&id);
417    let ident = id.url().host_str().unwrap_or("").to_string();
418    let mut name = format!("{}-{}", ident, hash);
419    if is_shallow {
420        name.push_str("-shallow");
421    }
422    name
423}
424
425impl<'gctx> RegistrySource<'gctx> {
426    /// Creates a [`Source`] of a "remote" registry.
427    /// It could be either an HTTP-based [`http_remote::HttpRegistry`] or
428    /// a Git-based [`git_remote::GitRegistry`].
429    pub fn remote(
430        source_id: SourceId,
431        gctx: &'gctx GlobalContext,
432    ) -> CargoResult<RegistrySource<'gctx>> {
433        assert!(source_id.is_remote_registry());
434        let name = short_name(
435            source_id,
436            gctx.cli_unstable()
437                .git
438                .map_or(false, |features| features.shallow_index)
439                && !source_id.is_sparse(),
440        );
441        let ops = if source_id.is_sparse() {
442            Box::new(http_remote::HttpRegistry::new(source_id, gctx, &name)?) as Box<_>
443        } else {
444            Box::new(git_remote::GitRegistry::new(source_id, gctx, &name)) as Box<_>
445        };
446
447        Ok(RegistrySource::new(source_id, gctx, &name, ops))
448    }
449
450    /// Creates a [`Source`] of a local registry, with [`local::LocalRegistry`] under the hood.
451    ///
452    /// * `path` --- The root path of a local registry on the file system.
453    pub fn local(
454        source_id: SourceId,
455        path: &Path,
456        gctx: &'gctx GlobalContext,
457    ) -> RegistrySource<'gctx> {
458        let name = short_name(source_id, false);
459        let ops = local::LocalRegistry::new(path, gctx, &name);
460        RegistrySource::new(source_id, gctx, &name, Box::new(ops))
461    }
462
463    /// Creates a source of a registry. This is a inner helper function.
464    ///
465    /// * `name` --- Name of a path segment which may affect where `.crate`
466    ///   tarballs, the registry index and cache are stored. Expect to be unique.
467    /// * `ops` --- The underlying [`RegistryData`] type.
468    fn new(
469        source_id: SourceId,
470        gctx: &'gctx GlobalContext,
471        name: &str,
472        ops: Box<dyn RegistryData + 'gctx>,
473    ) -> RegistrySource<'gctx> {
474        // Before starting to work on the registry, make sure that
475        // `<cargo_home>/registry` is marked as excluded from indexing and
476        // backups. Older versions of Cargo didn't do this, so we do it here
477        // regardless of whether `<cargo_home>` exists.
478        //
479        // This does not use `create_dir_all_excluded_from_backups_atomic` for
480        // the same reason: we want to exclude it even if the directory already
481        // exists.
482        //
483        // IO errors in creating and marking it are ignored, e.g. in case we're on a
484        // read-only filesystem.
485        let registry_base = gctx.registry_base_path();
486        let _ = registry_base.create_dir();
487        cargo_util::paths::exclude_from_backups_and_indexing(&registry_base.into_path_unlocked());
488
489        RegistrySource {
490            name: name.into(),
491            src_path: gctx.registry_source_path().join(name),
492            gctx,
493            source_id,
494            index: index::RegistryIndex::new(source_id, ops.index_path(), gctx),
495            ops,
496            selected_precise_yanked: RefCell::new(HashSet::default()),
497        }
498    }
499
500    /// Decode the [configuration](RegistryConfig) stored within the registry.
501    ///
502    /// This requires that the index has been at least checked out.
503    pub async fn config(&self) -> CargoResult<Option<RegistryConfig>> {
504        self.ops.config().await
505    }
506
507    /// Unpacks a downloaded package into a location where it's ready to be
508    /// compiled.
509    ///
510    /// No action is taken if the source looks like it's already unpacked.
511    ///
512    /// # History of interruption detection with `.cargo-ok` file
513    ///
514    /// Cargo has always included a `.cargo-ok` file ([`PACKAGE_SOURCE_LOCK`])
515    /// to detect if extraction was interrupted, but it was originally empty.
516    ///
517    /// In 1.34, Cargo was changed to create the `.cargo-ok` file before it
518    /// started extraction to implement fine-grained locking. After it was
519    /// finished extracting, it wrote two bytes to indicate it was complete.
520    /// It would use the length check to detect if it was possibly interrupted.
521    ///
522    /// In 1.36, Cargo changed to not use fine-grained locking, and instead used
523    /// a global lock. The use of `.cargo-ok` was no longer needed for locking
524    /// purposes, but was kept to detect when extraction was interrupted.
525    ///
526    /// In 1.49, Cargo changed to not create the `.cargo-ok` file before it
527    /// started extraction to deal with `.crate` files that inexplicably had
528    /// a `.cargo-ok` file in them.
529    ///
530    /// In 1.64, Cargo changed to detect `.crate` files with `.cargo-ok` files
531    /// in them in response to [CVE-2022-36113], which dealt with malicious
532    /// `.crate` files making `.cargo-ok` a symlink causing cargo to write "ok"
533    /// to any arbitrary file on the filesystem it has permission to.
534    ///
535    /// In 1.71, `.cargo-ok` changed to contain a JSON `{ v: 1 }` to indicate
536    /// the version of it. A failure of parsing will result in a heavy-hammer
537    /// approach that unpacks the `.crate` file again. This is in response to a
538    /// security issue that the unpacking didn't respect umask on Unix systems.
539    ///
540    /// This is all a long-winded way of explaining the circumstances that might
541    /// cause a directory to contain a `.cargo-ok` file that is empty or
542    /// otherwise corrupted. Either this was extracted by a version of Rust
543    /// before 1.34, in which case everything should be fine. However, an empty
544    /// file created by versions 1.36 to 1.49 indicates that the extraction was
545    /// interrupted and that we need to start again.
546    ///
547    /// Another possibility is that the filesystem is simply corrupted, in
548    /// which case deleting the directory might be the safe thing to do. That
549    /// is probably unlikely, though.
550    ///
551    /// To be safe, we delete the directory and start over again if an empty
552    /// `.cargo-ok` file is found.
553    ///
554    /// [CVE-2022-36113]: https://blog.rust-lang.org/2022/09/14/cargo-cves.html#arbitrary-file-corruption-cve-2022-36113
555    fn unpack_package(&self, pkg: PackageId, tarball: &File) -> CargoResult<PathBuf> {
556        let package_dir = format!("{}-{}", pkg.name(), pkg.version());
557        let dst = self.src_path.join(&package_dir);
558        let path = dst.join(PACKAGE_SOURCE_LOCK);
559        let path = self
560            .gctx
561            .assert_package_cache_locked(CacheLockMode::DownloadExclusive, &path);
562        let unpack_dir = path.parent().unwrap();
563        match fs::read_to_string(path) {
564            Ok(ok) => match serde_json::from_str::<LockMetadata>(&ok) {
565                Ok(lock_meta) if lock_meta.v == 1 => {
566                    self.gctx
567                        .deferred_global_last_use()?
568                        .mark_registry_src_used(global_cache_tracker::RegistrySrc {
569                            encoded_registry_name: self.name,
570                            package_dir: package_dir.into(),
571                            size: None,
572                        });
573                    return Ok(unpack_dir.to_path_buf());
574                }
575                _ => {
576                    if ok == "ok" {
577                        tracing::debug!("old `ok` content found, clearing cache");
578                    } else {
579                        tracing::warn!("unrecognized .cargo-ok content, clearing cache: {ok}");
580                    }
581                    // See comment of `unpack_package` about why removing all stuff.
582                    paths::remove_dir_all(dst.as_path_unlocked())?;
583                }
584            },
585            Err(e) if e.kind() == io::ErrorKind::NotFound => {}
586            Err(e) => anyhow::bail!("unable to read .cargo-ok file at {path:?}: {e}"),
587        }
588        dst.create_dir()?;
589
590        let bytes_written = unpack(self.gctx, tarball, unpack_dir, &|_| true)?;
591        update_mtime_for_generated_files(unpack_dir);
592
593        // Now that we've finished unpacking, create and write to the lock file to indicate that
594        // unpacking was successful.
595        let mut ok = OpenOptions::new()
596            .create_new(true)
597            .read(true)
598            .write(true)
599            .open(&path)
600            .with_context(|| format!("failed to open `{}`", path.display()))?;
601
602        let lock_meta = LockMetadata { v: 1 };
603        write!(ok, "{}", serde_json::to_string(&lock_meta).unwrap())?;
604
605        self.gctx
606            .deferred_global_last_use()?
607            .mark_registry_src_used(global_cache_tracker::RegistrySrc {
608                encoded_registry_name: self.name,
609                package_dir: package_dir.into(),
610                size: Some(bytes_written),
611            });
612
613        Ok(unpack_dir.to_path_buf())
614    }
615
616    /// Unpacks the `.crate` tarball of the package in a given directory.
617    ///
618    /// Returns the path to the crate tarball directory,
619    /// which is always `<unpack_dir>/<pkg>-<version>`.
620    ///
621    /// This holds some assumptions
622    ///
623    /// * The associated tarball already exists
624    /// * If this is a local registry,
625    ///   the package cache lock must be externally synchronized.
626    ///   Cargo does not take care of it being locked or not.
627    pub fn unpack_package_in(
628        &self,
629        pkg: &PackageId,
630        unpack_dir: &Path,
631        include: &dyn Fn(&Path) -> bool,
632    ) -> CargoResult<PathBuf> {
633        let path = self.ops.cache_path().join(pkg.tarball_name());
634        let path = self.ops.assert_index_locked(&path);
635        let dst = unpack_dir.join(format!("{}-{}", pkg.name(), pkg.version()));
636        let tarball =
637            File::open(path).with_context(|| format!("failed to open {}", path.display()))?;
638        unpack(self.gctx, &tarball, &dst, include)?;
639        update_mtime_for_generated_files(&dst);
640        Ok(dst)
641    }
642
643    /// Turns the downloaded `.crate` tarball file into a [`Package`].
644    ///
645    /// This unconditionally sets checksum for the returned package, so it
646    /// should only be called after doing integrity check. That is to say,
647    /// you need to call either [`RegistryData::download`] or
648    /// [`RegistryData::finish_download`] before calling this method.
649    async fn get_pkg(&self, package: PackageId, path: &File) -> CargoResult<Package> {
650        let path = self
651            .unpack_package(package, path)
652            .with_context(|| format!("failed to unpack package `{}`", package))?;
653        let src = PathSource::new(&path, self.source_id, self.gctx);
654        src.load()?;
655        let mut pkg = match src.download(package).await? {
656            MaybePackage::Ready(pkg) => pkg,
657            MaybePackage::Download { .. } => unreachable!(),
658        };
659
660        // After we've loaded the package configure its summary's `checksum`
661        // field with the checksum we know for this `PackageId`.
662        let cksum = self
663            .index
664            .hash(package, &*self.ops)
665            .now_or_never()
666            .expect("a downloaded dep now pending!?")
667            .expect("summary not found");
668        pkg.manifest_mut()
669            .summary_mut()
670            .set_checksum(cksum.to_string());
671
672        Ok(pkg)
673    }
674}
675
676#[async_trait::async_trait(?Send)]
677impl<'gctx> Source for RegistrySource<'gctx> {
678    async fn query(
679        &self,
680        dep: &Dependency,
681        kind: QueryKind,
682        f: &mut dyn FnMut(IndexSummary),
683    ) -> CargoResult<()> {
684        let mut req = dep.version_req().clone();
685
686        // Handle `cargo update --precise` here.
687        if let Some((_, requested)) = self
688            .source_id
689            .precise_registry_version(dep.package_name().as_str())
690            .filter(|(c, to)| {
691                // We use prerelease match mode for `--precise` when either
692                // "update to" or locked version is pre-release.
693                // Otherwise the entire `--precise` will be ignored
694                // because default mode doesn't match the locked prerelease version.
695                let mode = if (c.is_prerelease() || to.is_prerelease())
696                    && self.gctx.cli_unstable().prerelease
697                {
698                    VersionReqMatchMode::Prerelease
699                } else {
700                    VersionReqMatchMode::Default
701                };
702                req.matches(c, mode)
703            })
704        {
705            req.precise_to(&requested);
706        }
707
708        let mut called = false;
709        let callback = &mut |s| {
710            called = true;
711            f(s);
712        };
713
714        // If this is a locked dependency, then it came from a lock file and in
715        // theory the registry is known to contain this version. If, however, we
716        // come back with no summaries, then our registry may need to be
717        // updated, so we fall back to performing a lazy update.
718        if kind == QueryKind::Exact && req.is_locked() && !self.ops.is_updated() {
719            debug!("attempting query without update");
720            self.index
721                .query_inner(dep.package_name(), &req, &*self.ops, &mut |is| {
722                    match &is {
723                        IndexSummary::Candidate(s) | IndexSummary::Yanked(s)
724                            if dep.matches(&s, VersionReqMatchMode::Default) =>
725                        {
726                            // We are looking for a package from a lock file so we do not care about yank
727                            callback(is)
728                        }
729                        _ => {}
730                    }
731                })
732                .await?;
733            if called {
734                return Ok(());
735            } else {
736                debug!("falling back to an update");
737                self.invalidate_cache();
738            }
739        }
740
741        let mut called = false;
742        let callback = &mut |s| {
743            called = true;
744            f(s);
745        };
746
747        let mut precise_yanked_in_use = false;
748        self.index
749            .query_inner(dep.package_name(), &req, &*self.ops, &mut |s| {
750                let matched = match kind {
751                    QueryKind::Exact | QueryKind::RejectedVersions => {
752                        let s = match &s {
753                            IndexSummary::Candidate(s)
754                            | IndexSummary::Yanked(s)
755                            | IndexSummary::Offline(s)
756                            | IndexSummary::Unsupported(s, _)
757                            | IndexSummary::Invalid(s) => s,
758                        };
759                        let mode = if req.is_precise() && self.gctx.cli_unstable().prerelease {
760                            VersionReqMatchMode::Prerelease
761                        } else {
762                            VersionReqMatchMode::Default
763                        };
764                        dep.matches(&s, mode)
765                    }
766                    QueryKind::AlternativeNames => true,
767                    QueryKind::Normalized => true,
768                };
769                if !matched {
770                    return;
771                }
772                match s {
773                    s @ _ if kind == QueryKind::RejectedVersions => callback(s),
774                    s @ IndexSummary::Candidate(_) => callback(s),
775                    s @ IndexSummary::Yanked(_) => {
776                        // HACK: While source knows nothing about yank policy,
777                        // We still detect `cargo update --precise <yanked>`
778                        // so we can warn about the user-visible selection.
779                        //
780                        // We should consider also move this out from source query.
781                        if req.is_precise() {
782                            precise_yanked_in_use = true;
783                        }
784                        callback(s);
785                    }
786                    IndexSummary::Unsupported(summary, v) => {
787                        tracing::debug!(
788                            "unsupported schema version {} ({} {})",
789                            v,
790                            summary.name(),
791                            summary.version()
792                        );
793                    }
794                    IndexSummary::Invalid(summary) => {
795                        tracing::debug!("invalid ({} {})", summary.name(), summary.version());
796                    }
797                    IndexSummary::Offline(summary) => {
798                        tracing::debug!("offline ({} {})", summary.name(), summary.version());
799                    }
800                }
801            })
802            .await?;
803        if precise_yanked_in_use {
804            let name = dep.package_name();
805            let version = req
806                .precise_version()
807                .expect("--precise <yanked-version> in use");
808            if self
809                .selected_precise_yanked
810                .borrow_mut()
811                .insert((name, version.clone()))
812            {
813                let mut shell = self.gctx.shell();
814                shell.print_report(
815                    &[Level::WARNING
816                        .secondary_title(format!(
817                            "selected package `{name}@{version}` was yanked by the author"
818                        ))
819                        .element(
820                            Level::HELP.message("if possible, try a compatible non-yanked version"),
821                        )],
822                    false,
823                )?;
824            }
825        }
826        if called {
827            return Ok(());
828        }
829        if kind == QueryKind::AlternativeNames || kind == QueryKind::Normalized {
830            // Attempt to handle misspellings by searching for a chain of related
831            // names to the original name. The resolver will later
832            // reject any candidates that have the wrong name, and with this it'll
833            // have enough information to offer "a similar crate exists" suggestions.
834            // For now we only try canonicalizing `-` to `_` and vice versa.
835            // More advanced fuzzy searching become in the future.
836            for name_permutation in [
837                dep.package_name().replace('-', "_"),
838                dep.package_name().replace('_', "-"),
839            ] {
840                let name_permutation = name_permutation.into();
841                if name_permutation == dep.package_name() {
842                    continue;
843                }
844                self.index
845                    .query_inner(name_permutation, &req, &*self.ops, &mut |s| f(s))
846                    .await?;
847            }
848        }
849        Ok(())
850    }
851
852    fn supports_checksums(&self) -> bool {
853        true
854    }
855
856    fn requires_precise(&self) -> bool {
857        false
858    }
859
860    fn source_id(&self) -> SourceId {
861        self.source_id
862    }
863
864    fn invalidate_cache(&self) {
865        self.index.clear_summaries_cache();
866        self.ops.invalidate_cache();
867    }
868
869    fn set_quiet(&mut self, quiet: bool) {
870        self.ops.set_quiet(quiet);
871    }
872
873    async fn download(&self, package: PackageId) -> CargoResult<MaybePackage> {
874        let hash = self.index.hash(package, &*self.ops).await?;
875        match self.ops.download(package, &hash).await? {
876            MaybeLock::Ready(file) => self.get_pkg(package, &file).await.map(MaybePackage::Ready),
877            MaybeLock::Download {
878                url,
879                descriptor,
880                authorization,
881            } => Ok(MaybePackage::Download {
882                url,
883                descriptor,
884                authorization,
885            }),
886        }
887    }
888
889    async fn finish_download(&self, package: PackageId, data: Vec<u8>) -> CargoResult<Package> {
890        let hash = self.index.hash(package, &*self.ops).await?;
891        let file = self.ops.finish_download(package, &hash, &data).await?;
892        self.get_pkg(package, &file).await
893    }
894
895    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
896        Ok(pkg.package_id().version().to_string())
897    }
898
899    fn describe(&self) -> String {
900        self.source_id.display_index()
901    }
902}
903
904/// Get the maximum unpack size that Cargo permits
905/// based on a given `size` of your compressed file.
906///
907/// Returns the larger one between `size * max compression ratio`
908/// and a fixed max unpacked size.
909///
910/// In reality, the compression ratio usually falls in the range of 2:1 to 10:1.
911/// We choose 20:1 to cover almost all possible cases hopefully.
912/// Any ratio higher than this is considered as a zip bomb.
913///
914/// In the future we might want to introduce a configurable size.
915///
916/// Some of the real world data from common compression algorithms:
917///
918/// * <https://www.zlib.net/zlib_tech.html>
919/// * <https://cran.r-project.org/web/packages/brotli/vignettes/brotli-2015-09-22.pdf>
920/// * <https://blog.cloudflare.com/results-experimenting-brotli/>
921/// * <https://tukaani.org/lzma/benchmarks.html>
922fn max_unpack_size(gctx: &GlobalContext, size: u64) -> u64 {
923    const SIZE_VAR: &str = "__CARGO_TEST_MAX_UNPACK_SIZE";
924    const RATIO_VAR: &str = "__CARGO_TEST_MAX_UNPACK_RATIO";
925    const MAX_UNPACK_SIZE: u64 = 512 * 1024 * 1024; // 512 MiB
926    const MAX_COMPRESSION_RATIO: usize = 20; // 20:1
927
928    let max_unpack_size = if cfg!(debug_assertions) && gctx.get_env(SIZE_VAR).is_ok() {
929        // For integration test only.
930        gctx.get_env(SIZE_VAR)
931            .unwrap()
932            .parse()
933            .expect("a max unpack size in bytes")
934    } else {
935        MAX_UNPACK_SIZE
936    };
937    let max_compression_ratio = if cfg!(debug_assertions) && gctx.get_env(RATIO_VAR).is_ok() {
938        // For integration test only.
939        gctx.get_env(RATIO_VAR)
940            .unwrap()
941            .parse()
942            .expect("a max compression ratio in bytes")
943    } else {
944        MAX_COMPRESSION_RATIO
945    };
946
947    u64::max(max_unpack_size, size * max_compression_ratio as u64)
948}
949
950/// Set the current [`umask`] value for the given tarball. No-op on non-Unix
951/// platforms.
952///
953/// On Windows, tar only looks at user permissions and tries to set the "read
954/// only" attribute, so no-op as well.
955///
956/// [`umask`]: https://man7.org/linux/man-pages/man2/umask.2.html
957#[allow(unused_variables)]
958fn set_mask<R: Read>(tar: &mut Archive<R>) {
959    #[cfg(unix)]
960    tar.set_mask(crate::util::get_umask());
961}
962
963/// Unpack a tarball with zip bomb and overwrite protections.
964fn unpack(
965    gctx: &GlobalContext,
966    tarball: &File,
967    unpack_dir: &Path,
968    include: &dyn Fn(&Path) -> bool,
969) -> CargoResult<u64> {
970    let mut tar = {
971        let size_limit = max_unpack_size(gctx, tarball.metadata()?.len());
972        let gz = GzDecoder::new(tarball);
973        let gz = LimitErrorReader::new(gz, size_limit);
974        let mut tar = Archive::new(gz);
975        set_mask(&mut tar);
976        tar
977    };
978    let mut bytes_written = 0;
979    let prefix = unpack_dir.file_name().unwrap();
980    let parent = unpack_dir.parent().unwrap();
981    for entry in tar.entries()? {
982        let mut entry = entry.context("failed to iterate over archive")?;
983        let entry_path = entry
984            .path()
985            .context("failed to read entry path")?
986            .into_owned();
987
988        if let Ok(path) = entry_path.strip_prefix(prefix) {
989            if !include(path) {
990                continue;
991            }
992        } else {
993            // We're going to unpack this tarball into the global source
994            // directory, but we want to make sure that it doesn't accidentally
995            // (or maliciously) overwrite source code from other crates. Cargo
996            // itself should never generate a tarball that hits this error, and
997            // crates.io should also block uploads with these sorts of tarballs,
998            // but be extra sure by adding a check here as well.
999            anyhow::bail!(
1000                "invalid tarball downloaded, contains \
1001                     a file at {entry_path:?} which isn't under {prefix:?}",
1002            )
1003        }
1004
1005        // Prevent unpacking symlinks and other unexpected entry types
1006        match entry.header().entry_type() {
1007            EntryType::Regular | EntryType::Directory => {}
1008            t => anyhow::bail!(
1009                "invalid tarball downloaded, contains an entry at {entry_path:?} with invalid type {t:?}",
1010            ),
1011        }
1012
1013        // Prevent unpacking the lockfile from the crate itself.
1014        if entry_path
1015            .file_name()
1016            .map_or(false, |p| p == PACKAGE_SOURCE_LOCK)
1017        {
1018            continue;
1019        }
1020        // Unpacking failed
1021        bytes_written += entry.size();
1022        let mut result = entry.unpack_in(parent).map_err(anyhow::Error::from);
1023        if cfg!(windows) && restricted_names::is_windows_reserved_path(&entry_path) {
1024            result = result.with_context(|| {
1025                format!(
1026                    "`{}` appears to contain a reserved Windows path, \
1027                        it cannot be extracted on Windows",
1028                    entry_path.display()
1029                )
1030            });
1031        }
1032        result.with_context(|| format!("failed to unpack entry at `{}`", entry_path.display()))?;
1033    }
1034
1035    Ok(bytes_written)
1036}
1037
1038/// Workaround for rust-lang/cargo#16237
1039///
1040/// Generated files should have the same deterministic mtime as other files.
1041/// However, since we forgot to set mtime for those files when uploading, they
1042/// always have older mtime (1973-11-29) that prevents zip from packing (requiring >1980)
1043///
1044/// This workaround updates mtime after we unpack the tarball at the destination.
1045fn update_mtime_for_generated_files(pkg_root: &Path) {
1046    const GENERATED_FILES: &[&str] = &["Cargo.lock", "Cargo.toml", ".cargo_vcs_info.json"];
1047    // Hardcoded value be removed once alexcrichton/tar-rs#420 is merged and released.
1048    // See also rust-lang/cargo#16237
1049    const DETERMINISTIC_TIMESTAMP: i64 = 1153704088;
1050
1051    for file in GENERATED_FILES {
1052        let path = pkg_root.join(file);
1053        let mtime = filetime::FileTime::from_unix_time(DETERMINISTIC_TIMESTAMP, 0);
1054        if let Err(e) = filetime::set_file_mtime(&path, mtime) {
1055            tracing::trace!("failed to set deterministic mtime for {path:?}: {e}");
1056        }
1057    }
1058}