| //! Operations that interact with the [registry web API][1]. |
| //! |
| //! [1]: https://doc.rust-lang.org/nightly/cargo/reference/registry-web-api.html |
| |
| mod login; |
| mod logout; |
| mod owner; |
| mod publish; |
| mod search; |
| mod yank; |
| |
| use std::collections::HashSet; |
| use std::str; |
| use std::task::Poll; |
| |
| use anyhow::{bail, format_err, Context as _}; |
| use cargo_credential::{Operation, Secret}; |
| use crates_io::{self, Registry}; |
| |
| use crate::core::source::Source; |
| use crate::core::SourceId; |
| use crate::sources::{RegistrySource, SourceConfigMap}; |
| use crate::util::auth; |
| use crate::util::config::{Config, PathAndArgs}; |
| use crate::util::errors::CargoResult; |
| use crate::util::network::http::http_handle; |
| use crate::util::IntoUrl; |
| |
| pub use self::login::registry_login; |
| pub use self::logout::registry_logout; |
| pub use self::owner::modify_owners; |
| pub use self::owner::OwnersOptions; |
| pub use self::publish::publish; |
| pub use self::publish::PublishOpts; |
| pub use self::search::search; |
| pub use self::yank::yank; |
| |
| /// Registry settings loaded from config files. |
| /// |
| /// This is loaded based on the `--registry` flag and the config settings. |
| #[derive(Debug, PartialEq)] |
| pub enum RegistryCredentialConfig { |
| None, |
| /// The authentication token. |
| Token(Secret<String>), |
| /// Process used for fetching a token. |
| Process(Vec<PathAndArgs>), |
| /// Secret Key and subject for Asymmetric tokens. |
| AsymmetricKey((Secret<String>, Option<String>)), |
| } |
| |
| impl RegistryCredentialConfig { |
| /// Returns `true` if the credential is [`None`]. |
| /// |
| /// [`None`]: Self::None |
| pub fn is_none(&self) -> bool { |
| matches!(self, Self::None) |
| } |
| /// Returns `true` if the credential is [`Token`]. |
| /// |
| /// [`Token`]: Self::Token |
| pub fn is_token(&self) -> bool { |
| matches!(self, Self::Token(..)) |
| } |
| /// Returns `true` if the credential is [`AsymmetricKey`]. |
| /// |
| /// [`AsymmetricKey`]: RegistryCredentialConfig::AsymmetricKey |
| pub fn is_asymmetric_key(&self) -> bool { |
| matches!(self, Self::AsymmetricKey(..)) |
| } |
| pub fn as_token(&self) -> Option<Secret<&str>> { |
| if let Self::Token(v) = self { |
| Some(v.as_deref()) |
| } else { |
| None |
| } |
| } |
| pub fn as_process(&self) -> Option<&Vec<PathAndArgs>> { |
| if let Self::Process(v) = self { |
| Some(v) |
| } else { |
| None |
| } |
| } |
| pub fn as_asymmetric_key(&self) -> Option<&(Secret<String>, Option<String>)> { |
| if let Self::AsymmetricKey(v) = self { |
| Some(v) |
| } else { |
| None |
| } |
| } |
| } |
| |
| /// Returns the `Registry` and `Source` based on command-line and config settings. |
| /// |
| /// * `token_from_cmdline`: The token from the command-line. If not set, uses the token |
| /// from the config. |
| /// * `index`: The index URL from the command-line. |
| /// * `registry`: The registry name from the command-line. If neither |
| /// `registry`, or `index` are set, then uses `crates-io`. |
| /// * `force_update`: If `true`, forces the index to be updated. |
| /// * `token_required`: If `true`, the token will be set. |
| fn registry( |
| config: &Config, |
| token_from_cmdline: Option<Secret<&str>>, |
| index: Option<&str>, |
| registry: Option<&str>, |
| force_update: bool, |
| token_required: Option<Operation<'_>>, |
| ) -> CargoResult<(Registry, RegistrySourceIds)> { |
| let source_ids = get_source_id(config, index, registry)?; |
| |
| if token_required.is_some() && index.is_some() && token_from_cmdline.is_none() { |
| bail!("command-line argument --index requires --token to be specified"); |
| } |
| if let Some(token) = token_from_cmdline { |
| auth::cache_token_from_commandline(config, &source_ids.original, token); |
| } |
| |
| let cfg = { |
| let _lock = config.acquire_package_cache_lock()?; |
| let mut src = RegistrySource::remote(source_ids.replacement, &HashSet::new(), config)?; |
| // Only update the index if `force_update` is set. |
| if force_update { |
| src.invalidate_cache() |
| } |
| let cfg = loop { |
| match src.config()? { |
| Poll::Pending => src |
| .block_until_ready() |
| .with_context(|| format!("failed to update {}", source_ids.replacement))?, |
| Poll::Ready(cfg) => break cfg, |
| } |
| }; |
| cfg.expect("remote registries must have config") |
| }; |
| let api_host = cfg |
| .api |
| .ok_or_else(|| format_err!("{} does not support API commands", source_ids.replacement))?; |
| let token = if token_required.is_some() || cfg.auth_required { |
| let operation = token_required.unwrap_or(Operation::Read); |
| Some(auth::auth_token( |
| config, |
| &source_ids.original, |
| None, |
| operation, |
| vec![], |
| )?) |
| } else { |
| None |
| }; |
| let handle = http_handle(config)?; |
| Ok(( |
| Registry::new_handle(api_host, token, handle, cfg.auth_required), |
| source_ids, |
| )) |
| } |
| |
| /// Gets the SourceId for an index or registry setting. |
| /// |
| /// The `index` and `reg` values are from the command-line or config settings. |
| /// If both are None, and no source-replacement is configured, returns the source for crates.io. |
| /// If both are None, and source replacement is configured, returns an error. |
| /// |
| /// The source for crates.io may be GitHub, index.crates.io, or a test-only registry depending |
| /// on configuration. |
| /// |
| /// If `reg` is set, source replacement is not followed. |
| /// |
| /// The return value is a pair of `SourceId`s: The first may be a built-in replacement of |
| /// crates.io (such as index.crates.io), while the second is always the original source. |
| fn get_source_id( |
| config: &Config, |
| index: Option<&str>, |
| reg: Option<&str>, |
| ) -> CargoResult<RegistrySourceIds> { |
| let sid = match (reg, index) { |
| (None, None) => SourceId::crates_io(config)?, |
| (_, Some(i)) => SourceId::for_registry(&i.into_url()?)?, |
| (Some(r), None) => SourceId::alt_registry(config, r)?, |
| }; |
| // Load source replacements that are built-in to Cargo. |
| let builtin_replacement_sid = SourceConfigMap::empty(config)? |
| .load(sid, &HashSet::new())? |
| .replaced_source_id(); |
| let replacement_sid = SourceConfigMap::new(config)? |
| .load(sid, &HashSet::new())? |
| .replaced_source_id(); |
| if reg.is_none() && index.is_none() && replacement_sid != builtin_replacement_sid { |
| // Neither --registry nor --index was passed and the user has configured source-replacement. |
| if let Some(replacement_name) = replacement_sid.alt_registry_key() { |
| bail!("crates-io is replaced with remote registry {replacement_name};\ninclude `--registry {replacement_name}` or `--registry crates-io`"); |
| } else { |
| bail!("crates-io is replaced with non-remote-registry source {replacement_sid};\ninclude `--registry crates-io` to use crates.io"); |
| } |
| } else { |
| Ok(RegistrySourceIds { |
| original: sid, |
| replacement: builtin_replacement_sid, |
| }) |
| } |
| } |
| |
| struct RegistrySourceIds { |
| /// Use when looking up the auth token, or writing out `Cargo.lock` |
| original: SourceId, |
| /// Use when interacting with the source (querying / publishing , etc) |
| /// |
| /// The source for crates.io may be replaced by a built-in source for accessing crates.io with |
| /// the sparse protocol, or a source for the testing framework (when the replace_crates_io |
| /// function is used) |
| /// |
| /// User-defined source replacement is not applied. |
| replacement: SourceId, |
| } |