diff --git a/src/xargs/mod.rs b/src/xargs/mod.rs index ff833335..782abf35 100644 --- a/src/xargs/mod.rs +++ b/src/xargs/mod.rs @@ -618,6 +618,12 @@ where match (&escape, pending[i]) { (Some(Escape::Quote(quote)), c) if c == *quote => escape = None, + (Some(Escape::Quote(q)), b'\n') => { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Unterminated quote: {q}"), + )); + } (Some(Escape::Quote(_)), c) => result.push(c), (Some(Escape::Slash), c) => { result.push(c); diff --git a/tests/test_xargs.rs b/tests/test_xargs.rs index 997c7e85..287b304a 100644 --- a/tests/test_xargs.rs +++ b/tests/test_xargs.rs @@ -390,6 +390,25 @@ fn xargs_unterminated_quote() { .no_stdout(); } +#[test] +fn xargs_quotes_cannot_span_lines() { + let temp_file = tempfile::NamedTempFile::new().unwrap(); + + for quote in ['\'', '"'] { + std::fs::write( + temp_file.path(), + format!("alpha{quote}beta\ngamma{quote}\n"), + ) + .unwrap(); + + ucmd() + .args(&["-a", &temp_file.path().to_string_lossy()]) + .fails_with_code(1) + .stderr_contains("Error: Unterminated quote:") + .no_stdout(); + } +} + #[test] fn xargs_zero_lines() { ucmd()