diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 41cd92010..9a9e35e00 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -61,7 +61,9 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{dev_t, major, minor}; use uucore::line_ending::LineEnding; use uucore::locale::{get_message, get_message_with_args}; -use uucore::quoting_style::{self, QuotingStyle, escape_name}; +use uucore::quoting_style::{ + self, QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name, +}; use uucore::{ display::Quotable, error::{UError, UResult, set_exit_code}, @@ -2008,7 +2010,7 @@ fn show_dir_name( config: &Config, ) -> std::io::Result<()> { let escaped_name = - quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); + locale_aware_escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); let name = if config.hyperlink && !config.dired { create_hyperlink(&escaped_name, path_data) @@ -2509,7 +2511,7 @@ fn display_items( // option, print the security context to the left of the size column. let quoted = items.iter().any(|item| { - let name = escape_name(&item.display_name, &config.quoting_style); + let name = locale_aware_escape_name(&item.display_name, &config.quoting_style); os_str_starts_with(&name, b"'") }); @@ -3152,7 +3154,7 @@ fn classify_file(path: &PathData, out: &mut BufWriter) -> Option { /// Takes a [`PathData`] struct and returns a cell with a name ready for displaying. /// /// This function relies on the following parameters in the provided `&Config`: -/// * `config.quoting_style` to decide how we will escape `name` using [`escape_name`]. +/// * `config.quoting_style` to decide how we will escape `name` using [`locale_aware_escape_name`]. /// * `config.inode` decides whether to display inode numbers beside names using [`get_inode`]. /// * `config.color` decides whether it's going to color `name` using [`color_name`]. /// * `config.indicator_style` to append specific characters to `name` using [`classify_file`]. @@ -3173,7 +3175,7 @@ fn display_item_name( current_column: LazyCell usize + '_>>, ) -> OsString { // This is our return value. We start by `&path.display_name` and modify it along the way. - let mut name = escape_name(&path.display_name, &config.quoting_style); + let mut name = locale_aware_escape_name(&path.display_name, &config.quoting_style); let is_wrap = |namelen: usize| config.width != 0 && *current_column + namelen > config.width.into(); @@ -3265,7 +3267,7 @@ fn display_item_name( name.push(path.p_buf.read_link().unwrap()); } else { name.push(color_name( - escape_name(target.as_os_str(), &config.quoting_style), + locale_aware_escape_name(target.as_os_str(), &config.quoting_style), path, style_manager, &mut state.out, @@ -3276,7 +3278,10 @@ fn display_item_name( } else { // If no coloring is required, we just use target as is. // Apply the right quoting - name.push(escape_name(target.as_os_str(), &config.quoting_style)); + name.push(locale_aware_escape_name( + target.as_os_str(), + &config.quoting_style, + )); } } Err(err) => { diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 65a9c6fe1..c00b09bbf 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -259,7 +259,9 @@ impl<'a> Input<'a> { Self::Path(path) => { let path = path.as_os_str(); if path.to_string_lossy().contains('\n') { - Some(Cow::Owned(quoting_style::escape_name(path, QS_ESCAPE))) + Some(Cow::Owned(quoting_style::locale_aware_escape_name( + path, QS_ESCAPE, + ))) } else { Some(Cow::Borrowed(path)) } @@ -759,7 +761,7 @@ fn files0_iter_file<'a>(path: &Path) -> UResult( } fn escape_name_wrapper(name: &OsStr) -> String { - quoting_style::escape_name(name, QS_ESCAPE) + quoting_style::locale_aware_escape_name(name, QS_ESCAPE) .into_string() .expect("All escaped names with the escaping option return valid strings.") } diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index f3edbae55..349527db7 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -8,7 +8,7 @@ use crate::format::spec::ArgumentLocation; use crate::{ error::set_exit_code, parser::num_parser::{ExtendedParser, ExtendedParserError}, - quoting_style::{Quotes, QuotingStyle, escape_name}, + quoting_style::{Quotes, QuotingStyle, locale_aware_escape_name}, show_error, show_warning, }; use os_display::Quotable; @@ -153,7 +153,7 @@ fn extract_value(p: Result>, input: &s Ok(v) => v, Err(e) => { set_exit_code(1); - let input = escape_name( + let input = locale_aware_escape_name( OsStr::new(input), &QuotingStyle::C { quotes: Quotes::None, diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index d22626590..3cffc08bc 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -5,7 +5,7 @@ // spell-checker:ignore (vars) intmax ptrdiff padlen -use crate::quoting_style::{QuotingStyle, escape_name}; +use crate::quoting_style::{QuotingStyle, locale_aware_escape_name}; use super::{ ExtendedBigDecimal, FormatChar, FormatError, OctalParsing, @@ -402,7 +402,7 @@ impl Spec { writer.write_all(&parsed).map_err(FormatError::IoError) } Self::QuotedString { position } => { - let s = escape_name( + let s = locale_aware_escape_name( args.next_string(position).as_ref(), &QuotingStyle::Shell { escape: true, diff --git a/src/uucore/src/lib/features/quoting_style/mod.rs b/src/uucore/src/lib/features/quoting_style/mod.rs index 107bdcf06..c9651c0ea 100644 --- a/src/uucore/src/lib/features/quoting_style/mod.rs +++ b/src/uucore/src/lib/features/quoting_style/mod.rs @@ -8,6 +8,7 @@ use std::ffi::{OsStr, OsString}; use std::fmt; +use crate::i18n::{self, UEncoding}; use crate::quoting_style::c_quoter::CQuoter; use crate::quoting_style::literal_quoter::LiteralQuoter; use crate::quoting_style::shell_quoter::{EscapedShellQuoter, NonEscapedShellQuoter}; @@ -89,7 +90,12 @@ pub enum Quotes { /// /// This inner function provides an additional flag `dirname` which /// is meant for ls' directory name display. -fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec { +fn escape_name_inner( + name: &[u8], + style: &QuotingStyle, + dirname: bool, + encoding: UEncoding, +) -> Vec { // Early handle Literal with show_control style if let QuotingStyle::Literal { show_control: true } = style { return name.to_owned(); @@ -121,30 +127,53 @@ fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec { + for b in name { + if b.is_ascii() { + quoter.push_char(*b as char); + } else { + quoter.push_invalid(&[*b]); + } + } + } + UEncoding::Utf8 => { + for chunk in name.utf8_chunks() { + quoter.push_str(chunk.valid()); + quoter.push_invalid(chunk.invalid()); + } + } } quoter.finalize() } /// Escape a filename with respect to the given style. -pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> OsString { +pub fn escape_name(name: &OsStr, style: &QuotingStyle, encoding: UEncoding) -> OsString { let name = crate::os_str_as_bytes_lossy(name); - crate::os_string_from_vec(escape_name_inner(&name, style, false)) + crate::os_string_from_vec(escape_name_inner(&name, style, false, encoding)) .expect("all byte sequences should be valid for platform, or already replaced in name") } +/// Retrieve the encoding from the locale and pass it to `escape_name`. +pub fn locale_aware_escape_name(name: &OsStr, style: &QuotingStyle) -> OsString { + escape_name(name, style, i18n::get_locale_encoding()) +} + /// 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) -> OsString { +pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle, encoding: UEncoding) -> OsString { let name = crate::os_str_as_bytes_lossy(dir_name); - crate::os_string_from_vec(escape_name_inner(&name, style, true)) + crate::os_string_from_vec(escape_name_inner(&name, style, true, encoding)) .expect("all byte sequences should be valid for platform, or already replaced in name") } +/// Retrieve the encoding from the locale and pass it to `escape_dir_name`. +pub fn locale_aware_escape_dir_name(name: &OsStr, style: &QuotingStyle) -> OsString { + escape_dir_name(name, style, i18n::get_locale_encoding()) +} + impl fmt::Display for QuotingStyle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -183,7 +212,10 @@ impl fmt::Display for Quotes { #[cfg(test)] mod tests { - use crate::quoting_style::{Quotes, QuotingStyle, escape_name_inner}; + use crate::{ + i18n::UEncoding, + quoting_style::{Quotes, QuotingStyle, escape_name_inner}, + }; // spell-checker:ignore (tests/words) one\'two one'two @@ -235,7 +267,7 @@ mod tests { fn check_names_inner(name: &[u8], map: &[(T, &str)]) -> Vec> { map.iter() - .map(|(_, style)| escape_name_inner(name, &get_style(style), false)) + .map(|(_, style)| escape_name_inner(name, &get_style(style), false, UEncoding::Utf8)) .collect() } diff --git a/test/sums b/test/sums new file mode 100644 index 000000000..9a3546341 --- /dev/null +++ b/test/sums @@ -0,0 +1,2 @@ +SHA256 (funky˙name) = 29953405eaa3dcc41c37d1621d55b6a47eee93e05613e439e73295029740b10c +SHA256 (funky˙) = 29953405eaa3dcc41c37d1621d55b6a47eee93e05613e439e73295029740b10c