1mod cargo_info;
6mod cargo_login;
7mod cargo_logout;
8mod cargo_owner;
9mod cargo_publish;
10mod cargo_search;
11mod cargo_yank;
12
13use crate::util::data_structures::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::context::{GlobalContext, PathAndArgs};
22use crate::sources::source::Source;
23use crate::sources::{RegistrySource, SourceConfigMap};
24use crate::util::auth;
25use crate::util::cache_lock::CacheLockMode;
26use crate::util::errors::CargoResult;
27use crate::util::network::http_async;
28use crate::workspace::{Package, PackageId, SourceId};
29
30pub use self::cargo_info::info;
31pub use self::cargo_login::registry_login;
32pub use self::cargo_logout::registry_logout;
33pub use self::cargo_owner::OwnersOptions;
34pub use self::cargo_owner::modify_owners;
35pub use self::cargo_publish::PublishOpts;
36pub use self::cargo_publish::publish;
37pub use self::cargo_search::search;
38pub use self::cargo_yank::yank;
39
40pub(crate) use self::cargo_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}
66
67impl RegistryCredentialConfig {
68 pub fn is_none(&self) -> bool {
72 matches!(self, Self::None)
73 }
74 pub fn is_token(&self) -> bool {
78 matches!(self, Self::Token(..))
79 }
80 pub fn as_token(&self) -> Option<Secret<&str>> {
81 if let Self::Token(v) = self {
82 Some(v.as_deref())
83 } else {
84 None
85 }
86 }
87 pub fn as_process(&self) -> Option<&Vec<PathAndArgs>> {
88 if let Self::Process(v) = self {
89 Some(v)
90 } else {
91 None
92 }
93 }
94}
95
96fn registry<'gctx>(
108 gctx: &'gctx GlobalContext,
109 source_ids: &RegistrySourceIds,
110 token_from_cmdline: Option<Secret<&str>>,
111 reg_or_index: Option<&RegistryOrIndex>,
112 force_update: bool,
113 token_required: Option<Operation<'_>>,
114) -> CargoResult<(Registry<RegistryClient<'gctx>>, RegistrySource<'gctx>)> {
115 let is_index = reg_or_index.map(|v| v.is_index()).unwrap_or_default();
116 if is_index && token_required.is_some() && token_from_cmdline.is_none() {
117 bail!("command-line argument --index requires --token to be specified");
118 }
119 if let Some(token) = token_from_cmdline {
120 auth::cache_token_from_commandline(gctx, &source_ids.original, token);
121 }
122
123 let src = RegistrySource::remote(source_ids.replacement, gctx)?;
124 let cfg = {
125 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
126 if force_update {
128 src.invalidate_cache()
129 }
130 crate::util::block_on(src.config())
131 .with_context(|| format!("failed to update {}", source_ids.replacement))?
132 .expect("remote registries must have config")
133 };
134 let api_host = cfg
135 .api
136 .ok_or_else(|| format_err!("{} does not support API commands", source_ids.replacement))?;
137 let token = if token_required.is_some() || cfg.auth_required {
138 let operation = token_required.unwrap_or(Operation::Read);
139 Some(auth::auth_token(
140 gctx,
141 &source_ids.original,
142 None,
143 operation,
144 vec![],
145 false,
146 )?)
147 } else {
148 None
149 };
150 let handle = RegistryClient(gctx.http_async()?);
151 Ok((
152 Registry::new_handle(api_host, token, handle, cfg.auth_required),
153 src,
154 ))
155}
156
157pub(crate) fn get_source_id(
171 gctx: &GlobalContext,
172 reg_or_index: Option<&RegistryOrIndex>,
173) -> CargoResult<RegistrySourceIds> {
174 let sid = get_initial_source_id(gctx, reg_or_index)?;
175 let (builtin_replacement_sid, replacement_sid) = get_replacement_source_ids(gctx, sid)?;
176
177 if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid {
178 bail!(gen_replacement_error(replacement_sid));
179 } else {
180 Ok(RegistrySourceIds {
181 original: sid,
182 replacement: builtin_replacement_sid,
183 })
184 }
185}
186
187fn get_source_id_with_package_id(
189 gctx: &GlobalContext,
190 package_id: Option<PackageId>,
191 reg_or_index: Option<&RegistryOrIndex>,
192) -> CargoResult<(bool, RegistrySourceIds)> {
193 let (use_package_source_id, sid) = match (®_or_index, package_id) {
194 (None, Some(package_id)) => (true, package_id.source_id()),
195 (None, None) => (false, SourceId::crates_io(gctx)?),
196 (Some(RegistryOrIndex::Index(url)), None) => (false, SourceId::for_registry(url)?),
197 (Some(RegistryOrIndex::Registry(r)), None) => (false, SourceId::alt_registry(gctx, r)?),
198 (Some(reg_or_index), Some(package_id)) => {
199 let sid = get_initial_source_id_from_registry_or_index(gctx, reg_or_index)?;
200 let package_source_id = package_id.source_id();
201 if sid == package_source_id
204 || is_replacement_for_package_source(gctx, sid, package_source_id)?
205 {
206 (true, package_source_id)
207 } else {
208 (false, sid)
209 }
210 }
211 };
212
213 let (builtin_replacement_sid, replacement_sid) = get_replacement_source_ids(gctx, sid)?;
214
215 if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid {
216 bail!(gen_replacement_error(replacement_sid));
217 } else {
218 Ok((
219 use_package_source_id,
220 RegistrySourceIds {
221 original: sid,
222 replacement: builtin_replacement_sid,
223 },
224 ))
225 }
226}
227
228fn get_initial_source_id(
229 gctx: &GlobalContext,
230 reg_or_index: Option<&RegistryOrIndex>,
231) -> CargoResult<SourceId> {
232 match reg_or_index {
233 None => SourceId::crates_io(gctx),
234 Some(reg_or_index) => get_initial_source_id_from_registry_or_index(gctx, reg_or_index),
235 }
236}
237
238fn get_initial_source_id_from_registry_or_index(
239 gctx: &GlobalContext,
240 reg_or_index: &RegistryOrIndex,
241) -> CargoResult<SourceId> {
242 match reg_or_index {
243 RegistryOrIndex::Index(url) => SourceId::for_registry(url),
244 RegistryOrIndex::Registry(r) => SourceId::alt_registry(gctx, r),
245 }
246}
247
248fn get_replacement_source_ids(
249 gctx: &GlobalContext,
250 sid: SourceId,
251) -> CargoResult<(SourceId, SourceId)> {
252 let builtin_replacement_sid = SourceConfigMap::empty(gctx)?
253 .load(sid)?
254 .replaced_source_id();
255 let replacement_sid = SourceConfigMap::new(gctx)?.load(sid)?.replaced_source_id();
256 Ok((builtin_replacement_sid, replacement_sid))
257}
258
259fn is_replacement_for_package_source(
260 gctx: &GlobalContext,
261 sid: SourceId,
262 package_source_id: SourceId,
263) -> CargoResult<bool> {
264 let pkg_source_replacement_sid = SourceConfigMap::new(gctx)?
265 .load(package_source_id)?
266 .replaced_source_id();
267 Ok(pkg_source_replacement_sid == sid)
268}
269
270fn gen_replacement_error(replacement_sid: SourceId) -> String {
271 let error_message = if let Some(replacement_name) = replacement_sid.alt_registry_key() {
273 format!(
274 "crates-io is replaced with remote registry {};\ninclude `--registry {}` or `--registry crates-io`",
275 replacement_name, replacement_name
276 )
277 } else {
278 format!(
279 "crates-io is replaced with non-remote-registry source {};\ninclude `--registry crates-io` to use crates.io",
280 replacement_sid
281 )
282 };
283
284 error_message
285}
286
287pub(crate) struct RegistrySourceIds {
288 pub(crate) original: SourceId,
290 pub(crate) replacement: SourceId,
298}
299
300pub(crate) fn infer_registry(pkgs: &[&Package]) -> CargoResult<Option<RegistryOrIndex>> {
302 let publishable_pkgs: Vec<_> = pkgs
304 .iter()
305 .filter(|p| p.publish() != &Some(Vec::new()))
306 .collect();
307
308 let Some((first, rest)) = publishable_pkgs.split_first() else {
309 return Ok(None);
310 };
311
312 if rest.iter().all(|p| p.publish() == first.publish()) {
314 match publishable_pkgs[0].publish().as_deref() {
315 Some([unique_pkg_reg]) => {
316 Ok(Some(RegistryOrIndex::Registry(unique_pkg_reg.to_owned())))
317 }
318 None | Some([]) => Ok(None),
319 Some(regs) => {
320 let mut regs: Vec<_> = regs.iter().map(|s| format!("\"{}\"", s)).collect();
321 regs.sort();
322 regs.dedup();
323 let (last_reg, regs) = regs.split_last().unwrap();
325 bail!(
326 "--registry is required to disambiguate between {} or {} registries",
327 regs.join(", "),
328 last_reg
329 )
330 }
331 }
332 } else {
333 let common_regs = publishable_pkgs
334 .iter()
335 .filter_map(|p| p.publish().as_deref())
338 .map(|p| p.iter().collect::<HashSet<_>>())
339 .reduce(|xs, ys| xs.intersection(&ys).cloned().collect())
340 .unwrap_or_default();
341 if common_regs.is_empty() {
342 bail!("conflicts between `package.publish` fields in the selected packages");
343 } else {
344 bail!("--registry is required because not all `package.publish` settings agree",);
345 }
346 }
347}
348
349struct RegistryClient<'gctx>(&'gctx http_async::Client);
350
351impl<'gctx> crates_io::HttpClient for RegistryClient<'gctx> {
352 type Error = http_async::Error;
353
354 fn request(&self, req: http::Request<Vec<u8>>) -> Result<http::Response<Vec<u8>>, Self::Error> {
355 self.0.request_blocking(req)
356 }
357}