1mod info;
6mod login;
7mod logout;
8mod owner;
9mod publish;
10mod search;
11mod yank;
12
13use std::collections::HashSet;
14use std::str;
15use std::task::Poll;
16
17use anyhow::{bail, format_err, Context as _};
18use cargo_credential::{Operation, Secret};
19use crates_io::Registry;
20use url::Url;
21
22use crate::core::{Package, PackageId, SourceId};
23use crate::sources::source::Source;
24use crate::sources::{RegistrySource, SourceConfigMap};
25use crate::util::auth;
26use crate::util::cache_lock::CacheLockMode;
27use crate::util::context::{GlobalContext, PathAndArgs};
28use crate::util::errors::CargoResult;
29use crate::util::network::http::http_handle;
30
31pub use self::info::info;
32pub use self::login::registry_login;
33pub use self::logout::registry_logout;
34pub use self::owner::modify_owners;
35pub use self::owner::OwnersOptions;
36pub use self::publish::publish;
37pub use self::publish::PublishOpts;
38pub use self::search::search;
39pub use self::yank::yank;
40
41pub(crate) use self::publish::prepare_transmit;
42
43#[derive(Debug, Clone)]
45pub enum RegistryOrIndex {
46 Registry(String),
47 Index(Url),
48}
49
50impl RegistryOrIndex {
51 fn is_index(&self) -> bool {
52 matches!(self, RegistryOrIndex::Index(..))
53 }
54}
55
56#[derive(Debug, PartialEq)]
60pub enum RegistryCredentialConfig {
61 None,
62 Token(Secret<String>),
64 Process(Vec<PathAndArgs>),
66 AsymmetricKey((Secret<String>, Option<String>)),
68}
69
70impl RegistryCredentialConfig {
71 pub fn is_none(&self) -> bool {
75 matches!(self, Self::None)
76 }
77 pub fn is_token(&self) -> bool {
81 matches!(self, Self::Token(..))
82 }
83 pub fn is_asymmetric_key(&self) -> bool {
87 matches!(self, Self::AsymmetricKey(..))
88 }
89 pub fn as_token(&self) -> Option<Secret<&str>> {
90 if let Self::Token(v) = self {
91 Some(v.as_deref())
92 } else {
93 None
94 }
95 }
96 pub fn as_process(&self) -> Option<&Vec<PathAndArgs>> {
97 if let Self::Process(v) = self {
98 Some(v)
99 } else {
100 None
101 }
102 }
103 pub fn as_asymmetric_key(&self) -> Option<&(Secret<String>, Option<String>)> {
104 if let Self::AsymmetricKey(v) = self {
105 Some(v)
106 } else {
107 None
108 }
109 }
110}
111
112fn registry<'gctx>(
124 gctx: &'gctx GlobalContext,
125 source_ids: &RegistrySourceIds,
126 token_from_cmdline: Option<Secret<&str>>,
127 reg_or_index: Option<&RegistryOrIndex>,
128 force_update: bool,
129 token_required: Option<Operation<'_>>,
130) -> CargoResult<(Registry, RegistrySource<'gctx>)> {
131 let is_index = reg_or_index.map(|v| v.is_index()).unwrap_or_default();
132 if is_index && token_required.is_some() && token_from_cmdline.is_none() {
133 bail!("command-line argument --index requires --token to be specified");
134 }
135 if let Some(token) = token_from_cmdline {
136 auth::cache_token_from_commandline(gctx, &source_ids.original, token);
137 }
138
139 let mut src = RegistrySource::remote(source_ids.replacement, &HashSet::new(), gctx)?;
140 let cfg = {
141 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
142 if force_update {
144 src.invalidate_cache()
145 }
146 let cfg = loop {
147 match src.config()? {
148 Poll::Pending => src
149 .block_until_ready()
150 .with_context(|| format!("failed to update {}", source_ids.replacement))?,
151 Poll::Ready(cfg) => break cfg,
152 }
153 };
154 cfg.expect("remote registries must have config")
155 };
156 let api_host = cfg
157 .api
158 .ok_or_else(|| format_err!("{} does not support API commands", source_ids.replacement))?;
159 let token = if token_required.is_some() || cfg.auth_required {
160 let operation = token_required.unwrap_or(Operation::Read);
161 Some(auth::auth_token(
162 gctx,
163 &source_ids.original,
164 None,
165 operation,
166 vec![],
167 false,
168 )?)
169 } else {
170 None
171 };
172 let handle = http_handle(gctx)?;
173 Ok((
174 Registry::new_handle(api_host, token, handle, cfg.auth_required),
175 src,
176 ))
177}
178
179pub(crate) fn get_source_id(
193 gctx: &GlobalContext,
194 reg_or_index: Option<&RegistryOrIndex>,
195) -> CargoResult<RegistrySourceIds> {
196 let sid = get_initial_source_id(gctx, reg_or_index)?;
197 let (builtin_replacement_sid, replacement_sid) = get_replacement_source_ids(gctx, sid)?;
198
199 if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid {
200 bail!(gen_replacement_error(replacement_sid));
201 } else {
202 Ok(RegistrySourceIds {
203 original: sid,
204 replacement: builtin_replacement_sid,
205 })
206 }
207}
208
209fn get_source_id_with_package_id(
211 gctx: &GlobalContext,
212 package_id: Option<PackageId>,
213 reg_or_index: Option<&RegistryOrIndex>,
214) -> CargoResult<(bool, RegistrySourceIds)> {
215 let (use_package_source_id, sid) = match (®_or_index, package_id) {
216 (None, Some(package_id)) => (true, package_id.source_id()),
217 (None, None) => (false, SourceId::crates_io(gctx)?),
218 (Some(RegistryOrIndex::Index(url)), None) => (false, SourceId::for_registry(url)?),
219 (Some(RegistryOrIndex::Registry(r)), None) => (false, SourceId::alt_registry(gctx, r)?),
220 (Some(reg_or_index), Some(package_id)) => {
221 let sid = get_initial_source_id_from_registry_or_index(gctx, reg_or_index)?;
222 let package_source_id = package_id.source_id();
223 if sid == package_source_id
226 || is_replacement_for_package_source(gctx, sid, package_source_id)?
227 {
228 (true, package_source_id)
229 } else {
230 (false, sid)
231 }
232 }
233 };
234
235 let (builtin_replacement_sid, replacement_sid) = get_replacement_source_ids(gctx, sid)?;
236
237 if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid {
238 bail!(gen_replacement_error(replacement_sid));
239 } else {
240 Ok((
241 use_package_source_id,
242 RegistrySourceIds {
243 original: sid,
244 replacement: builtin_replacement_sid,
245 },
246 ))
247 }
248}
249
250fn get_initial_source_id(
251 gctx: &GlobalContext,
252 reg_or_index: Option<&RegistryOrIndex>,
253) -> CargoResult<SourceId> {
254 match reg_or_index {
255 None => SourceId::crates_io(gctx),
256 Some(reg_or_index) => get_initial_source_id_from_registry_or_index(gctx, reg_or_index),
257 }
258}
259
260fn get_initial_source_id_from_registry_or_index(
261 gctx: &GlobalContext,
262 reg_or_index: &RegistryOrIndex,
263) -> CargoResult<SourceId> {
264 match reg_or_index {
265 RegistryOrIndex::Index(url) => SourceId::for_registry(url),
266 RegistryOrIndex::Registry(r) => SourceId::alt_registry(gctx, r),
267 }
268}
269
270fn get_replacement_source_ids(
271 gctx: &GlobalContext,
272 sid: SourceId,
273) -> CargoResult<(SourceId, SourceId)> {
274 let builtin_replacement_sid = SourceConfigMap::empty(gctx)?
275 .load(sid, &HashSet::new())?
276 .replaced_source_id();
277 let replacement_sid = SourceConfigMap::new(gctx)?
278 .load(sid, &HashSet::new())?
279 .replaced_source_id();
280 Ok((builtin_replacement_sid, replacement_sid))
281}
282
283fn is_replacement_for_package_source(
284 gctx: &GlobalContext,
285 sid: SourceId,
286 package_source_id: SourceId,
287) -> CargoResult<bool> {
288 let pkg_source_replacement_sid = SourceConfigMap::new(gctx)?
289 .load(package_source_id, &HashSet::new())?
290 .replaced_source_id();
291 Ok(pkg_source_replacement_sid == sid)
292}
293
294fn gen_replacement_error(replacement_sid: SourceId) -> String {
295 let error_message = if let Some(replacement_name) = replacement_sid.alt_registry_key() {
297 format!(
298 "crates-io is replaced with remote registry {};\ninclude `--registry {}` or `--registry crates-io`",
299 replacement_name, replacement_name
300 )
301 } else {
302 format!(
303 "crates-io is replaced with non-remote-registry source {};\ninclude `--registry crates-io` to use crates.io",
304 replacement_sid
305 )
306 };
307
308 error_message
309}
310
311pub(crate) struct RegistrySourceIds {
312 pub(crate) original: SourceId,
314 pub(crate) replacement: SourceId,
322}
323
324pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult<Option<RegistryOrIndex>> {
326 let publishable_pkgs: Vec<_> = pkgs
328 .iter()
329 .filter(|p| p.publish() != &Some(Vec::new()))
330 .collect();
331
332 let Some((first, rest)) = publishable_pkgs.split_first() else {
333 return Ok(None);
334 };
335
336 if rest.iter().all(|p| p.publish() == first.publish()) {
338 match publishable_pkgs[0].publish().as_deref() {
339 Some([unique_pkg_reg]) => {
340 Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned())))
341 }
342 None | Some([]) => Ok(None),
343 Some(regs) => {
344 let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect();
345 regs.sort();
346 regs.dedup();
347 let (last_reg, regs) = regs.split_last().unwrap();
349 bail!(
350 "--registry is required to disambiguate between {} or {} registries",
351 regs.join(", "),
352 last_reg
353 )
354 }
355 }
356 } else {
357 let common_regs = publishable_pkgs
358 .iter()
359 .filter_map(|p| p.publish().as_deref())
362 .map(|p| p.iter().collect::<HashSet<_>>())
363 .reduce(|xs, ys| xs.intersection(&ys).cloned().collect())
364 .unwrap_or_default();
365 if common_regs.is_empty() {
366 bail!("conflicts between `package.publish` fields in the selected packages");
367 } else {
368 bail!("--registry is required because not all `package.publish` settings agree",);
369 }
370 }
371}