Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,29 @@ 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 = []

[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"
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ FTPS support is achieved through [rust-native-tls](https://github.com/sfackler/r

```toml
[dependencies]
ftp = { version = "<version>", features = ["secure"] }
ftp = { version = "<version>", features = ["openssl"] }
```

rust-ftp also supports native-tls

```toml
[dependencies]
ftp = { version = "<version>", features = ["native-tls"] }
```

## Usage
Expand Down
18 changes: 9 additions & 9 deletions src/data_stream.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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<TcpStream>),
#[cfg(all(feature = "secure", feature = "native-tls"))]
#[cfg(feature = "native-tls")]
Ssl(TlsStream<TcpStream>),
}

#[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 {
Expand All @@ -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(),
}
}
Expand All @@ -52,7 +52,7 @@ impl Read for DataStream {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
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),
}
}
Expand All @@ -62,15 +62,15 @@ impl Write for DataStream {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
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),
}
}

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(),
}
}
Expand Down
84 changes: 62 additions & 22 deletions src/ftp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use super::{
};

use {
chrono::{offset::TimeZone, DateTime, Utc},
regex::Regex,
std::{
borrow::Cow,
Expand All @@ -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! {
Expand All @@ -39,17 +38,51 @@ lazy_static! {
pub struct FtpStream {
reader: BufReader<DataStream>,
welcome_msg: Option<String>,
#[cfg(all(feature = "secure", feature = "native-tls"))]
#[cfg(feature = "native-tls")]
tls_ctx: Option<TlsConnector>,
#[cfg(all(feature = "secure", feature = "native-tls"))]
#[cfg(feature = "native-tls")]
domain: Option<String>,
#[cfg(all(feature = "secure", not(feature = "native-tls")))]
#[cfg(feature = "openssl")]
ssl_cfg: Option<SslContext>,
}

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<A: ToSocketAddrs>(addr: A) -> crate::Result<FtpStream> {
TcpStream::connect(addr)
.map_err(FtpError::ConnectionError)
Expand All @@ -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<A: ToSocketAddrs>(addr: A) -> crate::Result<FtpStream> {
TcpStream::connect(addr)
.map_err(FtpError::ConnectionError)
Expand All @@ -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<A: ToSocketAddrs>(addr: A) -> crate::Result<FtpStream> {
TcpStream::connect(addr)
.map_err(FtpError::ConnectionError)
Expand Down Expand Up @@ -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,
Expand All @@ -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")?;
Expand Down Expand Up @@ -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<FtpStream> {
// Ask the server to stop securing data
self.write_str("CCC\r\n")?;
Expand All @@ -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)
}
Expand All @@ -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<FtpStream> {
// Ask the server to start securing data.
self.write_str("AUTH TLS\r\n")?;
Expand Down Expand Up @@ -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<FtpStream> {
// Ask the server to stop securing data
self.write_str("CCC\r\n")?;
Expand All @@ -279,15 +314,15 @@ 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<DataStream> {
let addr = self.pasv()?;
self.write_str(cmd)?;
Ok(DataStream::Tcp(TcpStream::connect(addr)?))
}

/// 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<DataStream> {
let addr = self.pasv()?;
self.write_str(cmd)?;
Expand All @@ -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<DataStream> {
let addr = self.pasv()?;
self.write_str(cmd)?;
Expand Down Expand Up @@ -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)?
Expand Down Expand Up @@ -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<Option<DateTime<Utc>>> {
pub fn mdtm(&mut self, pathname: &str) -> crate::Result<Option<DateTime>> {
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::<i32>().unwrap(),
caps[1].parse::<u32>().unwrap(),
caps[2].parse::<u32>().unwrap(),
caps[3].parse::<u32>().unwrap(),
);
Expand All @@ -655,9 +690,14 @@ impl FtpStream {
caps[5].parse::<u32>().unwrap(),
caps[6].parse::<u32>().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),
}
Expand Down
11 changes: 5 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
//! before authentication.
//!
#![cfg_attr(
all(feature = "secure", not(feature = "native-tls")),
feature = "openssl",
doc = r##"
## FTPS Usage

Expand All @@ -51,7 +51,7 @@ let _ = ftp_stream.quit();
"##
)]
#![cfg_attr(
all(feature = "secure", feature = "native-tls"),
feature = "native-ssl",
doc = r##"
## FTPS Usage

Expand All @@ -74,20 +74,19 @@ 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;
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.
Expand Down
Loading