diff --git a/src/cargo/util/context/mod.rs b/src/cargo/util/context/mod.rs index 0503e8ff35c..8954c02cedd 100644 --- a/src/cargo/util/context/mod.rs +++ b/src/cargo/util/context/mod.rs @@ -1298,7 +1298,7 @@ impl GlobalContext { ) -> CargoResult<()> { let includes = self.include_paths(cv, false)?; for include in includes { - let Some(abs_path) = include.resolve_path(self) else { + let Some(abs_path) = include.resolve_path(self)? else { continue; }; @@ -1413,7 +1413,7 @@ impl GlobalContext { // Accumulate all values here. let mut root = CV::Table(HashMap::new(), value.definition().clone()); for include in includes { - let Some(abs_path) = include.resolve_path(self) else { + let Some(abs_path) = include.resolve_path(self)? else { continue; }; @@ -2305,9 +2305,9 @@ impl ConfigInclude { /// For CLI based include (e.g., `--config 'include = "foo.toml"'`), /// it is relative to the current working directory. /// - /// Returns `None` if this is an optional include and the file doesn't exist. - /// Otherwise returns `Some(PathBuf)` with the absolute path. - fn resolve_path(&self, gctx: &GlobalContext) -> Option { + /// Returns `Ok(None)` if this is an optional include and the file doesn't exist. + /// Otherwise returns `Ok(Some(PathBuf))` with the absolute path. + fn resolve_path(&self, gctx: &GlobalContext) -> CargoResult> { let abs_path = match &self.def { Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(), Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(), @@ -2321,10 +2321,22 @@ impl ConfigInclude { self.def, abs_path.display(), ); - None - } else { - Some(abs_path) + return Ok(None); } + + let reserved_paths = ["config.toml.d", "user.config.toml", "user.config.toml.d"]; + for reserved in &reserved_paths { + let suffix = Path::new(".cargo").join(reserved); + if abs_path.ends_with(&suffix) { + bail!( + "`{}` is a reserved configuration path, but was included in `{}`", + suffix.display(), + self.def, + ) + } + } + + Ok(Some(abs_path)) } } diff --git a/tests/testsuite/config_include.rs b/tests/testsuite/config_include.rs index 06e71f2901d..2f08fe1be35 100644 --- a/tests/testsuite/config_include.rs +++ b/tests/testsuite/config_include.rs @@ -684,3 +684,96 @@ Caused by: "#]], ); } + +#[cargo_test] +fn user_config_toml_reserved() { + write_config_at( + ".cargo/config.toml", + " + [[include]] + path = 'user.config.toml' + ", + ); + + write_config_at( + ".cargo/user.config.toml", + " + key1 = 1 + ", + ); + + let gctx = GlobalContextBuilder::new() + .unstable_flag("config-include") + .build_err(); + assert_error( + gctx.unwrap_err(), + str![[r#" +could not load Cargo configuration + +Caused by: + `.cargo/user.config.toml` is a reserved configuration path, but was included in `[ROOT]/.cargo/config.toml` +"#]], + ); +} + +#[cargo_test] +fn config_toml_fragment_dir_reserved() { + write_config_at( + ".cargo/config.toml", + " + [[include]] + path = 'config.toml.d' + ", + ); + + write_config_at( + ".cargo/config.toml.d/00-myconfig.toml", + " + key1 = 1 + ", + ); + + let gctx = GlobalContextBuilder::new() + .unstable_flag("config-include") + .build_err(); + assert_error( + gctx.unwrap_err(), + str![[r#" +could not load Cargo configuration + +Caused by: + expected a config include path ending with `.toml`, but found `config.toml.d` from `[ROOT]/.cargo/config.toml` +"#]], + ); +} + +#[cargo_test] +fn user_config_toml_fragment_dir_reserved() { + write_config_at( + ".cargo/config.toml", + " + [[include]] + path = 'user.config.toml.d' + ", + ); + + write_config_at( + ".cargo/user.config.toml.d/00-myconfig.toml", + " + key1 = 1 + ", + ); + + let gctx = GlobalContextBuilder::new() + .unstable_flag("config-include") + .build_err(); + assert_error( + gctx.unwrap_err(), + str![[r#" +could not load Cargo configuration + +Caused by: + expected a config include path ending with `.toml`, but found `user.config.toml.d` from `[ROOT]/.cargo/config.toml` +"#]], + ); +}