@@ -40,7 +40,7 @@ impl<'a> NonEscapedShellQuoter<'a> {
4040 dirname : bool ,
4141 size_hint : usize ,
4242 ) -> Self {
43- let ( quotes, must_quote) = initial_quoting ( reference, dirname, always_quote) ;
43+ let ( quotes, must_quote) = initial_quoting ( reference, dirname, always_quote, false ) ;
4444 Self {
4545 reference,
4646 quotes,
@@ -108,7 +108,7 @@ pub(super) struct EscapedShellQuoter<'a> {
108108
109109impl < ' a > EscapedShellQuoter < ' a > {
110110 pub fn new ( reference : & ' a [ u8 ] , always_quote : bool , dirname : bool , size_hint : usize ) -> Self {
111- let ( quotes, must_quote) = initial_quoting ( reference, dirname, always_quote) ;
111+ let ( quotes, must_quote) = initial_quoting ( reference, dirname, always_quote, true ) ;
112112 Self {
113113 reference,
114114 quotes,
@@ -185,11 +185,20 @@ impl Quoter for EscapedShellQuoter<'_> {
185185}
186186
187187/// Deduce the initial quoting status from the provided information
188- fn initial_quoting ( input : & [ u8 ] , dirname : bool , always_quote : bool ) -> ( Quotes , bool ) {
189- if input
190- . iter ( )
191- . any ( |c| shell_escaped_char_set ( dirname) . contains ( c) )
192- {
188+ fn initial_quoting (
189+ input : & [ u8 ] ,
190+ dirname : bool ,
191+ always_quote : bool ,
192+ check_control_chars : bool ,
193+ ) -> ( Quotes , bool ) {
194+ const ASCII_CONTROL_LIMIT : u8 = 32 ;
195+
196+ let has_special_chars = input. iter ( ) . any ( |c| {
197+ shell_escaped_char_set ( dirname) . contains ( c)
198+ || ( check_control_chars && * c < ASCII_CONTROL_LIMIT )
199+ } ) ;
200+
201+ if has_special_chars {
193202 ( Quotes :: Single , true )
194203 } else if input. contains ( & b'\'' ) {
195204 ( Quotes :: Double , true )
@@ -239,3 +248,73 @@ fn finalize_shell_quoter(
239248 buffer
240249 }
241250}
251+
252+ #[ cfg( test) ]
253+ mod tests {
254+ use super :: * ;
255+
256+ #[ test]
257+ fn test_initial_quoting ( ) {
258+ // Control chars (< 32) force single quotes in escape mode
259+ assert_eq ! (
260+ initial_quoting( b"\x01 " , false , false , true ) ,
261+ ( Quotes :: Single , true )
262+ ) ;
263+
264+ // Control + quote uses single quotes (segmented) in escape mode
265+ assert_eq ! (
266+ initial_quoting( b"\x01 '\x01 " , false , false , true ) ,
267+ ( Quotes :: Single , true )
268+ ) ;
269+
270+ // Simple quote uses double quotes in escape mode
271+ assert_eq ! (
272+ initial_quoting( b"a'b" , false , false , true ) ,
273+ ( Quotes :: Double , true )
274+ ) ;
275+
276+ // Shell special chars force single quotes in escape mode
277+ assert_eq ! (
278+ initial_quoting( b"test$var" , false , false , true ) ,
279+ ( Quotes :: Single , true )
280+ ) ;
281+ assert_eq ! (
282+ initial_quoting( b"test\n line" , false , false , true ) ,
283+ ( Quotes :: Single , true )
284+ ) ;
285+
286+ // Empty string forces quotes in escape mode
287+ assert_eq ! (
288+ initial_quoting( b"" , false , false , true ) ,
289+ ( Quotes :: Single , true )
290+ ) ;
291+
292+ // Always quote flag works in escape mode
293+ assert_eq ! (
294+ initial_quoting( b"normal" , false , true , true ) ,
295+ ( Quotes :: Single , true )
296+ ) ;
297+
298+ // Normal text doesn't need quoting in escape mode
299+ assert_eq ! (
300+ initial_quoting( b"hello" , false , false , true ) ,
301+ ( Quotes :: Single , false )
302+ ) ;
303+
304+ // Dirname affects colon handling in escape mode
305+ assert_eq ! (
306+ initial_quoting( b"dir:name" , true , false , true ) ,
307+ ( Quotes :: Single , true )
308+ ) ;
309+ assert_eq ! (
310+ initial_quoting( b"file:name" , false , false , true ) ,
311+ ( Quotes :: Single , false )
312+ ) ;
313+
314+ // Control chars ignored in non-escape mode
315+ assert_eq ! (
316+ initial_quoting( b"\x01 " , false , false , false ) ,
317+ ( Quotes :: Single , false )
318+ ) ;
319+ }
320+ }
0 commit comments