From 26a5806b6ecaa369d485f45069723e5b5c97a135 Mon Sep 17 00:00:00 2001 From: max-amb Date: Tue, 30 Dec 2025 17:20:13 +0000 Subject: [PATCH 1/5] stat: Added newline escaping for file names --- src/uu/stat/src/stat.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 327e89a6888..7acb7b470cb 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -440,8 +440,19 @@ fn quote_file_name(file_name: &str, quoting_style: &QuotingStyle) -> String { format!("'{escaped}'") } QuotingStyle::ShellEscapeAlways => { - let quote = if file_name.contains('\'') { '"' } else { '\'' }; - format!("{quote}{file_name}{quote}") + if !file_name.contains(|x: char| x.is_control()) { + let quote = if file_name.contains('\'') { '"' } else { '\'' }; + return format!("{quote}{file_name}{quote}"); + } + let mut escaped_file_name = String::from("$'"); + for c in file_name.chars() { + match c { + '\n' => escaped_file_name.push_str("\\n"), + _ => escaped_file_name.push(c), + } + } + escaped_file_name.push('\''); + escaped_file_name } QuotingStyle::Quote => file_name.to_string(), } From bd75dde65e131da61182d349f92e3265e661c95a Mon Sep 17 00:00:00 2001 From: max-amb Date: Tue, 30 Dec 2025 17:36:46 +0000 Subject: [PATCH 2/5] rustfmt cleaning up use --- src/uu/stat/src/stat.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 7acb7b470cb..9bd065bae0e 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -11,8 +11,8 @@ use clap::builder::ValueParser; use uucore::display::Quotable; use uucore::fs::display_permissions; use uucore::fsext::{ - FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, pretty_fstype, - read_fs_list, statfs, + metadata_get_time, pretty_filetype, pretty_fstype, read_fs_list, statfs, FsMeta, + MetadataTimeField, StatFs, }; use uucore::libc::mode_t; use uucore::{entries, format_usage, show_error, show_warning}; @@ -27,7 +27,7 @@ use std::path::Path; use std::{env, fs}; use thiserror::Error; -use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec}; +use uucore::time::{format_system_time, system_time_to_sec, FormatSystemTimeFallback}; #[derive(Debug, Error)] enum StatError { @@ -1402,7 +1402,7 @@ fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String { mod tests { use crate::{pad_and_print_bytes, print_padding, quote_file_name}; - use super::{Flags, Precision, ScanUtil, Stater, Token, group_num, precision_trunc}; + use super::{group_num, precision_trunc, Flags, Precision, ScanUtil, Stater, Token}; #[test] fn test_scanners() { From 65502605a1dd9753f9dd192d0d7fa52f02c9c1f5 Mon Sep 17 00:00:00 2001 From: max-amb Date: Tue, 30 Dec 2025 17:37:00 +0000 Subject: [PATCH 3/5] Added test as part of tests::test_quote_file_name --- src/uu/stat/src/stat.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 9bd065bae0e..0380d5430b3 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -1565,5 +1565,11 @@ mod tests { quote_file_name(file_name, &crate::QuotingStyle::ShellEscapeAlways), "\'nice\" file\'" ); + + let file_name = "nice\n file"; + assert_eq!( + quote_file_name(file_name, &crate::QuotingStyle::ShellEscapeAlways), + "$'nice\\n file'" + ); } } From da8855238669ba67e750dbde0d99fedd5f97b8ae Mon Sep 17 00:00:00 2001 From: max-amb Date: Tue, 30 Dec 2025 21:46:41 +0000 Subject: [PATCH 4/5] Moved test to integration test --- src/uu/stat/src/stat.rs | 6 ------ tests/by-util/test_stat.rs | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 0380d5430b3..9bd065bae0e 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -1565,11 +1565,5 @@ mod tests { quote_file_name(file_name, &crate::QuotingStyle::ShellEscapeAlways), "\'nice\" file\'" ); - - let file_name = "nice\n file"; - assert_eq!( - quote_file_name(file_name, &crate::QuotingStyle::ShellEscapeAlways), - "$'nice\\n file'" - ); } } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 0aad7361bb8..fcc94f890f3 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -567,3 +567,25 @@ fn test_mount_point_combined_with_other_specifiers() { "Should print mount point, file name, and size" ); } + +#[test] +fn test_quote_file_name_with_newline() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let file_name = "nice\\n file"; + let formatted_file_name = &format!("$'{}'", file_name); + let expected_result = "{\"name\":\"\"$'nice\\n file'\"\"}\n"; + at.touch(formatted_file_name); + + let result = ts + .ucmd() + .args(&["-c", "{\"name\":\"%N\"}", formatted_file_name]) + .succeeds() + .stdout_move_str(); + + assert_eq!( + result, expected_result, + "We are testing that \\n is handled correctly" + ); +} From 22605cb0c5b2fc7fa83272d757b338a329dcc405 Mon Sep 17 00:00:00 2001 From: max-amb Date: Tue, 30 Dec 2025 21:58:23 +0000 Subject: [PATCH 5/5] Revert previous fmt changes --- src/uu/stat/src/stat.rs | 8 ++++---- tests/by-util/test_stat.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 9bd065bae0e..7acb7b470cb 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -11,8 +11,8 @@ use clap::builder::ValueParser; use uucore::display::Quotable; use uucore::fs::display_permissions; use uucore::fsext::{ - metadata_get_time, pretty_filetype, pretty_fstype, read_fs_list, statfs, FsMeta, - MetadataTimeField, StatFs, + FsMeta, MetadataTimeField, StatFs, metadata_get_time, pretty_filetype, pretty_fstype, + read_fs_list, statfs, }; use uucore::libc::mode_t; use uucore::{entries, format_usage, show_error, show_warning}; @@ -27,7 +27,7 @@ use std::path::Path; use std::{env, fs}; use thiserror::Error; -use uucore::time::{format_system_time, system_time_to_sec, FormatSystemTimeFallback}; +use uucore::time::{FormatSystemTimeFallback, format_system_time, system_time_to_sec}; #[derive(Debug, Error)] enum StatError { @@ -1402,7 +1402,7 @@ fn pretty_time(meta: &Metadata, md_time_field: MetadataTimeField) -> String { mod tests { use crate::{pad_and_print_bytes, print_padding, quote_file_name}; - use super::{group_num, precision_trunc, Flags, Precision, ScanUtil, Stater, Token}; + use super::{Flags, Precision, ScanUtil, Stater, Token, group_num, precision_trunc}; #[test] fn test_scanners() { diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index fcc94f890f3..1dfc442df1d 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -574,7 +574,7 @@ fn test_quote_file_name_with_newline() { let at = &ts.fixtures; let file_name = "nice\\n file"; - let formatted_file_name = &format!("$'{}'", file_name); + let formatted_file_name = &format!("$'{file_name}'"); let expected_result = "{\"name\":\"\"$'nice\\n file'\"\"}\n"; at.touch(formatted_file_name);