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