ftp_client/
client.rs

1//! Implement the Client and most actual functionality
2//! for it.
3//!
4//! Most functions were implemented using the RFC959 as reference
5//! and may not work as expected with deviant server implementations.
6use crate::status_code::{StatusCode, StatusCodeKind};
7use derive_more::From;
8use log::warn;
9use native_tls::{TlsConnector, TlsStream};
10use std::io::prelude::*;
11use std::io::BufReader;
12use std::io::{Read, Write};
13use std::net::TcpStream;
14
15/// Represents a raw server response, with
16/// a status code and the message after it.
17///
18/// Note that usual FTP responses follow the format:
19/// STATUS_CODE: MESSAGE.
20#[derive(Debug, PartialEq)]
21pub struct ServerResponse {
22    message: String,
23    status_code: StatusCode,
24}
25
26impl ServerResponse {
27    /// Summarize an error message, when a response is not one of the expect
28    /// status codes provided.
29    pub fn summarize_error(&self, expected: Vec<StatusCodeKind>) -> String {
30        format!(
31            "Got {}: {}, expected {:?}",
32            self.status_code.code, self.message, expected
33        )
34    }
35}
36
37/// A FTP client can run in two main modes: active and passive.
38///
39/// The passive mode is the simpler to run, you simply ask the
40/// server for a host to connect and connect to it, this is your
41/// data connection (for more information about the FTP protocol, check
42/// RFC959).
43///
44/// The active mode is different, you must open a port on your machine
45/// and use that to connect to the server. This can be a problem if you
46/// have firewalls set on your machine/network, for the common user, the
47/// passive mode should work fine.
48///
49/// The ExtendedPassive mode listed is simply the passive mode with support for
50/// IPV6.
51#[derive(Debug, Clone, Copy)]
52pub enum ClientMode {
53    /// The passive mode, using the PASV command
54    Passive,
55    /// The extended passive mode, using the EPSV command
56    ExtendedPassive,
57    /// The active mode, not implemented yet
58    Active,
59}
60
61impl ServerResponse {
62    /// Parse a server response from the server text response.
63    pub fn parse(text: &str) -> Self {
64        let status_code = StatusCode::parse(text);
65        let message = text[3..].trim().to_string();
66
67        Self {
68            message,
69            status_code,
70        }
71    }
72
73    /// Returns whether the status code returned indicates failure.
74    pub fn is_failure_status(&self) -> bool {
75        self.status_code.is_failure()
76    }
77}
78
79#[derive(From)]
80enum ClientStream {
81    TcpStream(TcpStream),
82    TlsStream(TlsStream<TcpStream>),
83}
84
85impl ClientStream {
86    pub fn peer_addr(&self) -> Result<std::net::SocketAddr, std::io::Error> {
87        match self {
88            ClientStream::TcpStream(stream) => stream.peer_addr(),
89            ClientStream::TlsStream(stream) => stream.get_ref().peer_addr(),
90        }
91    }
92}
93
94impl Read for ClientStream {
95    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
96        match self {
97            ClientStream::TcpStream(stream) => stream.read(buf),
98            ClientStream::TlsStream(stream) => stream.read(buf),
99        }
100    }
101}
102
103impl Write for ClientStream {
104    fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
105        match self {
106            ClientStream::TcpStream(stream) => stream.write(buf),
107            ClientStream::TlsStream(stream) => stream.write(buf),
108        }
109    }
110
111    fn flush(&mut self) -> Result<(), std::io::Error> {
112        match self {
113            ClientStream::TcpStream(stream) => stream.flush(),
114            ClientStream::TlsStream(stream) => stream.flush(),
115        }
116    }
117}
118
119/// The Client is where most of the functionality is, it keeps
120/// a control connection open and opens up data connections as
121/// commands are issued. This struct is a very thin wrapper over
122/// the FTP protocol.
123pub struct Client {
124    stream: BufReader<ClientStream>,
125    buffer: String,
126    welcome_string: Option<String>,
127    mode: ClientMode,
128}
129
130impl Client {
131    /// Set the mode for the client.
132    pub fn set_mode(&mut self, mode: ClientMode) {
133        self.mode = mode
134    }
135
136    /// Connect to a new FTP server using plain text (no TLS).
137    pub fn connect(
138        hostname: &str,
139        user: &str,
140        password: &str,
141    ) -> Result<Self, crate::error::Error> {
142        Self::connect_with_port(hostname, 21, user, password)
143    }
144
145    /// Connect to a new FTP server using a secure connection (TLS).
146    pub fn connect_tls(
147        hostname: &str,
148        user: &str,
149        password: &str,
150    ) -> Result<Self, crate::error::Error> {
151        Self::connect_tls_with_port(hostname, 21, user, password)
152    }
153
154    /// Connect to a new FTP server using a secure connection (TLS) on a specific port.
155    pub fn connect_tls_with_port(
156        hostname: &str,
157        port: u32,
158        user: &str,
159        password: &str,
160    ) -> Result<Self, crate::error::Error> {
161        let connector = TlsConnector::new()?;
162
163        let host = format!("{}:{}", hostname, port);
164        let raw_stream = TcpStream::connect(&host)?;
165        let tls_stream = connector.connect(&host, raw_stream)?;
166        let stream: BufReader<ClientStream> = BufReader::new(tls_stream.into());
167
168        let buffer = String::new();
169        let mut client = Client {
170            stream,
171            buffer,
172            welcome_string: None,
173            mode: ClientMode::ExtendedPassive,
174        };
175        let response = client.parse_reply_expecting(vec![StatusCodeKind::ReadyForNewUser])?;
176        client.welcome_string = Some(response.message);
177        client.login(user, password)?;
178
179        Ok(client)
180    }
181
182    /// Connect to a new FTP server using plain text (no TLS) on a specific port.
183    pub fn connect_with_port(
184        hostname: &str,
185        port: u32,
186        user: &str,
187        password: &str,
188    ) -> Result<Self, crate::error::Error> {
189        let host = format!("{}:{}", hostname, port);
190        let raw_stream = TcpStream::connect(&host)?;
191        let stream = BufReader::new(raw_stream.into());
192
193        let buffer = String::new();
194        let mut client = Client {
195            stream,
196            buffer,
197            welcome_string: None,
198            mode: ClientMode::ExtendedPassive,
199        };
200        let response = client.parse_reply_expecting(vec![StatusCodeKind::ReadyForNewUser])?;
201        client.welcome_string = Some(response.message);
202        client.login(user, password)?;
203
204        Ok(client)
205    }
206
207    /// Get the welcome message sent by the server at the connection establishment.
208    pub fn get_welcome(&self) -> Option<&String> {
209        self.welcome_string.as_ref()
210    }
211
212    /// Login using the given user and password.
213    /// Note that many servers require a login with an anonymous user,
214    /// such as client.login("anonymous", "anonymous@mail.com").
215    pub fn login(&mut self, user: &str, password: &str) -> Result<(), crate::error::Error> {
216        self.write_unary_command_expecting("USER", user, vec![StatusCodeKind::PasswordRequired])?;
217        self.write_unary_command_expecting("PASS", password, vec![StatusCodeKind::UserLoggedIn])?;
218
219        Ok(())
220    }
221
222    /// Logout from the current user/password pair.
223    pub fn logout(&mut self) -> Result<(), crate::error::Error> {
224        self.write_command_expecting("QUIT", vec![StatusCodeKind::ClosingControlConnection])?;
225
226        Ok(())
227    }
228
229    /// Change the working directory on the current session.
230    pub fn cwd(&mut self, dir: &str) -> Result<(), crate::error::Error> {
231        self.write_unary_command_expecting(
232            "CWD",
233            dir,
234            vec![StatusCodeKind::RequestFileActionCompleted],
235        )?;
236
237        Ok(())
238    }
239
240    /// Go up to the parent directory on the current session.
241    pub fn cdup(&mut self) -> Result<(), crate::error::Error> {
242        self.write_command_expecting("CDUP", vec![StatusCodeKind::RequestFileActionCompleted])?;
243
244        Ok(())
245    }
246
247    /// Show server information regarding its implementation status
248    /// to the user.
249    ///
250    /// The help command can also be used with an argument to see detailed
251    /// information about a single command, this behaviour is not implemented.
252    pub fn help(&mut self) -> Result<(), crate::error::Error> {
253        self.write_command_expecting(
254            "HELP",
255            vec![StatusCodeKind::SystemStatus, StatusCodeKind::HelpMessage],
256        )?;
257        Ok(())
258    }
259
260    /// This command should not do anything other than receiving
261    /// an OK response from the server.
262    pub fn noop(&mut self) -> Result<(), crate::error::Error> {
263        self.write_command_expecting("NOOP", vec![StatusCodeKind::Ok])?;
264        Ok(())
265    }
266
267    /// Set the transfer type to ascii
268    pub fn ascii(&mut self) -> Result<(), crate::error::Error> {
269        self.write_unary_command_expecting("TYPE", "A", vec![StatusCodeKind::Ok])?;
270        Ok(())
271    }
272
273    /// Set the transfer type to binary
274    pub fn binary(&mut self) -> Result<(), crate::error::Error> {
275        self.write_unary_command_expecting("TYPE", "I", vec![StatusCodeKind::Ok])?;
276        Ok(())
277    }
278
279    /// Get the current reported status from the server. This can be used
280    /// during transfer and between them. This command can be used with
281    /// and argument to get behaviour similar to LIST, this particular
282    /// behaviour is not implemented.
283    pub fn status(&mut self) -> Result<String, crate::error::Error> {
284        let response = self.write_command_expecting("STAT", vec![StatusCodeKind::SystemStatus])?;
285
286        Ok(response.message)
287    }
288
289    /// List the provided path in any way the server desires.
290    pub fn list(&mut self, path: &str) -> Result<String, crate::error::Error> {
291        let mut conn = self.get_data_connection()?;
292        self.write_unary_command_expecting(
293            "LIST",
294            path,
295            vec![
296                StatusCodeKind::TransferStarted,
297                StatusCodeKind::TransferAboutToStart,
298            ],
299        )?;
300
301        let mut buffer = Vec::with_capacity(1024);
302        conn.read_to_end(&mut buffer)?;
303        self.parse_reply_expecting(vec![StatusCodeKind::RequestActionCompleted])?;
304        let text = String::from_utf8(buffer).map_err(|_| {
305            crate::error::Error::SerializationFailed(
306                "Invalid ASCII returned on server directory listing.".to_string(),
307            )
308        })?;
309        Ok(text)
310    }
311
312    /// List the provided path, providing only name information about files and directories.
313    pub fn list_names(&mut self, path: &str) -> Result<Vec<String>, crate::error::Error> {
314        let mut conn = self.get_data_connection()?;
315        self.write_unary_command_expecting(
316            "NLST",
317            path,
318            vec![
319                StatusCodeKind::TransferStarted,
320                StatusCodeKind::TransferAboutToStart,
321            ],
322        )?;
323
324        let mut buffer = Vec::with_capacity(1024);
325        conn.read_to_end(&mut buffer)?;
326        self.parse_reply_expecting(vec![StatusCodeKind::RequestActionCompleted])?;
327        let text = String::from_utf8(buffer).map_err(|_| {
328            crate::error::Error::SerializationFailed(
329                "Invalid ASCII returned on server directory name listing.".to_string(),
330            )
331        })?;
332        Ok(text.lines().map(|line| line.to_owned()).collect())
333    }
334
335    /// Store a new file on a provided path and name.
336    pub fn store<B: AsRef<[u8]>>(
337        &mut self,
338        path: &str,
339        data: B,
340    ) -> Result<(), crate::error::Error> {
341        // Scope connection so it drops before reading server reply.
342        {
343            let mut conn = self.get_data_connection()?;
344            self.write_unary_command_expecting(
345                "STOR",
346                path,
347                vec![
348                    StatusCodeKind::TransferStarted,
349                    StatusCodeKind::TransferAboutToStart,
350                ],
351            )?;
352            conn.get_mut().write_all(data.as_ref())?;
353        }
354
355        self.parse_reply_expecting(vec![StatusCodeKind::RequestActionCompleted])?;
356
357        Ok(())
358    }
359
360    /// Store a new file on a provided path using a random unique name.
361    pub fn store_unique<B: AsRef<[u8]>>(&mut self, data: B) -> Result<String, crate::error::Error> {
362        // Scope connection so it drops before reading server reply.
363        {
364            let mut conn = self.get_data_connection()?;
365            self.write_command_expecting(
366                "STOU",
367                vec![
368                    StatusCodeKind::TransferStarted,
369                    StatusCodeKind::TransferAboutToStart,
370                ],
371            )?;
372            conn.get_mut().write_all(data.as_ref())?;
373        }
374
375        let reply = self.parse_reply_expecting(vec![StatusCodeKind::RequestActionCompleted])?;
376
377        Ok(reply.message)
378    }
379
380    /// Append to a existing file or a create a new one.
381    pub fn append<B: AsRef<[u8]>>(
382        &mut self,
383        path: &str,
384        data: B,
385    ) -> Result<(), crate::error::Error> {
386        // Scope connection so it drops before reading server reply.
387        {
388            let mut conn = self.get_data_connection()?;
389            self.write_unary_command_expecting(
390                "APPE",
391                path,
392                vec![
393                    StatusCodeKind::TransferStarted,
394                    StatusCodeKind::TransferAboutToStart,
395                ],
396            )?;
397            conn.get_mut().write_all(data.as_ref())?;
398        }
399
400        self.parse_reply_expecting(vec![
401            StatusCodeKind::RequestActionCompleted,
402            StatusCodeKind::RequestFileActionCompleted,
403        ])?;
404
405        Ok(())
406    }
407
408    /// Restart a file transfer. Unimplemented.
409    pub fn restart(&mut self) -> Result<(), crate::error::Error> {
410        unimplemented!();
411    }
412
413    /// Abort a file transfer. Unimplemented.
414    pub fn abort(&mut self) -> Result<(), crate::error::Error> {
415        unimplemented!();
416    }
417
418    /// Preallocate space on the server. Unimplemented.
419    pub fn allocate(
420        &mut self,
421        _logical_size: usize,
422        _logical_page_size: Option<usize>,
423    ) -> Result<(), crate::error::Error> {
424        unimplemented!();
425    }
426
427    /// Move a file from a path to another, essentially renaming it.
428    pub fn rename_file(
429        &mut self,
430        path_from: &str,
431        path_to: &str,
432    ) -> Result<(), crate::error::Error> {
433        self.write_unary_command_expecting(
434            "RNFR",
435            path_from,
436            vec![StatusCodeKind::RequestActionPending],
437        )?;
438        self.write_unary_command_expecting(
439            "RNTO",
440            path_to,
441            vec![StatusCodeKind::RequestFileActionCompleted],
442        )?;
443
444        Ok(())
445    }
446
447    /// Remove an existing directory.
448    pub fn remove_directory(&mut self, dir_path: &str) -> Result<(), crate::error::Error> {
449        self.write_unary_command_expecting(
450            "RMD",
451            dir_path,
452            vec![StatusCodeKind::RequestFileActionCompleted],
453        )?;
454        Ok(())
455    }
456
457    /// Make a new directory.
458    pub fn make_directory(&mut self, dir_path: &str) -> Result<(), crate::error::Error> {
459        self.write_unary_command_expecting("MKD", dir_path, vec![StatusCodeKind::PathCreated])?;
460        Ok(())
461    }
462
463    /// Get the current working directory.
464    pub fn pwd(&mut self) -> Result<String, crate::error::Error> {
465        let response = self.write_command_expecting("PWD", vec![StatusCodeKind::PathCreated])?;
466        Ok(response.message)
467    }
468
469    /// This command is used by the server to provide services
470    /// specific to his system that are essential to file transfer
471    /// but not sufficiently universal to be included as commands in
472    /// the protocol.
473    ///
474    /// The nature of these services and the
475    /// specification of their syntax can be stated in a reply to
476    /// the HELP SITE command.
477    ///
478    /// Extracted from RFC959.
479    pub fn site_parameters(&mut self) -> Result<String, crate::error::Error> {
480        let response = self.write_command_expecting(
481            "SITE",
482            vec![StatusCodeKind::Ok, StatusCodeKind::FeatureNotImplemented],
483        )?;
484
485        Ok(response.message)
486    }
487
488    /// Get the type of operating system on the server.
489    pub fn system(&mut self) -> Result<String, crate::error::Error> {
490        let response =
491            self.write_command_expecting("SYST", vec![StatusCodeKind::NameSystemType])?;
492
493        Ok(response.message)
494    }
495
496    /// Delete a file at a path.
497    pub fn delete_file(&mut self, dir_path: &str) -> Result<(), crate::error::Error> {
498        self.write_unary_command_expecting(
499            "DELE",
500            dir_path,
501            vec![StatusCodeKind::RequestFileActionCompleted],
502        )?;
503
504        Ok(())
505    }
506
507    /// Download a file at a path into a byte buffer.
508    pub fn retrieve_file(&mut self, path: &str) -> Result<Vec<u8>, crate::error::Error> {
509        let mut conn = self.get_data_connection()?;
510        self.write_unary_command_expecting(
511            "RETR",
512            path,
513            vec![
514                StatusCodeKind::TransferAboutToStart,
515                StatusCodeKind::TransferStarted,
516            ],
517        )?;
518
519        let mut buffer = Vec::with_capacity(1024);
520        conn.read_to_end(&mut buffer)?;
521        self.parse_reply_expecting(vec![StatusCodeKind::RequestActionCompleted])?;
522        Ok(buffer)
523    }
524
525    /// Acquire the data connection using the current ClientMode.
526    pub fn get_data_connection(&mut self) -> Result<BufReader<TcpStream>, crate::error::Error> {
527        match self.mode {
528            ClientMode::Active => unimplemented!(),
529            ClientMode::Passive => self.passive_mode_connection(),
530            ClientMode::ExtendedPassive => self.extended_passive_mode_connection(),
531        }
532    }
533
534    /// Create a extended passive mode connection.
535    pub fn extended_passive_mode_connection(
536        &mut self,
537    ) -> Result<BufReader<TcpStream>, crate::error::Error> {
538        let response =
539            self.write_command_expecting("EPSV", vec![StatusCodeKind::EnteredExtendedPassiveMode])?;
540        let socket = self.decode_extended_passive_mode_socket(&response.message)?;
541
542        Ok(BufReader::new(TcpStream::connect(socket)?))
543    }
544
545    /// Create a passive mode connection.
546    pub fn passive_mode_connection(&mut self) -> Result<BufReader<TcpStream>, crate::error::Error> {
547        let response =
548            self.write_command_expecting("PASV", vec![StatusCodeKind::EnteredPassiveMode])?;
549        let socket = self.decode_passive_mode_ip(&response.message)?;
550
551        Ok(BufReader::new(TcpStream::connect(socket)?))
552    }
553
554    /// Write a command with one argument to the server expecting a list of positive status codes.
555    pub fn write_unary_command_expecting(
556        &mut self,
557        cmd: &str,
558        arg: &str,
559        valid_statuses: Vec<StatusCodeKind>,
560    ) -> Result<ServerResponse, crate::error::Error> {
561        self.write_unary_command(cmd, arg)?;
562        self.parse_reply_expecting(valid_statuses)
563    }
564
565    /// Write a command with one argument to the server.
566    pub fn write_unary_command(&mut self, cmd: &str, arg: &str) -> Result<(), crate::error::Error> {
567        let text = format!("{} {}\r\n", cmd, arg);
568        self.stream.get_mut().write_all(text.as_bytes())?;
569
570        Ok(())
571    }
572
573    /// Write a command to the server expecting a list of positive status codes.
574    pub fn write_command_expecting(
575        &mut self,
576        cmd: &str,
577        valid_statuses: Vec<StatusCodeKind>,
578    ) -> Result<ServerResponse, crate::error::Error> {
579        self.write_command(cmd)?;
580        self.parse_reply_expecting(valid_statuses)
581    }
582
583    /// Write a command to the server.
584    pub fn write_command(&mut self, cmd: &str) -> Result<(), crate::error::Error> {
585        let text = format!("{}\r\n", cmd);
586        self.stream.get_mut().write_all(text.as_bytes())?;
587
588        Ok(())
589    }
590
591    /// Parse the server reply into a ServerResponse expecting a list of status codes.
592    pub fn parse_reply_expecting(
593        &mut self,
594        valid_statuses: Vec<StatusCodeKind>,
595    ) -> Result<ServerResponse, crate::error::Error> {
596        let response = self.parse_reply()?;
597
598        let is_expected_status = valid_statuses.contains(&response.status_code.kind);
599        // We are a bit liberal on what we accept.
600        let is_positive_status = response.status_code.is_valid();
601        warn!(
602            "Unexpected positive status was accepted: {:?}",
603            response.status_code
604        );
605
606        if is_expected_status || is_positive_status {
607            Ok(response)
608        } else {
609            Err(crate::error::Error::UnexpectedStatusCode(
610                response.summarize_error(valid_statuses),
611            ))
612        }
613    }
614
615    /// Parse the server reply into a ServerResponse.
616    pub fn parse_reply(&mut self) -> Result<ServerResponse, crate::error::Error> {
617        self.buffer.clear();
618        self.stream.read_line(&mut self.buffer)?;
619        Ok(ServerResponse::parse(&self.buffer))
620    }
621
622    /// Read the server reply as a raw string.
623    pub fn read_reply(&mut self) -> Result<String, crate::error::Error> {
624        self.buffer.clear();
625        self.stream.read_line(&mut self.buffer)?;
626        Ok(self.buffer.clone())
627    }
628
629    fn decode_passive_mode_ip(
630        &self,
631        message: &str,
632    ) -> Result<std::net::SocketAddrV4, crate::error::Error> {
633        let first_bracket = message.find('(');
634        let second_bracket = message.find(')');
635        let cant_parse_error = || {
636            crate::error::Error::InvalidSocketPassiveMode(format!(
637                "Cannot parse socket sent from server for passive mode: {}.",
638                message
639            ))
640        };
641
642        match (first_bracket, second_bracket) {
643            (Some(start), Some(end)) => {
644                // We are dealing with ASCII strings only on this point, so +1 is okay.
645                let nums: Vec<u8> = message[start + 1..end]
646                    .split(',')
647                    // Try to parse all digits between ','
648                    .flat_map(|val| val.parse())
649                    .collect();
650                if nums.len() < 4 {
651                    Err(cant_parse_error())
652                } else {
653                    let ip = std::net::Ipv4Addr::new(nums[0], nums[1], nums[2], nums[3]);
654
655                    Ok(std::net::SocketAddrV4::new(
656                        ip,
657                        256 * nums[4] as u16 + nums[5] as u16,
658                    ))
659                }
660            }
661            _ => Err(cant_parse_error()),
662        }
663    }
664
665    fn decode_extended_passive_mode_socket(
666        &self,
667        response: &str,
668    ) -> Result<std::net::SocketAddr, crate::error::Error> {
669        let first_delimiter = response.find("|||");
670        let second_delimiter = response.rfind('|');
671        let cant_parse_error = || {
672            crate::error::Error::InvalidSocketPassiveMode(format!(
673                "Cannot parse socket sent from server for passive mode: {}.",
674                response
675            ))
676        };
677
678        match (first_delimiter, second_delimiter) {
679            (Some(start), Some(end)) => {
680                let port: u16 = response[start + 3..end]
681                    .parse()
682                    .map_err(move |_| cant_parse_error())?;
683                let ip = self
684                    .stream
685                    .get_ref()
686                    .peer_addr()
687                    .map_err(move |_| cant_parse_error())?
688                    .ip();
689                Ok(std::net::SocketAddr::new(ip, port))
690            }
691            _ => Err(cant_parse_error()),
692        }
693    }
694}