@@ -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,17 @@ 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+ let has_special_chars = input. iter ( ) . any ( |c| {
195+ shell_escaped_char_set ( dirname) . contains ( c) || ( check_control_chars && c. is_ascii_control ( ) )
196+ } ) ;
197+
198+ if has_special_chars {
193199 ( Quotes :: Single , true )
194200 } else if input. contains ( & b'\'' ) {
195201 ( Quotes :: Double , true )
@@ -239,3 +245,73 @@ fn finalize_shell_quoter(
239245 buffer
240246 }
241247}
248+
249+ #[ cfg( test) ]
250+ mod tests {
251+ use super :: * ;
252+
253+ #[ test]
254+ fn test_initial_quoting ( ) {
255+ // Control chars (0-31 and 0x7F) force single quotes in escape mode
256+ assert_eq ! (
257+ initial_quoting( b"\x01 " , false , false , true ) ,
258+ ( Quotes :: Single , true )
259+ ) ;
260+
261+ // Control + quote uses single quotes (segmented) in escape mode
262+ assert_eq ! (
263+ initial_quoting( b"\x01 '\x01 " , false , false , true ) ,
264+ ( Quotes :: Single , true )
265+ ) ;
266+
267+ // Simple quote uses double quotes in escape mode
268+ assert_eq ! (
269+ initial_quoting( b"a'b" , false , false , true ) ,
270+ ( Quotes :: Double , true )
271+ ) ;
272+
273+ // Shell special chars force single quotes in escape mode
274+ assert_eq ! (
275+ initial_quoting( b"test$var" , false , false , true ) ,
276+ ( Quotes :: Single , true )
277+ ) ;
278+ assert_eq ! (
279+ initial_quoting( b"test\n line" , false , false , true ) ,
280+ ( Quotes :: Single , true )
281+ ) ;
282+
283+ // Empty string forces quotes in escape mode
284+ assert_eq ! (
285+ initial_quoting( b"" , false , false , true ) ,
286+ ( Quotes :: Single , true )
287+ ) ;
288+
289+ // Always quote flag works in escape mode
290+ assert_eq ! (
291+ initial_quoting( b"normal" , false , true , true ) ,
292+ ( Quotes :: Single , true )
293+ ) ;
294+
295+ // Normal text doesn't need quoting in escape mode
296+ assert_eq ! (
297+ initial_quoting( b"hello" , false , false , true ) ,
298+ ( Quotes :: Single , false )
299+ ) ;
300+
301+ // Dirname affects colon handling in escape mode
302+ assert_eq ! (
303+ initial_quoting( b"dir:name" , true , false , true ) ,
304+ ( Quotes :: Single , true )
305+ ) ;
306+ assert_eq ! (
307+ initial_quoting( b"file:name" , false , false , true ) ,
308+ ( Quotes :: Single , false )
309+ ) ;
310+
311+ // Control chars ignored in non-escape mode
312+ assert_eq ! (
313+ initial_quoting( b"\x01 " , false , false , false ) ,
314+ ( Quotes :: Single , false )
315+ ) ;
316+ }
317+ }
0 commit comments