diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 66e80b90d..937b3d23c 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -10,6 +10,7 @@ mod mode; use clap::{Arg, ArgAction, ArgMatches, Command}; use file_diff::diff; use filetime::{FileTime, set_file_times}; +use std::ffi::OsString; use std::fmt::Debug; use std::fs::File; use std::fs::{self, metadata}; @@ -168,9 +169,9 @@ static ARG_FILES: &str = "files"; pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from_localized(args); - let paths: Vec = matches - .get_many::(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) + let paths: Vec = matches + .get_many::(ARG_FILES) + .map(|v| v.cloned().collect()) .unwrap_or_default(); let behavior = behavior(&matches)?; @@ -303,7 +304,8 @@ pub fn uu_app() -> Command { Arg::new(ARG_FILES) .action(ArgAction::Append) .num_args(1..) - .value_hint(clap::ValueHint::AnyPath), + .value_hint(clap::ValueHint::AnyPath) + .value_parser(clap::value_parser!(OsString)), ) } @@ -435,7 +437,7 @@ fn behavior(matches: &ArgMatches) -> UResult { /// /// Returns a Result type with the Err variant containing the error message. /// -fn directory(paths: &[String], b: &Behavior) -> UResult<()> { +fn directory(paths: &[OsString], b: &Behavior) -> UResult<()> { if paths.is_empty() { Err(InstallError::DirNeedsArg.into()) } else { @@ -518,7 +520,7 @@ fn is_potential_directory_path(path: &Path) -> bool { /// Returns a Result type with the Err variant containing the error message. /// #[allow(clippy::cognitive_complexity)] -fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { +fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { // first check that paths contains at least one element if paths.is_empty() { return Err(UUsageError::new( @@ -528,7 +530,7 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { } if b.no_target_dir && paths.len() > 2 { return Err(InstallError::ExtraOperand( - paths[2].clone(), + paths[2].to_string_lossy().into_owned(), format_usage(&translate!("install-usage")), ) .into()); @@ -544,7 +546,7 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { if paths.is_empty() { return Err(UUsageError::new( 1, - translate!("install-error-missing-destination-operand", "path" => last_path.to_str().unwrap()), + translate!("install-error-missing-destination-operand", "path" => last_path.to_string_lossy()), )); } @@ -566,8 +568,12 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { if let Some(to_create) = to_create { // if the path ends in /, remove it + let to_create_owned; let to_create = if to_create.to_string_lossy().ends_with('/') { - Path::new(to_create.to_str().unwrap().trim_end_matches('/')) + let path_str = to_create.to_string_lossy(); + let trimmed = path_str.trim_end_matches('/'); + to_create_owned = PathBuf::from(trimmed); + to_create_owned.as_path() } else { to_create }; @@ -835,7 +841,7 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> { /// fn strip_file(to: &Path, b: &Behavior) -> UResult<()> { // Check if the filename starts with a hyphen and adjust the path - let to_str = to.as_os_str().to_str().unwrap_or_default(); + let to_str = to.to_string_lossy(); let to = if to_str.starts_with('-') { let mut new_path = PathBuf::from("."); new_path.push(to); @@ -1085,7 +1091,7 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool { } // Check if the contents of the source and destination files differ. - if !diff(from.to_str().unwrap(), to.to_str().unwrap()) { + if !diff(&from.to_string_lossy(), &to.to_string_lossy()) { return true; } diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index ea3448bbb..ded775579 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -7,6 +7,8 @@ #[cfg(not(target_os = "openbsd"))] use filetime::FileTime; use std::fs; +#[cfg(target_os = "linux")] +use std::os::unix::ffi::OsStringExt; use std::os::unix::fs::{MetadataExt, PermissionsExt}; #[cfg(not(windows))] use std::process::Command; @@ -2366,3 +2368,16 @@ fn test_install_compare_with_mode_bits() { ); } } + +#[test] +#[cfg(target_os = "linux")] +fn test_install_non_utf8_paths() { + let (at, mut ucmd) = at_and_ucmd!(); + let source_filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]); + let dest_dir = "target_dir"; + + std::fs::write(at.plus(&source_filename), b"test content").unwrap(); + at.mkdir(dest_dir); + + ucmd.arg(&source_filename).arg(dest_dir).succeeds(); +}