Skip to content

Commit 64e46b0

Browse files
committed
Auto merge of #11318 - RyanAD:3736-improve-invalid-char-error, r=weihanglo
Add warning when PATH env separator is in project path Closes #3736 Adds a check during `cargo init` and `cargo new` to check if the path contains an invalid PATH character (`:`, `;`, or `"`). These characters can break things in various ways (including `cargo test`). These characters are not valid in certain environment variables and cannot be escaped. For more information see: https://github.com/rust-lang/rust/blob/7feb003882ecf7699e6705b537673e20985accff/library/std/src/sys/unix/os.rs#L233 https://man7.org/linux/man-pages/man8/ld.so.8.html https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html https://doc.rust-lang.org/std/env/fn.join_paths.html#errors To test cargo new and cargo init: `cargo new --name testing test:ing` `mkdir test:ing2 && cd 'test:ing2' && cargo init --name testing` To test the updated error message when in a directory with invalid characters: `cargo new testing3 && mv testing3 test:ing3 && cd 'test:ing3' && cargo test` cc `@weihanglo`
2 parents f5cdfa4 + eb8d3e2 commit 64e46b0

File tree

10 files changed

+129
-13
lines changed

10 files changed

+129
-13
lines changed

crates/cargo-util/src/paths.rs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,19 @@ use tempfile::Builder as TempFileBuilder;
1818
/// environment variable this is will be used for, which is included in the
1919
/// error message.
2020
pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> Result<OsString> {
21-
env::join_paths(paths.iter())
22-
.with_context(|| {
23-
let paths = paths.iter().map(Path::new).collect::<Vec<_>>();
24-
format!("failed to join path array: {:?}", paths)
25-
})
26-
.with_context(|| {
27-
format!(
28-
"failed to join search paths together\n\
29-
Does ${} have an unterminated quote character?",
30-
env
31-
)
32-
})
21+
env::join_paths(paths.iter()).with_context(|| {
22+
let mut message = format!(
23+
"failed to join paths from `${env}` together\n\n\
24+
Check if any of path segments listed below contain an \
25+
unterminated quote character or path separator:"
26+
);
27+
for path in paths {
28+
use std::fmt::Write;
29+
write!(&mut message, "\n {:?}", Path::new(path)).unwrap();
30+
}
31+
32+
message
33+
})
3334
}
3435

3536
/// Returns the name of the environment variable used for searching for
@@ -743,3 +744,44 @@ fn exclude_from_time_machine(path: &Path) {
743744
// Errors are ignored, since it's an optional feature and failure
744745
// doesn't prevent Cargo from working
745746
}
747+
748+
#[cfg(test)]
749+
mod tests {
750+
use super::join_paths;
751+
752+
#[test]
753+
fn join_paths_lists_paths_on_error() {
754+
let valid_paths = vec!["/testing/one", "/testing/two"];
755+
// does not fail on valid input
756+
let _joined = join_paths(&valid_paths, "TESTING1").unwrap();
757+
758+
#[cfg(unix)]
759+
{
760+
let invalid_paths = vec!["/testing/one", "/testing/t:wo/three"];
761+
let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
762+
assert_eq!(
763+
err.to_string(),
764+
"failed to join paths from `$TESTING2` together\n\n\
765+
Check if any of path segments listed below contain an \
766+
unterminated quote character or path separator:\
767+
\n \"/testing/one\"\
768+
\n \"/testing/t:wo/three\"\
769+
"
770+
);
771+
}
772+
#[cfg(windows)]
773+
{
774+
let invalid_paths = vec!["/testing/one", "/testing/t\"wo/three"];
775+
let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
776+
assert_eq!(
777+
err.to_string(),
778+
"failed to join paths from `$TESTING2` together\n\n\
779+
Check if any of path segments listed below contain an \
780+
unterminated quote character or path separator:\
781+
\n \"/testing/one\"\
782+
\n \"/testing/t\\\"wo/three\"\
783+
"
784+
);
785+
}
786+
}
787+
}

