@@ -9,7 +9,8 @@ use chrono::{DateTime, Utc};
9
9
use regex:: Regex ;
10
10
11
11
use tokio:: io:: {
12
- copy, AsyncBufReadExt , AsyncRead , AsyncReadExt , AsyncWriteExt , BufReader , BufWriter ,
12
+ copy, AsyncBufRead , AsyncBufReadExt , AsyncRead , AsyncReadExt , AsyncWriteExt , BufReader ,
13
+ BufWriter ,
13
14
} ;
14
15
use tokio:: net:: { TcpStream , ToSocketAddrs } ;
15
16
@@ -435,28 +436,38 @@ impl FtpStream {
435
436
open_code : u32 ,
436
437
close_code : & [ u32 ] ,
437
438
) -> Result < Vec < String > > {
438
- let mut lines: Vec < String > = Vec :: new ( ) ;
439
-
440
- let mut data_stream = BufReader :: new ( self . data_command ( & cmd) . await ?) ;
439
+ let data_stream = BufReader :: new ( self . data_command ( & cmd) . await ?) ;
441
440
self . read_response_in ( & [ open_code, status:: ALREADY_OPEN ] )
442
441
. await ?;
442
+ let lines = Self :: get_lines_from_stream ( data_stream) . await ?;
443
+ self . read_response_in ( close_code) . await ?;
444
+ Ok ( lines)
445
+ }
443
446
444
- let mut line = String :: new ( ) ;
447
+ /// Consume a stream and return a vector of lines
448
+ async fn get_lines_from_stream < R > ( data_stream : R ) -> Result < Vec < String > >
449
+ where
450
+ R : AsyncBufRead + Unpin ,
451
+ {
452
+ let mut lines: Vec < String > = Vec :: new ( ) ;
453
+
454
+ let mut lines_stream = data_stream. lines ( ) ;
445
455
loop {
446
- match data_stream. read_to_string ( & mut line) . await {
447
- Ok ( 0 ) => break ,
448
- Ok ( _) => lines. extend (
449
- line. split ( "\r \n " )
450
- . map ( String :: from)
451
- . filter ( |s| !s. is_empty ( ) ) ,
452
- ) ,
453
- Err ( err) => return Err ( FtpError :: ConnectionError ( err) ) ,
454
- } ;
455
- }
456
- drop ( data_stream) ;
456
+ let line = lines_stream
457
+ . next_line ( )
458
+ . await
459
+ . map_err ( FtpError :: ConnectionError ) ?;
457
460
458
- self . read_response_in ( close_code) . await ?;
459
- Ok ( lines)
461
+ match line {
462
+ Some ( line) => {
463
+ if line. is_empty ( ) {
464
+ continue ;
465
+ }
466
+ lines. push ( line) ;
467
+ }
468
+ None => break Ok ( lines) ,
469
+ }
470
+ }
460
471
}
461
472
462
473
/// Execute `LIST` command which returns the detailed file listing in human readable format.
@@ -597,3 +608,37 @@ impl FtpStream {
597
608
}
598
609
}
599
610
}
611
+
612
+ #[ cfg( test) ]
613
+ mod tests {
614
+ use super :: FtpStream ;
615
+ use tokio:: { io:: stream_reader, stream} ;
616
+
617
+ #[ tokio:: test]
618
+ async fn list_command_dos_newlines ( ) {
619
+ let data_stream = stream_reader ( stream:: once ( Ok (
620
+ b"Hello\r \n World\r \n \r \n Be\r \n Happy\r \n " as & [ u8 ]
621
+ ) ) ) ;
622
+
623
+ assert_eq ! (
624
+ FtpStream :: get_lines_from_stream( data_stream) . await . unwrap( ) ,
625
+ [ "Hello" , "World" , "Be" , "Happy" ]
626
+ . iter( )
627
+ . map( <& str >:: to_string)
628
+ . collect:: <Vec <_>>( )
629
+ ) ;
630
+ }
631
+
632
+ #[ tokio:: test]
633
+ async fn list_command_unix_newlines ( ) {
634
+ let data_stream = stream_reader ( stream:: once ( Ok ( b"Hello\n World\n \n Be\n Happy\n " as & [ u8 ] ) ) ) ;
635
+
636
+ assert_eq ! (
637
+ FtpStream :: get_lines_from_stream( data_stream) . await . unwrap( ) ,
638
+ [ "Hello" , "World" , "Be" , "Happy" ]
639
+ . iter( )
640
+ . map( <& str >:: to_string)
641
+ . collect:: <Vec <_>>( )
642
+ ) ;
643
+ }
644
+ }
0 commit comments