diff --git a/Cargo.toml b/Cargo.toml index ca96ca90..ede9bc51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,19 +8,17 @@ description = "FTP client for Rust" readme = "README.md" license = "Apache-2.0/MIT" keywords = ["ftp"] +edition = "2021" categories = ["network-programming"] [badges] travis-ci = { repository = "mattnenterprise/rust-ftp" } [lib] -name ="ftp" +name = "ftp" path = "src/lib.rs" [features] -# Enable support of FTPS which requires openssl -secure = ["openssl"] - # Add debug output (to STDOUT) of commands sent to the server # and lines read from the server debug_print = [] @@ -28,12 +26,11 @@ debug_print = [] [dependencies] lazy_static = "1" regex = "1" -chrono = "0.4" openssl = { version = "0.10", optional = true } - -[dependencies.native-tls] -version = "0.2" -optional = true +native-tls = { version = "0.2", optional = true } [package.metadata.docs.rs] -rustc-args = ["--cfg", "secure"] +rustc-args = ["--cfg", "openssl"] + +[dev-dependencies] +chrono = "0.4" diff --git a/README.md b/README.md index b37f8b4e..873c0d72 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,14 @@ FTPS support is achieved through [rust-native-tls](https://github.com/sfackler/r ```toml [dependencies] -ftp = { version = "", features = ["secure"] } +ftp = { version = "", features = ["openssl"] } +``` + +rust-ftp also supports native-tls + +```toml +[dependencies] +ftp = { version = "", features = ["native-tls"] } ``` ## Usage diff --git a/src/data_stream.rs b/src/data_stream.rs index 431b1276..f1a47085 100644 --- a/src/data_stream.rs +++ b/src/data_stream.rs @@ -1,6 +1,6 @@ -#[cfg(all(feature = "secure", feature = "native-tls"))] +#[cfg(feature = "native-tls")] use native_tls::TlsStream; -#[cfg(all(feature = "secure", not(feature = "native-tls")))] +#[cfg(feature = "openssl")] use openssl::ssl::SslStream; use std::{ @@ -12,13 +12,13 @@ use std::{ #[derive(Debug)] pub enum DataStream { Tcp(TcpStream), - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] Ssl(SslStream), - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] Ssl(TlsStream), } -#[cfg(feature = "secure")] +#[cfg(any(feature = "openssl", feature = "native-tls"))] impl DataStream { /// Unwrap the stream into TcpStream. This method is only used in secure connection. pub fn into_tcp_stream(self) -> TcpStream { @@ -42,7 +42,7 @@ impl DataStream { pub fn get_ref(&self) -> &TcpStream { match *self { DataStream::Tcp(ref stream) => stream, - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] DataStream::Ssl(ref stream) => stream.get_ref(), } } @@ -52,7 +52,7 @@ impl Read for DataStream { fn read(&mut self, buf: &mut [u8]) -> Result { match *self { DataStream::Tcp(ref mut stream) => stream.read(buf), - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] DataStream::Ssl(ref mut stream) => stream.read(buf), } } @@ -62,7 +62,7 @@ impl Write for DataStream { fn write(&mut self, buf: &[u8]) -> Result { match *self { DataStream::Tcp(ref mut stream) => stream.write(buf), - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] DataStream::Ssl(ref mut stream) => stream.write(buf), } } @@ -70,7 +70,7 @@ impl Write for DataStream { fn flush(&mut self) -> Result<()> { match *self { DataStream::Tcp(ref mut stream) => stream.flush(), - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] DataStream::Ssl(ref mut stream) => stream.flush(), } } diff --git a/src/ftp.rs b/src/ftp.rs index 38526189..9d76aab4 100644 --- a/src/ftp.rs +++ b/src/ftp.rs @@ -7,7 +7,6 @@ use super::{ }; use { - chrono::{offset::TimeZone, DateTime, Utc}, regex::Regex, std::{ borrow::Cow, @@ -17,9 +16,9 @@ use { }, }; -#[cfg(all(feature = "secure", feature = "native-tls"))] +#[cfg(feature = "native-tls")] use native_tls::TlsConnector; -#[cfg(all(feature = "secure", not(feature = "native-tls")))] +#[cfg(feature = "openssl")] use openssl::ssl::{Ssl, SslContext}; lazy_static! { @@ -39,17 +38,51 @@ lazy_static! { pub struct FtpStream { reader: BufReader, welcome_msg: Option, - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] tls_ctx: Option, - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] domain: Option, - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] ssl_cfg: Option, } +pub struct DateTime { + pub year: u32, + pub month: u32, + pub day: u32, + pub hour: u32, + pub minute: u32, + pub second: u32, +} +impl DateTime { + pub fn timestamp(&self) -> u32 { + let days_asof_m = [31_u16, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; + let yyear = self.year - 1; + let countleap = ((yyear / 4) - (yyear / 100) + (yyear / 400)) + - ((1970 / 4) - (1970 / 100) + (1970 / 400)); + + let m = if self.month > 1 { + let days_per_month = ((self.year % 4 == 0 + && ((self.year % 100 != 0) || self.year % 400 == 0)) + && self.month > 2 + || self.month == 2 && self.day >= 29) as u16; + (days_asof_m[(self.month - 2) as usize] + days_per_month) as u32 * 86400 + } else { + 0 + }; + (self.year - 1970) * 365 * 86400 + + (countleap * 86400) + + self.second + + (self.hour * 3600) + + (self.minute * 60) + + ((self.day - 1) * 86400) + + m + } +} + impl FtpStream { /// Creates an FTP Stream and returns the welcome message - #[cfg(not(feature = "secure"))] + #[cfg(not(any(feature = "openssl", feature = "native-tls")))] pub fn connect(addr: A) -> crate::Result { TcpStream::connect(addr) .map_err(FtpError::ConnectionError) @@ -70,7 +103,7 @@ impl FtpStream { } /// Creates an FTP Stream and returns the welcome message - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] pub fn connect(addr: A) -> crate::Result { TcpStream::connect(addr) .map_err(FtpError::ConnectionError) @@ -93,7 +126,7 @@ impl FtpStream { } /// Creates an FTP Stream and returns the welcome message - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] pub fn connect(addr: A) -> crate::Result { TcpStream::connect(addr) .map_err(FtpError::ConnectionError) @@ -134,7 +167,7 @@ impl FtpStream { /// let mut (ftp_stream, _welcome_msg) = FtpStream::connect("127.0.0.1:21").unwrap(); /// let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost").unwrap(); /// ``` - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] pub fn into_secure( mut self, tls_connector: TlsConnector, @@ -150,6 +183,7 @@ impl FtpStream { )), tls_ctx: Some(tls_connector), domain: Some(String::from(domain)), + welcome_msg: self.welcome_msg, }; // Set protection buffer size secured_ftp_tream.write_str("PBSZ 0\r\n")?; @@ -182,7 +216,7 @@ impl FtpStream { /// // Do all public things /// let _ = ftp_stream.quit(); /// ``` - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] pub fn into_insecure(mut self) -> crate::Result { // Ask the server to stop securing data self.write_str("CCC\r\n")?; @@ -191,6 +225,7 @@ impl FtpStream { reader: BufReader::new(DataStream::Tcp(self.reader.into_inner().into_tcp_stream())), tls_ctx: None, domain: None, + welcome_msg: self.welcome_msg, }; Ok(plain_ftp_stream) } @@ -216,7 +251,7 @@ impl FtpStream { /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap(); /// let mut ftp_stream = ftp_stream.into_secure(ctx).unwrap(); /// ``` - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] pub fn into_secure(mut self, ssl_context: SslContext) -> crate::Result { // Ask the server to start securing data. self.write_str("AUTH TLS\r\n")?; @@ -263,7 +298,7 @@ impl FtpStream { /// // Do all public things /// let _ = ftp_stream.quit(); /// ``` - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] pub fn into_insecure(mut self) -> crate::Result { // Ask the server to stop securing data self.write_str("CCC\r\n")?; @@ -279,7 +314,7 @@ impl FtpStream { } /// Execute command which send data back in a separate stream - #[cfg(not(feature = "secure"))] + #[cfg(not(any(feature = "openssl", feature = "native-tls")))] fn data_command(&mut self, cmd: &str) -> crate::Result { let addr = self.pasv()?; self.write_str(cmd)?; @@ -287,7 +322,7 @@ impl FtpStream { } /// Execute command which send data back in a separate stream - #[cfg(all(feature = "secure", feature = "native-tls"))] + #[cfg(feature = "native-tls")] fn data_command(&mut self, cmd: &str) -> crate::Result { let addr = self.pasv()?; self.write_str(cmd)?; @@ -302,7 +337,7 @@ impl FtpStream { } /// Execute command which send data back in a separate stream - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] fn data_command(&mut self, cmd: &str) -> crate::Result { let addr = self.pasv()?; self.write_str(cmd)?; @@ -541,7 +576,7 @@ impl FtpStream { let mut data_stream = BufWriter::new(self.data_command(&stor_command)?); self.read_response_in(&[status::ALREADY_OPEN, status::ABOUT_TO_SEND])?; copy(r, &mut data_stream)?; - #[cfg(all(feature = "secure", not(feature = "native-tls")))] + #[cfg(feature = "openssl")] { if let DataStream::Ssl(mut ssl_stream) = data_stream.into_inner().map_err(std::io::Error::from)? @@ -639,14 +674,14 @@ impl FtpStream { /// Retrieves the modification time of the file at `pathname` if it exists. /// In case the file does not exist `None` is returned. - pub fn mdtm(&mut self, pathname: &str) -> crate::Result>> { + pub fn mdtm(&mut self, pathname: &str) -> crate::Result> { self.write_str(format!("MDTM {}\r\n", pathname))?; let Line(_, content) = self.read_response(status::FILE)?; match MDTM_RE.captures(&content) { Some(caps) => { let (year, month, day) = ( - caps[1].parse::().unwrap(), + caps[1].parse::().unwrap(), caps[2].parse::().unwrap(), caps[3].parse::().unwrap(), ); @@ -655,9 +690,14 @@ impl FtpStream { caps[5].parse::().unwrap(), caps[6].parse::().unwrap(), ); - Ok(Some( - Utc.ymd(year, month, day).and_hms(hour, minute, second), - )) + Ok(Some(DateTime { + year, + month, + day, + hour, + minute, + second, + })) } None => Ok(None), } diff --git a/src/lib.rs b/src/lib.rs index edc54cc3..130bb19a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ //! before authentication. //! #![cfg_attr( - all(feature = "secure", not(feature = "native-tls")), + feature = "openssl", doc = r##" ## FTPS Usage @@ -51,7 +51,7 @@ let _ = ftp_stream.quit(); "## )] #![cfg_attr( - all(feature = "secure", feature = "native-tls"), + feature = "native-ssl", doc = r##" ## FTPS Usage @@ -74,12 +74,11 @@ let _ = ftp_stream.quit(); )] #[macro_use] extern crate lazy_static; -extern crate chrono; extern crate regex; -#[cfg(all(feature = "secure", feature = "native-tls"))] +#[cfg(feature = "native-tls")] pub extern crate native_tls; -#[cfg(all(feature = "secure", not(feature = "native-tls")))] +#[cfg(feature = "openssl")] pub extern crate openssl; mod data_stream; @@ -87,7 +86,7 @@ mod ftp; pub mod status; pub mod types; -pub use self::ftp::FtpStream; +pub use self::ftp::{DateTime, FtpStream}; pub use self::types::FtpError; /// A shorthand for a Result whose error type is always an FtpError. diff --git a/src/types.rs b/src/types.rs index a12df6a7..4af4661c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -11,7 +11,7 @@ pub type Result = std::result::Result; #[derive(Debug)] pub enum FtpError { ConnectionError(std::io::Error), - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] SecureError(String), InvalidResponse(String), InvalidAddress(std::net::AddrParseError), @@ -23,25 +23,25 @@ impl From for FtpError { } } -#[cfg(all(feature = "secure", feature = "native-tls"))] +#[cfg(feature = "native-tls")] impl From> for FtpError { fn from(err: native_tls::HandshakeError) -> Self { FtpError::SecureError(err.to_string()) } } -#[cfg(all(feature = "secure", not(feature = "native-tls")))] +#[cfg(feature = "openssl")] impl From for FtpError { fn from(err: openssl::error::ErrorStack) -> Self { FtpError::SecureError(err.to_string()) } } -#[cfg(all(feature = "secure", not(feature = "native-tls")))] +#[cfg(feature = "openssl")] impl From for FtpError { fn from(err: openssl::ssl::Error) -> Self { FtpError::SecureError(err.to_string()) } } -#[cfg(all(feature = "secure", not(feature = "native-tls")))] +#[cfg(feature = "openssl")] impl From> for FtpError { fn from(err: openssl::ssl::HandshakeError) -> Self { FtpError::SecureError(err.to_string()) @@ -116,7 +116,7 @@ impl fmt::Display for FtpError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { FtpError::ConnectionError(ref ioerr) => write!(f, "FTP ConnectionError: {}", ioerr), - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] FtpError::SecureError(ref desc) => write!(f, "FTP SecureError: {}", desc), FtpError::InvalidResponse(ref desc) => { write!(f, "FTP InvalidResponse: {}", desc) @@ -130,7 +130,7 @@ impl std::error::Error for FtpError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match *self { FtpError::ConnectionError(ref ioerr) => Some(ioerr), - #[cfg(feature = "secure")] + #[cfg(any(feature = "openssl", feature = "native-tls"))] FtpError::SecureError(_) => None, FtpError::InvalidResponse(_) => None, FtpError::InvalidAddress(ref aperr) => Some(aperr), @@ -140,8 +140,33 @@ impl std::error::Error for FtpError { #[cfg(test)] mod tests { - use super::*; + use crate::DateTime; + use chrono::TimeZone; + + #[test] + fn test_datetime() { + let year: u32 = 2024; + let month: u32 = 3; + let day: u32 = 28; + let hour: u32 = 13; + let minute: u32 = 33; + let second: u32 = 59; + + let dt = DateTime { + year, + month, + day, + hour, + minute, + second, + }; + let chronos_dt = chrono::Utc + .ymd(year as i32, month, day) + .and_hms(hour, minute, second); + + assert_eq!(dt.timestamp(), chronos_dt.timestamp() as u32); + } #[test] fn error_str() {