diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f4e34714704..9a22006e097 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -21,7 +21,7 @@ use std::os::windows::fs::MetadataExt; use std::{ cmp::Reverse, error::Error, - ffi::OsString, + ffi::{OsStr, OsString}, fmt::{Display, Write as FmtWrite}, fs::{self, DirEntry, FileType, Metadata, ReadDir}, io::{stdout, BufWriter, ErrorKind, Stdout, Write}, @@ -55,7 +55,7 @@ use uucore::libc::{dev_t, major, minor}; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::line_ending::LineEnding; -use uucore::quoting_style::{escape_dir_name, escape_name, QuotingStyle}; +use uucore::quoting_style::{self, QuotingStyle}; use uucore::{ display::Quotable, error::{set_exit_code, UError, UResult}, @@ -2048,7 +2048,11 @@ impl PathData { /// file11 /// ``` fn show_dir_name(path_data: &PathData, out: &mut BufWriter, config: &Config) { - let escaped_name = escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); + // FIXME: replace this with appropriate behavior for literal unprintable bytes + let escaped_name = + quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style) + .to_string_lossy() + .to_string(); let name = if config.hyperlink && !config.dired { create_hyperlink(&escaped_name, path_data) @@ -3002,7 +3006,6 @@ use std::sync::Mutex; #[cfg(unix)] use uucore::entries; use uucore::fs::FileInformation; -use uucore::quoting_style; #[cfg(unix)] fn cached_uid2usr(uid: u32) -> String { @@ -3542,3 +3545,10 @@ fn calculate_padding_collection( padding_collections } + +// FIXME: replace this with appropriate behavior for literal unprintable bytes +fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { + quoting_style::escape_name(name, style) + .to_string_lossy() + .to_string() +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 33b70ee62f5..1c2d99628f7 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -13,7 +13,7 @@ mod word_count; use std::{ borrow::{Borrow, Cow}, cmp::max, - ffi::OsString, + ffi::{OsStr, OsString}, fs::{self, File}, io::{self, Write}, iter, @@ -28,7 +28,7 @@ use utf8::{BufReadDecoder, BufReadDecoderError}; use uucore::{ error::{FromIo, UError, UResult}, format_usage, help_about, help_usage, - quoting_style::{escape_name, QuotingStyle}, + quoting_style::{self, QuotingStyle}, shortcut_value_parser::ShortcutValueParser, show, }; @@ -259,7 +259,7 @@ impl<'a> Input<'a> { match self { Self::Path(path) => Some(match path.to_str() { Some(s) if !s.contains('\n') => Cow::Borrowed(s), - _ => Cow::Owned(escape_name(path.as_os_str(), QS_ESCAPE)), + _ => Cow::Owned(escape_name_wrapper(path.as_os_str())), }), Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(STDIN_REPR)), Self::Stdin(StdinKind::Implicit) => None, @@ -269,7 +269,7 @@ impl<'a> Input<'a> { /// Converts input into the form that appears in errors. fn path_display(&self) -> String { match self { - Self::Path(path) => escape_name(path.as_os_str(), QS_ESCAPE), + Self::Path(path) => escape_name_wrapper(path.as_os_str()), Self::Stdin(_) => String::from("standard input"), } } @@ -361,7 +361,7 @@ impl WcError { Some((input, idx)) => { let path = match input { Input::Stdin(_) => STDIN_REPR.into(), - Input::Path(path) => escape_name(path.as_os_str(), QS_ESCAPE).into(), + Input::Path(path) => escape_name_wrapper(path.as_os_str()).into(), }; Self::ZeroLengthFileNameCtx { path, idx } } @@ -761,7 +761,9 @@ fn files0_iter_file<'a>(path: &Path) -> UResult Err(e.map_err_context(|| { format!( "cannot open {} for reading", - escape_name(path.as_os_str(), QS_QUOTE_ESCAPE) + quoting_style::escape_name(path.as_os_str(), QS_QUOTE_ESCAPE) + .into_string() + .expect("All escaped names with the escaping option return valid strings.") ) })), } @@ -793,9 +795,9 @@ fn files0_iter<'a>( Ok(Input::Path(PathBuf::from(s).into())) } } - Err(e) => Err(e.map_err_context(|| { - format!("{}: read error", escape_name(&err_path, QS_ESCAPE)) - }) as Box), + Err(e) => Err(e + .map_err_context(|| format!("{}: read error", escape_name_wrapper(&err_path))) + as Box), }), ); // Loop until there is an error; yield that error and then nothing else. @@ -808,6 +810,12 @@ fn files0_iter<'a>( }) } +fn escape_name_wrapper(name: &OsStr) -> String { + quoting_style::escape_name(name, QS_ESCAPE) + .into_string() + .expect("All escaped names with the escaping option return valid strings.") +} + fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { let mut total_word_count = WordCount::default(); let mut num_inputs: usize = 0; diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 75851049895..5cdd0342122 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -112,7 +112,8 @@ fn extract_value(p: Result>, input: &str) -> T Default::default() } ParseError::PartialMatch(v, rest) => { - if input.starts_with('\'') { + let bytes = input.as_encoded_bytes(); + if !bytes.is_empty() && bytes[0] == b'\'' { show_warning!( "{}: character(s) following character constant have been ignored", &rest, diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 581e1fa0624..81dbc1ebc29 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -353,20 +353,20 @@ impl Spec { writer.write_all(&parsed).map_err(FormatError::IoError) } Self::QuotedString => { - let s = args.get_str(); - writer - .write_all( - escape_name( - s.as_ref(), - &QuotingStyle::Shell { - escape: true, - always_quote: false, - show_control: false, - }, - ) - .as_bytes(), - ) - .map_err(FormatError::IoError) + let s = escape_name( + args.get_str().as_ref(), + &QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }, + ); + #[cfg(unix)] + let bytes = std::os::unix::ffi::OsStringExt::into_vec(s); + #[cfg(not(unix))] + let bytes = s.to_string_lossy().as_bytes().to_owned(); + + writer.write_all(&bytes).map_err(FormatError::IoError) } Self::SignedInt { width, diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index 8abc6bda7b0..2e9cd0b7ec7 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -6,8 +6,10 @@ //! Set of functions for escaping names according to different quoting styles. use std::char::from_digit; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fmt; +#[cfg(unix)] +use std::os::unix::ffi::{OsStrExt, OsStringExt}; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. @@ -459,17 +461,37 @@ fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec String { - let name = name.to_string_lossy(); - String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)).to_string() +pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> OsString { + #[cfg(unix)] + { + let name = name.as_bytes(); + OsStringExt::from_vec(escape_name_inner(name, style, false)) + } + #[cfg(not(unix))] + { + let name = name.to_string_lossy(); + String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)) + .to_string() + .into() + } } /// Escape a directory name with respect to the given style. /// This is mainly meant to be used for ls' directory name printing and is not /// likely to be used elsewhere. -pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> String { - let dir_name = dir_name.to_string_lossy(); - String::from_utf8_lossy(&escape_name_inner(dir_name.as_bytes(), style, true)).to_string() +pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> OsString { + #[cfg(unix)] + { + let name = dir_name.as_bytes(); + OsStringExt::from_vec(escape_name_inner(name, style, true)) + } + #[cfg(not(unix))] + { + let name = dir_name.to_string_lossy(); + String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, true)) + .to_string() + .into() + } } impl fmt::Display for QuotingStyle {