Skip to content

Commit 1698cef

Browse files
committed
fix(toml): Reject registry-index in user-written manifests
1 parent 92ffbdf commit 1698cef

File tree

9 files changed

+177
-34
lines changed

9 files changed

+177
-34
lines changed

src/cargo/core/source_id.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,11 @@ impl SourceId {
387387
matches!(self.inner.kind, SourceKind::Git(_))
388388
}
389389

390+
/// Returns `true` if this source is from a directory.
391+
pub fn is_directory(self) -> bool {
392+
self.inner.kind == SourceKind::Directory
393+
}
394+
390395
/// Creates an implementation of `Source` corresponding to this ID.
391396
///
392397
/// * `yanked_whitelist` --- Packages allowed to be used, even if they are yanked.

src/cargo/core/workspace.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ pub struct Workspace<'gctx> {
9797
/// `cargo install` or `cargo package` commands.
9898
is_ephemeral: bool,
9999

100+
/// `true` if this workspace is being used for package verification.
101+
/// This is a special case of ephemeral workspace where we want to preload
102+
/// cargo-generated manifests to avoid re-parsing them without the proper flags.
103+
is_verifying_package: bool,
104+
100105
/// `true` if this workspace should enforce optional dependencies even when
101106
/// not needed; false if this workspace should only enforce dependencies
102107
/// needed by the current configuration (such as in cargo install). In some
@@ -254,6 +259,7 @@ impl<'gctx> Workspace<'gctx> {
254259
member_ids: HashSet::new(),
255260
default_members: Vec::new(),
256261
is_ephemeral: false,
262+
is_verifying_package: false,
257263
require_optional_deps: true,
258264
loaded_packages: RefCell::new(HashMap::new()),
259265
ignore_lock: false,
@@ -646,6 +652,15 @@ impl<'gctx> Workspace<'gctx> {
646652
self.is_ephemeral
647653
}
648654

655+
pub fn is_verifying_package(&self) -> bool {
656+
self.is_verifying_package
657+
}
658+
659+
pub fn set_verifying_package(&mut self, verifying: bool) -> &mut Workspace<'gctx> {
660+
self.is_verifying_package = verifying;
661+
self
662+
}
663+
649664
pub fn require_optional_deps(&self) -> bool {
650665
self.require_optional_deps
651666
}
@@ -1206,7 +1221,10 @@ impl<'gctx> Workspace<'gctx> {
12061221
// `PathSource` with multiple entries in it, so the logic below is
12071222
// mostly just an optimization for normal `cargo build` in workspaces
12081223
// during development.
1209-
if self.is_ephemeral {
1224+
//
1225+
// However, for package verification workspaces, we do want to preload
1226+
// to avoid re-reading cargo-generated manifests without the proper flags.
1227+
if self.is_ephemeral && !self.is_verifying_package {
12101228
return;
12111229
}
12121230

src/cargo/ops/cargo_package/verify.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,16 @@ pub fn run_verify(
6363
// package has a workspace we can still build our new crate.
6464
let id = SourceId::for_path(&dst)?;
6565
let mut src = PathSource::new(&dst, id, ws.gctx());
66-
let new_pkg = src.root_package()?;
66+
let new_pkg = src.root_cargo_generated_package()?;
6767
let pkg_fingerprint = hash_all(&dst)?;
6868

6969
// When packaging we use an ephemeral workspace but reuse the build cache to reduce
7070
// verification time if the user has already compiled the dependencies and the fingerprint
7171
// is unchanged.
7272
let mut ws = Workspace::ephemeral(new_pkg, gctx, Some(ws.build_dir()), true)?;
73+
// Mark this as a verification workspace so that the package gets preloaded
74+
// with the cargo_generated flag set
75+
ws.set_verifying_package(true);
7376
if let Some(local_reg) = local_reg {
7477
ws.add_local_overlay(
7578
local_reg.upstream,

src/cargo/ops/cargo_read_manifest.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::path::Path;
33
use crate::core::{EitherManifest, Package, SourceId};
44
use crate::util::GlobalContext;
55
use crate::util::errors::CargoResult;
6-
use crate::util::toml::read_manifest;
6+
use crate::util::toml::{read_manifest, read_cargo_generated_manifest};
77
use tracing::trace;
88

99
pub fn read_package(
@@ -28,3 +28,31 @@ pub fn read_package(
2828

2929
Ok(Package::new(manifest, path))
3030
}
31+
32+
/// Reads a cargo-generated package manifest.
33+
///
34+
/// This is for reading packages that were generated by cargo itself
35+
/// (e.g., during package verification), which may contain internal-only
36+
/// fields like `registry-index`.
37+
pub fn read_cargo_generated_package(
38+
path: &Path,
39+
source_id: SourceId,
40+
gctx: &GlobalContext,
41+
) -> CargoResult<Package> {
42+
trace!(
43+
"read_cargo_generated_package; path={}; source-id={}",
44+
path.display(),
45+
source_id
46+
);
47+
let manifest = read_cargo_generated_manifest(path, source_id, gctx)?;
48+
let manifest = match manifest {
49+
EitherManifest::Real(manifest) => manifest,
50+
EitherManifest::Virtual(..) => anyhow::bail!(
51+
"found a virtual manifest at `{}` instead of a package \
52+
manifest",
53+
path.display()
54+
),
55+
};
56+
57+
Ok(Package::new(manifest, path))
58+
}

src/cargo/ops/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub use self::cargo_package::PackageOpts;
1616
pub use self::cargo_package::check_yanked;
1717
pub use self::cargo_package::package;
1818
pub use self::cargo_pkgid::pkgid;
19-
pub use self::cargo_read_manifest::read_package;
19+
pub use self::cargo_read_manifest::{read_package, read_cargo_generated_package};
2020
pub use self::cargo_run::run;
2121
pub use self::cargo_test::{TestOptions, run_benches, run_tests};
2222
pub use self::cargo_uninstall::uninstall;

src/cargo/sources/path.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,26 @@ impl<'gctx> PathSource<'gctx> {
8383
}
8484
}
8585

86+
/// Gets the cargo-generated package on the root path.
87+
///
88+
/// This is for reading packages that were generated by cargo itself
89+
/// (e.g., during package verification).
90+
pub fn root_cargo_generated_package(&mut self) -> CargoResult<Package> {
91+
trace!("root_cargo_generated_package; source={:?}", self);
92+
93+
if self.package.is_none() {
94+
self.package = Some(self.read_cargo_generated_package()?);
95+
}
96+
97+
match &self.package {
98+
Some(pkg) => Ok(pkg.clone()),
99+
None => Err(internal(format!(
100+
"no package found in source {:?}",
101+
self.path
102+
))),
103+
}
104+
}
105+
86106
/// List all files relevant to building this package inside this source.
87107
///
88108
/// This function will use the appropriate methods to determine the
@@ -128,6 +148,12 @@ impl<'gctx> PathSource<'gctx> {
128148
let pkg = ops::read_package(&path, self.source_id, self.gctx)?;
129149
Ok(pkg)
130150
}
151+
152+
fn read_cargo_generated_package(&self) -> CargoResult<Package> {
153+
let path = self.path.join("Cargo.toml");
154+
let pkg = ops::read_cargo_generated_package(&path, self.source_id, self.gctx)?;
155+
Ok(pkg)
156+
}
131157
}
132158

133159
impl<'gctx> Debug for PathSource<'gctx> {

src/cargo/util/toml/mod.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,31 @@ pub fn read_manifest(
6565
path: &Path,
6666
source_id: SourceId,
6767
gctx: &GlobalContext,
68+
) -> CargoResult<EitherManifest> {
69+
read_manifest_impl(path, source_id, gctx, false)
70+
}
71+
72+
/// Loads a cargo-generated `Cargo.toml` from a file on disk.
73+
///
74+
/// This is for reading manifests that were generated by cargo itself
75+
/// (e.g., during package verification), which may contain internal-only
76+
/// fields like `registry-index`.
77+
#[tracing::instrument(skip(gctx))]
78+
pub fn read_cargo_generated_manifest(
79+
path: &Path,
80+
source_id: SourceId,
81+
gctx: &GlobalContext,
82+
) -> CargoResult<EitherManifest> {
83+
read_manifest_impl(path, source_id, gctx, true)
84+
}
85+
86+
/// Internal implementation with cargo_generated parameter
87+
#[tracing::instrument(skip(gctx))]
88+
fn read_manifest_impl(
89+
path: &Path,
90+
source_id: SourceId,
91+
gctx: &GlobalContext,
92+
cargo_generated: bool,
6893
) -> CargoResult<EitherManifest> {
6994
let mut warnings = Default::default();
7095
let mut errors = Default::default();
@@ -99,7 +124,7 @@ pub fn read_manifest(
99124
)?;
100125

101126
if normalized_toml.package().is_some() {
102-
to_real_manifest(
127+
to_real_manifest_impl(
103128
contents,
104129
document,
105130
original_toml,
@@ -109,6 +134,7 @@ pub fn read_manifest(
109134
source_id,
110135
path,
111136
is_embedded,
137+
cargo_generated,
112138
gctx,
113139
&mut warnings,
114140
&mut errors,
@@ -1276,6 +1302,40 @@ pub fn to_real_manifest(
12761302
gctx: &GlobalContext,
12771303
warnings: &mut Vec<String>,
12781304
_errors: &mut Vec<String>,
1305+
) -> CargoResult<Manifest> {
1306+
to_real_manifest_impl(
1307+
contents,
1308+
document,
1309+
original_toml,
1310+
normalized_toml,
1311+
features,
1312+
workspace_config,
1313+
source_id,
1314+
manifest_file,
1315+
is_embedded,
1316+
false,
1317+
gctx,
1318+
warnings,
1319+
_errors,
1320+
)
1321+
}
1322+
1323+
/// Internal implementation with cargo_generated parameter
1324+
#[tracing::instrument(skip_all)]
1325+
fn to_real_manifest_impl(
1326+
contents: String,
1327+
document: toml::Spanned<toml::de::DeTable<'static>>,
1328+
original_toml: manifest::TomlManifest,
1329+
normalized_toml: manifest::TomlManifest,
1330+
features: Features,
1331+
workspace_config: WorkspaceConfig,
1332+
source_id: SourceId,
1333+
manifest_file: &Path,
1334+
is_embedded: bool,
1335+
cargo_generated: bool,
1336+
gctx: &GlobalContext,
1337+
warnings: &mut Vec<String>,
1338+
_errors: &mut Vec<String>,
12791339
) -> CargoResult<Manifest> {
12801340
let package_root = manifest_file.parent().unwrap();
12811341
if !package_root.is_dir() {
@@ -1582,6 +1642,7 @@ pub fn to_real_manifest(
15821642
warnings,
15831643
platform: None,
15841644
root: package_root,
1645+
cargo_generated,
15851646
};
15861647
gather_dependencies(
15871648
&mut manifest_ctx,
@@ -1977,6 +2038,7 @@ fn to_virtual_manifest(
19772038
warnings,
19782039
platform: None,
19792040
root,
2041+
cargo_generated: false,
19802042
};
19812043
(
19822044
replace(&normalized_toml, &mut manifest_ctx)?,
@@ -2045,6 +2107,7 @@ struct ManifestContext<'a, 'b> {
20452107
warnings: &'a mut Vec<String>,
20462108
platform: Option<Platform>,
20472109
root: &'a Path,
2110+
cargo_generated: bool,
20482111
}
20492112

20502113
#[tracing::instrument(skip_all)]
@@ -2175,6 +2238,7 @@ pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
21752238
warnings,
21762239
platform,
21772240
root,
2241+
cargo_generated: false,
21782242
},
21792243
kind,
21802244
)
@@ -2292,6 +2356,19 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
22922356
dep.set_registry_id(registry_id);
22932357
}
22942358
if let Some(registry_index) = &orig.registry_index {
2359+
// `registry-index` is for internal use only.
2360+
// It should not be used in user-written manifests as it bypasses the need for .cargo/config.toml configuration.
2361+
2362+
if !manifest_ctx.source_id.is_registry()
2363+
&& !manifest_ctx.source_id.is_directory()
2364+
&& !manifest_ctx.cargo_generated
2365+
{
2366+
bail!(
2367+
"dependency ({}) specification uses `registry-index` which is for internal use only\n\
2368+
help: use `registry = \"<name>\"` and configure the registry in `.cargo/config.toml`",
2369+
name_in_toml
2370+
);
2371+
}
22952372
let url = registry_index.into_url()?;
22962373
let registry_id = SourceId::for_registry(&url)?;
22972374
dep.set_registry_id(registry_id);
@@ -2936,7 +3013,8 @@ pub fn prepare_for_publish(
29363013
let mut warnings = Default::default();
29373014
let mut errors = Default::default();
29383015
let gctx = ws.gctx();
2939-
let manifest = to_real_manifest(
3016+
let cargo_generated = true;
3017+
let manifest = to_real_manifest_impl(
29403018
contents.to_owned(),
29413019
document.clone(),
29423020
original_toml,
@@ -2946,6 +3024,7 @@ pub fn prepare_for_publish(
29463024
source_id,
29473025
me.manifest_path(),
29483026
me.manifest().is_embedded(),
3027+
cargo_generated,
29493028
gctx,
29503029
&mut warnings,
29513030
&mut errors,

tests/testsuite/alt_registry.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -285,18 +285,14 @@ fn registry_index_not_allowed_in_user_manifests() {
285285
.file("src/lib.rs", "")
286286
.build();
287287

288-
// FIXME: This currently allows `registry-index` which is a bug.
289-
// It should error during manifest parsing because `registry-index` is for internal use only.
290-
// Instead, it tries to fetch from the URL and fails with a network error.
291288
p.cargo("check")
289+
.with_status(101)
292290
.with_stderr_data(str![[r#"
293-
[UPDATING] `[ROOT]/alternative-registry` index
294-
[LOCKING] 1 package to latest compatible version
295-
[DOWNLOADING] crates ...
296-
[DOWNLOADED] bar v0.1.0 (registry `[ROOT]/alternative-registry`)
297-
[CHECKING] bar v0.1.0 (registry `[ROOT]/alternative-registry`)
298-
[CHECKING] foo v0.0.0 ([ROOT]/foo)
299-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
291+
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
292+
293+
Caused by:
294+
dependency (bar) specification uses `registry-index` which is for internal use only
295+
[HELP] use `registry = "<name>"` and configure the registry in `.cargo/config.toml`
300296
301297
"#]])
302298
.run();

tests/testsuite/publish.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4628,26 +4628,14 @@ fn registry_index_not_allowed_in_publish() {
46284628
.file("src/main.rs", "fn main() {}")
46294629
.build();
46304630

4631-
// FIXME: This currently allows `registry-index` which is a bug.
4632-
// It should error during manifest parsing because `registry-index` is for internal use only.
4633-
// Instead, it actually works and the package can be published.
46344631
p.cargo("publish --registry alternative")
4632+
.with_status(101)
46354633
.with_stderr_data(str![[r#"
4636-
[UPDATING] `alternative` index
4637-
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
4638-
[UPDATING] `[ROOT]/registry` index
4639-
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
4640-
[VERIFYING] foo v0.0.1 ([ROOT]/foo)
4641-
[DOWNLOADING] crates ...
4642-
[DOWNLOADED] bar v0.1.0 (registry `[ROOT]/registry`)
4643-
[COMPILING] bar v0.1.0 (registry `[ROOT]/registry`)
4644-
[COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1)
4645-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
4646-
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
4647-
[UPLOADED] foo v0.0.1 to registry `alternative`
4648-
[NOTE] waiting for foo v0.0.1 to be available at registry `alternative`
4649-
[HELP] you may press ctrl-c to skip waiting; the crate should be available shortly
4650-
[PUBLISHED] foo v0.0.1 at registry `alternative`
4634+
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
4635+
4636+
Caused by:
4637+
dependency (bar) specification uses `registry-index` which is for internal use only
4638+
[HELP] use `registry = "<name>"` and configure the registry in `.cargo/config.toml`
46514639
46524640
"#]])
46534641
.run();

0 commit comments

Comments
 (0)