mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
printf: fix %q shell quoting with control chars and quotes (#10816)
This commit is contained in:
@@ -40,7 +40,7 @@ impl<'a> NonEscapedShellQuoter<'a> {
|
||||
dirname: bool,
|
||||
size_hint: usize,
|
||||
) -> Self {
|
||||
let (quotes, must_quote) = initial_quoting(reference, dirname, always_quote);
|
||||
let (quotes, must_quote) = initial_quoting(reference, dirname, always_quote, false);
|
||||
Self {
|
||||
reference,
|
||||
quotes,
|
||||
@@ -108,7 +108,7 @@ pub(super) struct EscapedShellQuoter<'a> {
|
||||
|
||||
impl<'a> EscapedShellQuoter<'a> {
|
||||
pub fn new(reference: &'a [u8], always_quote: bool, dirname: bool, size_hint: usize) -> Self {
|
||||
let (quotes, must_quote) = initial_quoting(reference, dirname, always_quote);
|
||||
let (quotes, must_quote) = initial_quoting(reference, dirname, always_quote, true);
|
||||
Self {
|
||||
reference,
|
||||
quotes,
|
||||
@@ -185,11 +185,17 @@ impl Quoter for EscapedShellQuoter<'_> {
|
||||
}
|
||||
|
||||
/// Deduce the initial quoting status from the provided information
|
||||
fn initial_quoting(input: &[u8], dirname: bool, always_quote: bool) -> (Quotes, bool) {
|
||||
if input
|
||||
.iter()
|
||||
.any(|c| shell_escaped_char_set(dirname).contains(c))
|
||||
{
|
||||
fn initial_quoting(
|
||||
input: &[u8],
|
||||
dirname: bool,
|
||||
always_quote: bool,
|
||||
check_control_chars: bool,
|
||||
) -> (Quotes, bool) {
|
||||
let has_special_chars = input.iter().any(|c| {
|
||||
shell_escaped_char_set(dirname).contains(c) || (check_control_chars && c.is_ascii_control())
|
||||
});
|
||||
|
||||
if has_special_chars {
|
||||
(Quotes::Single, true)
|
||||
} else if input.contains(&b'\'') {
|
||||
(Quotes::Double, true)
|
||||
@@ -239,3 +245,73 @@ fn finalize_shell_quoter(
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_initial_quoting() {
|
||||
// Control chars (0-31 and 0x7F) force single quotes in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"\x01", false, false, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
|
||||
// Control + quote uses single quotes (segmented) in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"\x01'\x01", false, false, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
|
||||
// Simple quote uses double quotes in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"a'b", false, false, true),
|
||||
(Quotes::Double, true)
|
||||
);
|
||||
|
||||
// Shell special chars force single quotes in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"test$var", false, false, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
assert_eq!(
|
||||
initial_quoting(b"test\nline", false, false, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
|
||||
// Empty string forces quotes in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"", false, false, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
|
||||
// Always quote flag works in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"normal", false, true, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
|
||||
// Normal text doesn't need quoting in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"hello", false, false, true),
|
||||
(Quotes::Single, false)
|
||||
);
|
||||
|
||||
// Dirname affects colon handling in escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"dir:name", true, false, true),
|
||||
(Quotes::Single, true)
|
||||
);
|
||||
assert_eq!(
|
||||
initial_quoting(b"file:name", false, false, true),
|
||||
(Quotes::Single, false)
|
||||
);
|
||||
|
||||
// Control chars ignored in non-escape mode
|
||||
assert_eq!(
|
||||
initial_quoting(b"\x01", false, false, false),
|
||||
(Quotes::Single, false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1492,3 +1492,16 @@ fn test_extreme_field_width_overflow() {
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("printf: write error"); //could contains additional message like "formatting width too large" not in GNU, thats fine.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_q_string_control_chars_with_quotes() {
|
||||
// Test %q with control characters and single quotes combined.
|
||||
// This tests the fix for the GNU compatibility issue where control
|
||||
// characters combined with single quotes should use the segmented
|
||||
// quoting approach rather than double quotes.
|
||||
let input = "\x01'\x01";
|
||||
new_ucmd!()
|
||||
.args(&["%q", input])
|
||||
.succeeds()
|
||||
.stdout_only("''$'\\001'\\'''$'\\001'");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user