src/cargo/ops/cargo_new.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ use cargo_util::paths;
77
use serde::de;
88
use serde::Deserialize;
99
use std::collections::BTreeMap;
10-
use std::fmt;
10+
use std::ffi::OsStr;
1111
use std::io::{BufRead, BufReader, ErrorKind};
1212
use std::path::{Path, PathBuf};
1313
use std::str::FromStr;
14+
use std::{fmt, slice};
1415
use toml_edit::easy as toml;
1516

1617
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -261,6 +262,19 @@ fn check_name(
261262
Ok(())
262263
}
263264

265+
/// Checks if the path contains any invalid PATH env characters.
266+
fn check_path(path: &Path, shell: &mut Shell) -> CargoResult<()> {
267+
// warn if the path contains characters that will break `env::join_paths`
268+
if let Err(_) = paths::join_paths(slice::from_ref(&OsStr::new(path)), "") {
269+
let path = path.to_string_lossy();
270+
shell.warn(format!(
271+
"the path `{path}` contains invalid PATH characters (usually `:`, `;`, or `\"`)\n\
272+
It is recommended to use a different name to avoid problems."
273+
))?;
274+
}
275+
Ok(())
276+
}
277+
264278
fn detect_source_paths_and_types(
265279
package_path: &Path,
266280
package_name: &str,
@@ -421,6 +435,8 @@ pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> {
421435
)
422436
}
423437

438+
check_path(path, &mut config.shell())?;
439+
424440
let is_bin = opts.kind.is_bin();
425441

426442
let name = get_name(path, opts)?;
@@ -458,6 +474,8 @@ pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<NewProjectKind> {
458474
anyhow::bail!("`cargo init` cannot be run on existing Cargo packages")
459475
}
460476

477+
check_path(path, &mut config.shell())?;
478+
461479
let name = get_name(path, opts)?;
462480

463481
let mut src_paths_types = vec![];

tests/testsuite/init/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ mod mercurial_autodetect;
2828
mod multibin_project_name_clash;
2929
#[cfg(not(windows))]
3030
mod no_filename;
31+
#[cfg(unix)]
32+
mod path_contains_separator;
3133
mod pijul_autodetect;
3234
mod reserved_name;
3335
mod simple_bin;

tests/testsuite/init/path_contains_separator/in/.keep

Whitespace-only changes.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use cargo_test_support::compare::assert_ui;
2+
use cargo_test_support::prelude::*;
3+
use cargo_test_support::{t, Project};
4+
5+
use cargo_test_support::curr_dir;
6+
7+
#[cargo_test]
8+
fn path_contains_separator() {
9+
let project = Project::from_template(curr_dir!().join("in"));
10+
let project_root = &project.root().join("test:ing");
11+
12+
if !project_root.exists() {
13+
t!(std::fs::create_dir(&project_root));
14+
}
15+
16+
snapbox::cmd::Command::cargo_ui()
17+
.arg_line("init --bin --vcs none --edition 2015 --name testing")
18+
.current_dir(project_root)
19+
.assert()
20+
.success()
21+
.stdout_matches_path(curr_dir!().join("stdout.log"))
22+
.stderr_matches_path(curr_dir!().join("stderr.log"));
23+
24+
assert_ui().subset_matches(curr_dir!().join("out"), project_root);
25+
assert!(!project_root.join(".gitignore").is_file());
26+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "testing"
3+
version = "0.1.0"
4+
edition = "2015"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
println!("Hello, world!");
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
warning: the path `[ROOT]/case/test:ing/.` contains invalid PATH characters (usually `:`, `;`, or `"`)
2+
It is recommended to use a different name to avoid problems.
3+
Created binary (application) package

tests/testsuite/init/path_contains_separator/stdout.log

Whitespace-only changes.

tests/testsuite/new.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,17 @@ Caused by:
529529
)
530530
.run();
531531
}
532+
533+
#[cfg(unix)]
534+
#[cargo_test]
535+
fn path_with_invalid_character() {
536+
cargo_process("new --name testing test:ing")
537+
.with_stderr(
538+
"\
539+
[WARNING] the path `[CWD]/test:ing` contains invalid PATH characters (usually `:`, `;`, or `\"`)
540+
It is recommended to use a different name to avoid problems.
541+
[CREATED] binary (application) `testing` package
542+
",
543+
)
544+
.run();
545+
}

0 commit comments

Comments
 (0)