From 7b252edb5d054dec54b904a7efc92bd67108084d Mon Sep 17 00:00:00 2001 From: Christian Visintin Date: Fri, 24 Feb 2023 16:04:46 +0100 Subject: [PATCH] 33 bug features are not additive (#34) * sync with multi type * async with additive features * My boss forced me to build this feature... Pure shit. * release notes --- .github/workflows/cargo.yml | 24 +--- CHANGELOG.md | 15 ++ README.md | 47 +++--- suppaftp-cli/Cargo.toml | 4 +- suppaftp-cli/src/actions.rs | 4 +- suppaftp-cli/src/main.rs | 2 +- suppaftp/Cargo.toml | 5 +- suppaftp/src/async_ftp/data_stream.rs | 39 ++--- suppaftp/src/async_ftp/mod.rs | 175 +++++++++++++++-------- suppaftp/src/async_ftp/tls.rs | 84 ++++++++++- suppaftp/src/async_ftp/tls/native_tls.rs | 101 +++++++++++-- suppaftp/src/async_ftp/tls/rustls.rs | 98 +++++++++++-- suppaftp/src/lib.rs | 83 ++++++----- suppaftp/src/sync_ftp/data_stream.rs | 38 +++-- suppaftp/src/sync_ftp/mod.rs | 116 +++++++++------ suppaftp/src/sync_ftp/tls.rs | 48 ++++++- suppaftp/src/sync_ftp/tls/native_tls.rs | 50 ++++--- suppaftp/src/sync_ftp/tls/rustls.rs | 28 ++-- 18 files changed, 681 insertions(+), 280 deletions(-) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index bedde89..ca39ff5 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -42,31 +42,19 @@ jobs: with: command: build args: --features async-native-tls,deprecated --package suppaftp - - name: Run tests (native-tls) + - name: Build all features uses: actions-rs/cargo@v1 with: - command: test - args: --lib --package suppaftp --no-default-features --features native-tls,async-native-tls,with-containers --no-fail-fast - env: - RUST_LOG: trace - - name: Run tests (rustls) + command: build + args: --features deprecated,native-tls,rustls,async-native-tls,async-rustls --package suppaftp + - name: Run tests uses: actions-rs/cargo@v1 with: command: test - args: --lib --package suppaftp --no-default-features --features rustls,async-rustls,with-containers --no-fail-fast + args: --lib --package suppaftp --no-default-features --features rustls,native-tls,async-native-tls,async-rustls,with-containers --no-fail-fast env: RUST_LOG: trace - name: Format run: cargo fmt --all -- --check - name: Clippy - run: cargo clippy --package suppaftp --features deprecated -- -Dwarnings - - name: Clippy (async) - run: cargo clippy --package suppaftp --features async,deprecated -- -Dwarnings - - name: Clippy (native-tls) - run: cargo clippy --package suppaftp --features native-tls,deprecated -- -Dwarnings - - name: Clippy (async-native-tls) - run: cargo clippy --package suppaftp --features async-native-tls,deprecated -- -Dwarnings - - name: Clippy (rustls) - run: cargo clippy --package suppaftp --features rustls,deprecated -- -Dwarnings - - name: Clippy (async-rustls) - run: cargo clippy --package suppaftp --features async-rustls,deprecated -- -Dwarnings + run: cargo clippy --package suppaftp --features deprecated,native-tls,rustls,async-native-tls,async-rustls -- -Dwarnings diff --git a/CHANGELOG.md b/CHANGELOG.md index a4bbcad..14eadc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [5.0.0](#500) - [4.7.0](#470) - [4.6.1](#461) - [4.6.0](#460) @@ -21,6 +22,20 @@ --- +## 5.0.0 + +Released on 24/02/2023 + +- [Issue 33](https://github.com/veeso/suppaftp/issues/33) **‼️ BREAKING CHANGES ‼️** + - Features are now additive. This means that you can successfully build suppaftp with all the features enabled at the same time. + - Ftp stream has now been split into different types: + - `FtpStream`: sync no-tls stream + - `NativeTlsFtpStream`: ftp stream with TLS with native-tls + - `RustlsFtpStream`: ftp stream with TLS with rustls + - `AsyncFtpStream`: async no-tls stream + - `AsyncNativeTlsFtpStream`: async ftp stream with TLS with async-native-tls + - `AsyncRustlsFtpStream`: async ftp stream with TLS with async-rustls + ## 4.7.0 Released on 01/02/2023 diff --git a/README.md b/README.md index 6ee6d04..123c26c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Developed by veeso and Matt McCoy

-

Current version: 4.7.0 (01/02/2023)

+

Current version: 5.0.0 (24/02/2023)

💡 If you don't know what to choose, `native-tls` should be preferred for compatibility reasons. +> 💡 If you don't know what to choose, `native-tls` should be preferred for compatibility reasons. > ❗ If you want to link libssl statically, enable feature `native-tls-vendored` #### Async support @@ -139,11 +139,11 @@ suppaftp = { version = "^4.7.0", features = ["rustls"] } If you want to enable **async** support, you must enable `async` feature in your cargo dependencies. ```toml -suppaftp = { version = "^4.7.0", features = ["async"] } +suppaftp = { version = "^5.0.0", features = ["async"] } ``` -> ⚠️ If you want to enable both **native-tls** and **async** you must use the **async-native-tls** feature ⚠️ -> ⚠️ If you want to enable both **rustls** and **async** you must use the **async-rustls** feature ⚠️ +> ⚠️ If you want to enable both **native-tls** and **async** you must use the **async-native-tls** feature ⚠️ +> ⚠️ If you want to enable both **rustls** and **async** you must use the **async-rustls** feature ⚠️ > ❗ If you want to link libssl statically, enable feature `async-native-tls-vendored` #### Deprecated methods @@ -194,18 +194,16 @@ fn main() { #### Ftp with TLS (native-tls) ```rust -use std::str; -use std::io::Cursor; -use suppaftp::{FtpStream}; -use suppaftp::native_tls::TlsConnector; +use suppaftp::{NativeTlsFtpStream, NativeTlsConnector}; +use suppaftp::native_tls::{TlsConnector, TlsStream}; fn main() { - // Create a connection to an FTP server and authenticate to it. - let mut ftp_stream = FtpStream::connect("127.0.0.1:21") - .into_secure(NativeTlsConnector::new().unwrap().into(), "domain-name") - .unwrap(); - // Terminate the connection to the server. - let _ = ftp_stream.quit(); + let ftp_stream = NativeTlsFtpStream::connect("test.rebex.net:21").unwrap(); + // Switch to the secure mode + let mut ftp_stream = ftp_stream.into_secure(NativeTlsConnector::from(TlsConnector::new().unwrap()), "test.rebex.net").unwrap(); + ftp_stream.login("demo", "password").unwrap(); + // Do other secret stuff + assert!(ftp_stream.quit().is_ok()); } ``` @@ -215,7 +213,7 @@ fn main() { use std::str; use std::io::Cursor; use std::sync::Arc; -use suppaftp::{FtpStream}; +use suppaftp::{RustlsFtpStream, RustlsConnector}; use suppaftp::rustls::ClientConfig; fn main() { @@ -233,9 +231,9 @@ fn main() { .with_no_client_auth(); // Create a connection to an FTP server and authenticate to it. let config = Arc::new(rustls_config()); - let mut ftp_stream = FtpStream::connect("test.rebex.net:21") + let mut ftp_stream = RustlsFtpStream::connect("test.rebex.net:21") .unwrap() - .into_secure(Arc::clone(&config).into(), "test.rebex.net") + .into_secure(RustlsConnector::from(Arc::clone(&config)), "test.rebex.net") .unwrap(); // Terminate the connection to the server. let _ = ftp_stream.quit(); @@ -245,14 +243,13 @@ fn main() { #### Going Async ```rust -use suppaftp::FtpStream; +use suppaftp::{AsyncFtpStream, AsyncNativeTlsConnector}; use suppaftp::async_native_tls::{TlsConnector, TlsStream}; -let ftp_stream = FtpStream::connect("test.rebex.net:21").await.unwrap(); +let ftp_stream = AsyncFtpStream::connect("test.rebex.net:21").await.unwrap(); // Switch to the secure mode -let mut ftp_stream = ftp_stream.into_secure(TlsConnector::new().into(), "test.rebex.net").await.unwrap(); +let mut ftp_stream = ftp_stream.into_secure(AsyncNativeTlsConnector::from(TlsConnector::new()), "test.rebex.net").await.unwrap(); ftp_stream.login("demo", "password").await.unwrap(); // Do other secret stuff -// Do all public stuff assert!(ftp_stream.quit().await.is_ok()); ``` diff --git a/suppaftp-cli/Cargo.toml b/suppaftp-cli/Cargo.toml index ee3b88b..b3787b9 100644 --- a/suppaftp-cli/Cargo.toml +++ b/suppaftp-cli/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" name = "suppaftp-cli" readme = "../README.md" repository = "https://github.com/veeso/suppaftp" -version = "4.7.0" +version = "5.0.0" [[bin]] name = "suppaftp" @@ -21,4 +21,4 @@ argh = "^0.1" env_logger = "^0.10" log = "^0.4" rpassword = "^7.2" -suppaftp = { path = "../suppaftp", version = "^4.7", features = [ "native-tls" ] } +suppaftp = { path = "../suppaftp", version = "^5.0", features = [ "native-tls" ] } diff --git a/suppaftp-cli/src/actions.rs b/suppaftp-cli/src/actions.rs index 4d8c168..0a07bf9 100644 --- a/suppaftp-cli/src/actions.rs +++ b/suppaftp-cli/src/actions.rs @@ -5,7 +5,7 @@ use std::io; use std::path::Path; use suppaftp::native_tls::TlsConnector; use suppaftp::types::FileType; -use suppaftp::Mode; +use suppaftp::{Mode, NativeTlsConnector}; pub fn quit(mut ftp: Option) { if let Some(mut ftp) = ftp.take() { @@ -60,7 +60,7 @@ pub fn connect(remote: &str, secure: bool) -> Option { }; // Get address without port let address: &str = remote.split(':').next().unwrap(); - stream = match stream.into_secure(ctx.into(), address) { + stream = match stream.into_secure(NativeTlsConnector::from(ctx), address) { Ok(s) => s, Err(err) => { eprintln!("Failed to setup TLS stream: {err}"); diff --git a/suppaftp-cli/src/main.rs b/suppaftp-cli/src/main.rs index a11281f..81f4181 100644 --- a/suppaftp-cli/src/main.rs +++ b/suppaftp-cli/src/main.rs @@ -17,7 +17,7 @@ use log::LevelFilter; use std::io; use std::io::Write; use std::str::FromStr; -use suppaftp::{FtpError, FtpStream}; +use suppaftp::{FtpError, NativeTlsFtpStream as FtpStream}; const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); const APP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); diff --git a/suppaftp/Cargo.toml b/suppaftp/Cargo.toml index a1c8fb3..cd58cd3 100644 --- a/suppaftp/Cargo.toml +++ b/suppaftp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "suppaftp" -version = "4.7.0" +version = "5.0.0" authors = ["Christian Visintin ", "Matt McCoy "] edition = "2021" documentation = "https://docs.rs/suppaftp/" @@ -24,6 +24,7 @@ thiserror = "^1" # async async-std = { version = "^1.10", optional = true } async-native-tls-crate = { package = "async-native-tls", version = "^0.4", optional = true } +async-trait = { version = "0.1.64", optional = true } async-tls = { version = "^0.11", optional = true } pin-project = { version = "^1", optional = true } # secure @@ -41,7 +42,7 @@ webpki-roots = "0.22.5" [features] default = [ ] # Enable async support for suppaftp -async = ["async-std", "pin-project"] +async = ["async-std", "async-trait", "pin-project"] async-default-tls = [ "async-native-tls" ] async-native-tls = [ "async-native-tls-crate", "async-secure" ] async-rustls = [ "async-tls", "async-secure" ] diff --git a/suppaftp/src/async_ftp/data_stream.rs b/suppaftp/src/async_ftp/data_stream.rs index c76f7bf..8ed63cc 100644 --- a/suppaftp/src/async_ftp/data_stream.rs +++ b/suppaftp/src/async_ftp/data_stream.rs @@ -2,27 +2,30 @@ //! //! This module exposes the async data stream implementation where bytes must be written to/read from -#[cfg(feature = "async-native-tls")] -use async_native_tls::TlsStream; #[cfg(any(feature = "async", feature = "async-secure"))] use async_std::io::{Read, Result, Write}; #[cfg(any(feature = "async", feature = "async-secure"))] use async_std::net::TcpStream; -#[cfg(feature = "async-rustls")] -use async_tls::client::TlsStream; use pin_project::pin_project; use std::pin::Pin; +use super::AsyncTlsStream; + /// Data Stream used for communications. It can be both of type Tcp in case of plain communication or Ssl in case of FTPS #[pin_project(project = DataStreamProj)] -pub enum DataStream { +pub enum DataStream +where + T: AsyncTlsStream, +{ Tcp(#[pin] TcpStream), - #[cfg(feature = "async-secure")] - Ssl(#[pin] Box>), + Ssl(#[pin] Box), } #[cfg(feature = "async-secure")] -impl DataStream { +impl DataStream +where + T: AsyncTlsStream, +{ /// Unwrap the stream into TcpStream. This method is only used in secure connection. pub fn into_tcp_stream(self) -> TcpStream { match self { @@ -32,12 +35,14 @@ impl DataStream { } } -impl DataStream { +impl DataStream +where + T: AsyncTlsStream, +{ /// Returns a reference to the underlying TcpStream. pub fn get_ref(&self) -> &TcpStream { match self { DataStream::Tcp(ref stream) => stream, - #[cfg(feature = "async-secure")] DataStream::Ssl(ref stream) => stream.get_ref(), } } @@ -45,7 +50,10 @@ impl DataStream { // -- async -impl Read for DataStream { +impl Read for DataStream +where + T: AsyncTlsStream, +{ fn poll_read( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, @@ -53,13 +61,15 @@ impl Read for DataStream { ) -> std::task::Poll> { match self.project() { DataStreamProj::Tcp(stream) => stream.poll_read(cx, buf), - #[cfg(feature = "async-secure")] DataStreamProj::Ssl(stream) => stream.poll_read(cx, buf), } } } -impl Write for DataStream { +impl Write for DataStream +where + T: AsyncTlsStream, +{ fn poll_write( self: Pin<&mut Self>, cx: &mut std::task::Context<'_>, @@ -67,7 +77,6 @@ impl Write for DataStream { ) -> std::task::Poll> { match self.project() { DataStreamProj::Tcp(stream) => stream.poll_write(cx, buf), - #[cfg(feature = "async-secure")] DataStreamProj::Ssl(stream) => stream.poll_write(cx, buf), } } @@ -78,7 +87,6 @@ impl Write for DataStream { ) -> std::task::Poll> { match self.project() { DataStreamProj::Tcp(stream) => stream.poll_flush(cx), - #[cfg(feature = "async-secure")] DataStreamProj::Ssl(stream) => stream.poll_flush(cx), } } @@ -89,7 +97,6 @@ impl Write for DataStream { ) -> std::task::Poll> { match self.project() { DataStreamProj::Tcp(stream) => stream.poll_close(cx), - #[cfg(feature = "async-secure")] DataStreamProj::Ssl(stream) => stream.poll_close(cx), } } diff --git a/suppaftp/src/async_ftp/mod.rs b/suppaftp/src/async_ftp/mod.rs index 4901344..165f845 100644 --- a/suppaftp/src/async_ftp/mod.rs +++ b/suppaftp/src/async_ftp/mod.rs @@ -3,7 +3,6 @@ //! This module contains the definition for all async implementation of suppaftp mod data_stream; -#[cfg(feature = "async-secure")] mod tls; use super::regex::{EPSV_PORT_RE, MDTM_RE, PASV_PORT_RE, SIZE_RE}; @@ -12,30 +11,48 @@ use super::Status; use crate::command::Command; #[cfg(feature = "async-secure")] use crate::command::ProtectionLevel; +use async_std::io::prelude::BufReadExt; use data_stream::DataStream; -#[cfg(feature = "async-secure")] -pub use tls::TlsConnector; +use tls::AsyncTlsStream; -use async_std::io::{copy, BufReader, Read, Write}; -use async_std::net::ToSocketAddrs; -use async_std::net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream}; -use async_std::prelude::*; +use async_std::io::{copy, BufReader, Read, Write, WriteExt}; +use async_std::net::{TcpListener, TcpStream, ToSocketAddrs}; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; +#[cfg(not(feature = "async-secure"))] +use std::marker::PhantomData; +use std::net::{Ipv4Addr, SocketAddr}; use std::string::String; +// export +pub use tls::AsyncNoTlsStream; +#[cfg(feature = "async-secure")] +pub use tls::AsyncTlsConnector; +#[cfg(feature = "async-native-tls")] +pub use tls::{AsyncNativeTlsConnector, AsyncNativeTlsStream}; +#[cfg(feature = "async-rustls")] +pub use tls::{AsyncRustlsConnector, AsyncRustlsStream}; + /// Stream to interface with the FTP server. This interface is only for the command stream. -pub struct FtpStream { - reader: BufReader, +pub struct ImplAsyncFtpStream +where + T: AsyncTlsStream, +{ + reader: BufReader>, mode: Mode, nat_workaround: bool, welcome_msg: Option, + #[cfg(not(feature = "async-secure"))] + marker: PhantomData, #[cfg(feature = "async-secure")] - tls_ctx: Option, + tls_ctx: Option>>, #[cfg(feature = "async-secure")] domain: Option, } -impl FtpStream { +impl ImplAsyncFtpStream +where + T: AsyncTlsStream, +{ /// Creates an FTP Stream. #[cfg(not(feature = "async-secure"))] pub async fn connect(addr: A) -> FtpResult { @@ -45,8 +62,9 @@ impl FtpStream { .map_err(FtpError::ConnectionError)?; debug!("Established connection with server"); - let mut ftp_stream = FtpStream { + let mut ftp_stream = ImplAsyncFtpStream { reader: BufReader::new(DataStream::Tcp(stream)), + marker: PhantomData {}, mode: Mode::Passive, nat_workaround: false, welcome_msg: None, @@ -71,7 +89,7 @@ impl FtpStream { .await .map_err(FtpError::ConnectionError)?; debug!("Connecting to server"); - let mut ftp_stream = FtpStream { + let mut ftp_stream = ImplAsyncFtpStream { reader: BufReader::new(DataStream::Tcp(stream)), mode: Mode::Passive, nat_workaround: false, @@ -97,20 +115,20 @@ impl FtpStream { /// ## Example /// /// ```rust,no_run - /// use suppaftp::FtpStream; + /// use suppaftp::ImplAsyncFtpStream; /// use suppaftp::async_native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; /// /// // Create a TlsConnector /// // NOTE: For custom options see /// let mut ctx = TlsConnector::new(); - /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").await.unwrap(); + /// let mut ftp_stream = ImplAsyncFtpStream::connect("127.0.0.1:21").await.unwrap(); /// let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost").await.unwrap(); /// ``` #[cfg(feature = "async-secure")] pub async fn into_secure( mut self, - tls_connector: TlsConnector, + tls_connector: impl AsyncTlsConnector + 'static, domain: &str, ) -> FtpResult { debug!("Initializing TLS auth"); @@ -125,11 +143,11 @@ impl FtpStream { ) .await .map_err(|e| FtpError::SecureError(format!("{e}")))?; - let mut secured_ftp_tream = FtpStream { + let mut secured_ftp_tream = ImplAsyncFtpStream { reader: BufReader::new(DataStream::Ssl(Box::new(stream))), mode: self.mode, nat_workaround: self.nat_workaround, - tls_ctx: Some(tls_connector), + tls_ctx: Some(Box::new(tls_connector)), domain: Some(String::from(domain)), welcome_msg: self.welcome_msg, }; @@ -152,19 +170,19 @@ impl FtpStream { /// ## Example /// /// ```rust,no_run - /// use suppaftp::FtpStream; + /// use suppaftp::ImplAsyncFtpStream; /// use suppaftp::native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; /// /// // Create a TlsConnector /// // NOTE: For custom options see /// let mut ctx = TlsConnector::new(); - /// let mut ftp_stream = FtpStream::connect_secure_implicit("127.0.0.1:990", ctx, "localhost").await.unwrap(); + /// let mut ftp_stream = ImplAsyncFtpStream::connect_secure_implicit("127.0.0.1:990", ctx, "localhost").await.unwrap(); /// ``` #[cfg(all(feature = "async-secure", feature = "deprecated"))] pub async fn connect_secure_implicit( addr: A, - tls_connector: TlsConnector, + tls_connector: impl AsyncTlsConnector + 'static, domain: &str, ) -> FtpResult { debug!("Connecting to server (secure)"); @@ -173,7 +191,7 @@ impl FtpStream { .map_err(FtpError::ConnectionError) .map(|stream| { debug!("Established connection with server"); - FtpStream { + Self { reader: BufReader::new(DataStream::Tcp(stream)), mode: Mode::Passive, nat_workaround: false, @@ -189,11 +207,11 @@ impl FtpStream { .await .map_err(|e| FtpError::SecureError(format!("{e}")))?; debug!("TLS Steam OK"); - let mut stream = FtpStream { + let mut stream = ImplAsyncFtpStream { reader: BufReader::new(DataStream::Ssl(stream.into())), mode: Mode::Passive, nat_workaround: false, - tls_ctx: Some(tls_connector), + tls_ctx: Some(Box::new(tls_connector)), domain: Some(String::from(domain)), welcome_msg: None, }; @@ -363,15 +381,16 @@ impl FtpStream { /// The implementation of `RETR` command where `filename` is the name of the file /// to download from FTP and `reader` is the function which operates with the /// data stream opened. - pub async fn retr(&mut self, file_name: S, mut reader: F) -> FtpResult + pub async fn retr(&mut self, file_name: S, mut reader: F) -> FtpResult where - F: FnMut(&mut dyn Read) -> FtpResult, + F: FnMut(&mut dyn Read) -> FtpResult, S: AsRef, { match self.retr_as_stream(file_name).await { Ok(mut stream) => { let result = reader(&mut stream)?; - self.finalize_retr_stream(stream).await.map(|_| result) + self.finalize_retr_stream(stream).await?; + Ok(result) } Err(err) => Err(err), } @@ -382,7 +401,10 @@ impl FtpStream { /// The reader returned should be dropped. /// Also you will have to read the response to make sure it has the correct value. /// Once file has been read, call `finalize_retr_stream()` - pub async fn retr_as_stream>(&mut self, file_name: S) -> FtpResult { + pub async fn retr_as_stream>( + &mut self, + file_name: S, + ) -> FtpResult> { debug!("Retrieving '{}'", file_name.as_ref()); let data_stream = self .data_command(Command::Retr(file_name.as_ref().to_string())) @@ -444,7 +466,10 @@ impl FtpStream { /// The returned stream must be then correctly manipulated to write the content of the source file to the remote destination /// The stream must be then correctly dropped. /// Once you've finished the write, YOU MUST CALL THIS METHOD: `finalize_put_stream` - pub async fn put_with_stream>(&mut self, filename: S) -> FtpResult { + pub async fn put_with_stream>( + &mut self, + filename: S, + ) -> FtpResult> { debug!("Put file {}", filename.as_ref()); let stream = self .data_command(Command::Store(filename.as_ref().to_string())) @@ -473,7 +498,7 @@ impl FtpStream { pub async fn append_with_stream>( &mut self, filename: S, - ) -> FtpResult { + ) -> FtpResult> { debug!("Appending to file {}", filename.as_ref()); let stream = self .data_command(Command::Appe(filename.as_ref().to_string())) @@ -612,7 +637,7 @@ impl FtpStream { // -- private /// Execute command which send data back in a separate stream - async fn data_command(&mut self, cmd: Command) -> FtpResult { + async fn data_command(&mut self, cmd: Command) -> FtpResult> { let stream = match self.mode { Mode::Active => { let listener = self.active().await?; @@ -730,9 +755,7 @@ impl FtpStream { let ip = match self.reader.get_mut() { DataStream::Tcp(stream) => stream.local_addr().unwrap().ip(), - - #[cfg(feature = "async-secure")] - DataStream::Ssl(stream) => stream.get_mut().local_addr().unwrap().ip(), + DataStream::Ssl(stream) => stream.get_ref().local_addr().unwrap().ip(), }; let msb = addr.port() / 256; @@ -749,7 +772,7 @@ impl FtpStream { /// Retrieve stream "message" async fn get_lines_from_stream( - data_stream: &mut BufReader, + data_stream: &mut BufReader>, ) -> FtpResult> { let mut lines: Vec = Vec::new(); @@ -860,7 +883,12 @@ mod test { use super::*; #[cfg(feature = "with-containers")] use crate::types::FormatControl; + use crate::AsyncFtpStream; + #[cfg(feature = "async-native-tls")] + use crate::{AsyncNativeTlsConnector, AsyncNativeTlsFtpStream}; + #[cfg(feature = "async-rustls")] + use crate::{AsyncRustlsConnector, AsyncRustlsFtpStream}; #[cfg(feature = "async-native-tls")] use async_native_tls::TlsConnector as NativeTlsConnector; #[cfg(feature = "async-rustls")] @@ -877,7 +905,7 @@ mod test { #[serial] async fn connect() { crate::log_init(); - let stream: FtpStream = setup_stream().await; + let stream = setup_stream().await; finalize_stream(stream).await; } @@ -885,10 +913,17 @@ mod test { #[cfg(feature = "async-native-tls")] #[serial] async fn should_connect_ssl_native_tls() { + use crate::AsyncNativeTlsFtpStream; + crate::log_init(); - let ftp_stream = FtpStream::connect("test.rebex.net:21").await.unwrap(); + let ftp_stream = AsyncNativeTlsFtpStream::connect("test.rebex.net:21") + .await + .unwrap(); let mut ftp_stream = ftp_stream - .into_secure(NativeTlsConnector::new().into(), "test.rebex.net") + .into_secure( + AsyncNativeTlsConnector::from(NativeTlsConnector::new()), + "test.rebex.net", + ) .await .unwrap(); // Set timeout (to test ref to ssl) @@ -906,9 +941,9 @@ mod test { #[cfg(all(feature = "async-native-tls", feature = "deprecated"))] async fn should_connect_ssl_implicit_native_tls() { crate::log_init(); - let mut ftp_stream = FtpStream::connect_secure_implicit( + let mut ftp_stream = AsyncNativeTlsFtpStream::connect_secure_implicit( "test.rebex.net:990", - NativeTlsConnector::new().into(), + AsyncNativeTlsConnector::from(NativeTlsConnector::new()), "test.rebex.net", ) .await @@ -928,10 +963,13 @@ mod test { #[serial] async fn should_work_after_clear_command_channel_native_tls() { crate::log_init(); - let mut ftp_stream = FtpStream::connect("test.rebex.net:21") + let mut ftp_stream = AsyncNativeTlsFtpStream::connect("test.rebex.net:21") .await .unwrap() - .into_secure(NativeTlsConnector::new().into(), "test.rebex.net") + .into_secure( + AsyncNativeTlsConnector::from(NativeTlsConnector::new()), + "test.rebex.net", + ) .await .unwrap() .clear_command_channel() @@ -950,9 +988,14 @@ mod test { #[serial] async fn should_connect_ssl_rustls() { crate::log_init(); - let ftp_stream = FtpStream::connect("ftp.uni-bayreuth.de:21").await.unwrap(); + let ftp_stream = AsyncRustlsFtpStream::connect("ftp.uni-bayreuth.de:21") + .await + .unwrap(); let mut ftp_stream = ftp_stream - .into_secure(RustlsTlsConnector::new().into(), "ftp.uni-bayreuth.de") + .into_secure( + AsyncRustlsConnector::from(RustlsTlsConnector::new()), + "ftp.uni-bayreuth.de", + ) .await .unwrap(); // Set timeout (to test ref to ssl) @@ -965,7 +1008,7 @@ mod test { #[serial] async fn should_change_mode() { crate::log_init(); - let mut ftp_stream = FtpStream::connect("test.rebex.net:21") + let mut ftp_stream = AsyncFtpStream::connect("test.rebex.net:21") .await .map(|x| x.active_mode()) .unwrap(); @@ -979,7 +1022,7 @@ mod test { #[serial] async fn welcome_message() { crate::log_init(); - let stream: FtpStream = setup_stream().await; + let stream = setup_stream().await; assert_eq!( stream.get_welcome_msg().unwrap(), "220 You will be disconnected after 15 minutes of inactivity." @@ -992,7 +1035,7 @@ mod test { #[serial] async fn should_set_passive_nat_workaround() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; stream.set_passive_nat_workaround(true); assert!(stream.nat_workaround); finalize_stream(stream).await; @@ -1003,7 +1046,7 @@ mod test { #[serial] async fn get_ref() { crate::log_init(); - let stream: FtpStream = setup_stream().await; + let stream = setup_stream().await; assert!(stream.get_ref().await.set_ttl(255).is_ok()); finalize_stream(stream).await; } @@ -1013,7 +1056,7 @@ mod test { #[serial] async fn change_wrkdir() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; let wrkdir: String = stream.pwd().await.unwrap(); assert!(stream.cwd("/").await.is_ok()); assert_eq!(stream.pwd().await.unwrap().as_str(), "/"); @@ -1026,7 +1069,7 @@ mod test { #[serial] async fn cd_up() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; let wrkdir: String = stream.pwd().await.unwrap(); assert!(stream.cdup().await.is_ok()); assert_eq!(stream.pwd().await.unwrap().as_str(), "/"); @@ -1039,7 +1082,7 @@ mod test { #[serial] async fn noop() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; assert!(stream.noop().await.is_ok()); finalize_stream(stream).await; } @@ -1049,7 +1092,7 @@ mod test { #[serial] async fn make_and_remove_dir() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; // Make directory assert!(stream.mkdir("omar").await.is_ok()); // It shouldn't allow me to re-create the directory; should return error code 550 @@ -1069,7 +1112,7 @@ mod test { #[serial] async fn set_transfer_type() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; assert!(stream.transfer_type(FileType::Binary).await.is_ok()); assert!(stream .transfer_type(FileType::Ascii(FormatControl::Default)) @@ -1085,7 +1128,7 @@ mod test { crate::log_init(); use async_std::io::Cursor; - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; // Set transfer type to Binary assert!(stream.transfer_type(FileType::Binary).await.is_ok()); // Write file @@ -1098,7 +1141,11 @@ mod test { // Read file let mut reader = stream.retr_as_stream("test.txt").await.unwrap(); let mut buffer = Vec::new(); - assert!(reader.read_to_end(&mut buffer).await.is_ok()); + assert!( + async_std::io::ReadExt::read_to_end(&mut reader, &mut buffer) + .await + .is_ok() + ); // Verify file matches assert_eq!(buffer.as_slice(), "test data\ntest data\n".as_bytes()); // Finalize @@ -1132,7 +1179,7 @@ mod test { #[serial] async fn should_abort_transfer() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; // Set transfer type to Binary assert!(stream.transfer_type(FileType::Binary).await.is_ok()); // cleanup @@ -1161,7 +1208,7 @@ mod test { #[cfg(feature = "with-containers")] async fn should_resume_transfer() { crate::log_init(); - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; // Set transfer type to Binary assert!(stream.transfer_type(FileType::Binary).await.is_ok()); // get dir @@ -1179,7 +1226,9 @@ mod test { drop(stream); drop(transfer_stream); // Re-connect to server - let mut stream = FtpStream::connect("127.0.0.1:10021").await.unwrap(); + let mut stream = ImplAsyncFtpStream::connect("127.0.0.1:10021") + .await + .unwrap(); assert!(stream.login("test", "test").await.is_ok()); // Go back to previous dir assert!(stream.cwd(wrkdir).await.is_ok()); @@ -1213,7 +1262,7 @@ mod test { crate::log_init(); use async_std::io::Cursor; - let mut stream: FtpStream = setup_stream().await; + let mut stream = setup_stream().await; // Set transfer type to Binary assert!(stream.transfer_type(FileType::Binary).await.is_ok()); stream.set_mode(Mode::ExtendedPassive); @@ -1229,9 +1278,11 @@ mod test { // -- test utils #[cfg(feature = "with-containers")] - async fn setup_stream() -> FtpStream { + async fn setup_stream() -> crate::AsyncFtpStream { crate::log_init(); - let mut ftp_stream = FtpStream::connect("127.0.0.1:10021").await.unwrap(); + let mut ftp_stream = ImplAsyncFtpStream::connect("127.0.0.1:10021") + .await + .unwrap(); assert!(ftp_stream.login("test", "test").await.is_ok()); // Create wrkdir let tempdir: String = generate_tempdir(); @@ -1242,7 +1293,7 @@ mod test { } #[cfg(feature = "with-containers")] - async fn finalize_stream(mut stream: FtpStream) { + async fn finalize_stream(mut stream: crate::AsyncFtpStream) { crate::log_init(); // Get working directory let wrkdir: String = stream.pwd().await.unwrap(); diff --git a/suppaftp/src/async_ftp/tls.rs b/suppaftp/src/async_ftp/tls.rs index 497eb65..7bed758 100644 --- a/suppaftp/src/async_ftp/tls.rs +++ b/suppaftp/src/async_ftp/tls.rs @@ -2,12 +2,92 @@ //! //! Tls wrappers +use async_std::io::{Read, Write}; +use async_std::net::TcpStream; +use async_trait::async_trait; +use std::fmt::Debug; + +use crate::FtpResult; + #[cfg(feature = "async-native-tls")] mod native_tls; #[cfg(feature = "async-native-tls")] -pub use self::native_tls::TlsConnector; +pub use self::native_tls::{AsyncNativeTlsConnector, AsyncNativeTlsStream}; #[cfg(feature = "async-rustls")] mod rustls; #[cfg(feature = "async-rustls")] -pub use self::rustls::TlsConnector; +pub use self::rustls::{AsyncRustlsConnector, AsyncRustlsStream}; + +#[async_trait] +pub trait AsyncTlsConnector: Debug { + type Stream: AsyncTlsStream; + + async fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult; +} + +pub trait AsyncTlsStream: Debug + Read + Write + Unpin { + type InnerStream: Read + Write; + + /// Get underlying tcp stream + fn tcp_stream(self) -> TcpStream; + + /// Get ref to underlying tcp stream + fn get_ref(&self) -> &TcpStream; + + /// Get mutable reference to tls stream + fn mut_ref(&mut self) -> &mut Self::InnerStream; +} + +#[derive(Debug)] +pub struct AsyncNoTlsStream; + +impl Read for AsyncNoTlsStream { + fn poll_read( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + _buf: &mut [u8], + ) -> std::task::Poll> { + panic!() + } +} + +impl Write for AsyncNoTlsStream { + fn poll_write( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + _buf: &[u8], + ) -> std::task::Poll> { + panic!() + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + panic!() + } + + fn poll_close( + self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + panic!() + } +} + +impl AsyncTlsStream for AsyncNoTlsStream { + type InnerStream = TcpStream; + + fn tcp_stream(self) -> TcpStream { + panic!() + } + + fn get_ref(&self) -> &TcpStream { + panic!() + } + + fn mut_ref(&mut self) -> &mut Self::InnerStream { + panic!() + } +} diff --git a/suppaftp/src/async_ftp/tls/native_tls.rs b/suppaftp/src/async_ftp/tls/native_tls.rs index dcc230f..f122cb3 100644 --- a/suppaftp/src/async_ftp/tls/native_tls.rs +++ b/suppaftp/src/async_ftp/tls/native_tls.rs @@ -2,27 +2,102 @@ //! //! Native tls types for suppaftp -use async_native_tls::{Error as TlsError, TlsConnector as NativeTlsConnector, TlsStream}; -use async_std::net::TcpStream; +use async_native_tls::{TlsConnector, TlsStream}; +use async_std::{ + io::{Read, Write}, + net::TcpStream, +}; +use async_trait::async_trait; +use pin_project::pin_project; +use std::pin::Pin; + +use super::{AsyncTlsConnector, AsyncTlsStream}; +use crate::{FtpError, FtpResult}; #[derive(Debug)] /// A Wrapper for the tls connector -pub struct TlsConnector { - connector: NativeTlsConnector, +pub struct AsyncNativeTlsConnector { + connector: TlsConnector, } -impl From for TlsConnector { - fn from(connector: NativeTlsConnector) -> Self { +impl From for AsyncNativeTlsConnector { + fn from(connector: TlsConnector) -> Self { Self { connector } } } -impl TlsConnector { - pub async fn connect( - &self, - domain: &str, - stream: TcpStream, - ) -> Result, TlsError> { - self.connector.connect(domain, stream).await +#[async_trait] +impl AsyncTlsConnector for AsyncNativeTlsConnector { + type Stream = AsyncNativeTlsStream; + + async fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult { + self.connector + .connect(domain, stream) + .await + .map(AsyncNativeTlsStream::from) + .map_err(|e| FtpError::SecureError(e.to_string())) + } +} + +#[derive(Debug)] +#[pin_project(project = AsyncNativeTlsStreamProj)] +pub struct AsyncNativeTlsStream { + #[pin] + stream: TlsStream, +} + +impl From> for AsyncNativeTlsStream { + fn from(stream: TlsStream) -> Self { + Self { stream } + } +} + +impl Read for AsyncNativeTlsStream { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut [u8], + ) -> std::task::Poll> { + self.project().stream.poll_read(cx, buf) + } +} + +impl Write for AsyncNativeTlsStream { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + self.project().stream.poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.project().stream.poll_flush(cx) + } + + fn poll_close( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.project().stream.poll_close(cx) + } +} + +impl AsyncTlsStream for AsyncNativeTlsStream { + type InnerStream = TlsStream; + + fn get_ref(&self) -> &TcpStream { + self.stream.get_ref() + } + + fn mut_ref(&mut self) -> &mut Self::InnerStream { + &mut self.stream + } + + fn tcp_stream(self) -> TcpStream { + self.stream.get_ref().clone() } } diff --git a/suppaftp/src/async_ftp/tls/rustls.rs b/suppaftp/src/async_ftp/tls/rustls.rs index 9cdb414..8c85110 100644 --- a/suppaftp/src/async_ftp/tls/rustls.rs +++ b/suppaftp/src/async_ftp/tls/rustls.rs @@ -2,33 +2,107 @@ //! //! rustls types for suppaftp -use async_std::io::Error as IoError; -use async_std::net::TcpStream; +use async_std::{ + io::{Read, Write}, + net::TcpStream, +}; use async_tls::{client::TlsStream, TlsConnector as RustlsTlsConnector}; +use async_trait::async_trait; +use pin_project::pin_project; +use std::pin::Pin; + +use super::{AsyncTlsConnector, AsyncTlsStream}; +use crate::{FtpError, FtpResult}; /// A Wrapper for the tls connector -pub struct TlsConnector { +pub struct AsyncRustlsConnector { connector: RustlsTlsConnector, } -impl std::fmt::Debug for TlsConnector { +impl std::fmt::Debug for AsyncRustlsConnector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "") } } -impl From for TlsConnector { +impl From for AsyncRustlsConnector { fn from(connector: RustlsTlsConnector) -> Self { Self { connector } } } -impl TlsConnector { - pub async fn connect( - &self, - domain: &str, - stream: TcpStream, - ) -> Result, IoError> { - self.connector.connect(domain, stream).await +#[async_trait] +impl AsyncTlsConnector for AsyncRustlsConnector { + type Stream = AsyncRustlsStream; + + async fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult { + self.connector + .connect(domain, stream) + .await + .map(AsyncRustlsStream::from) + .map_err(|e| FtpError::SecureError(e.to_string())) + } +} + +#[derive(Debug)] +#[pin_project(project = AsyncRustlsStreamProj)] +pub struct AsyncRustlsStream { + #[pin] + stream: TlsStream, +} + +impl From> for AsyncRustlsStream { + fn from(stream: TlsStream) -> Self { + Self { stream } + } +} + +impl Read for AsyncRustlsStream { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut [u8], + ) -> std::task::Poll> { + self.project().stream.poll_read(cx, buf) + } +} + +impl Write for AsyncRustlsStream { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + self.project().stream.poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.project().stream.poll_flush(cx) + } + + fn poll_close( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.project().stream.poll_close(cx) + } +} + +impl AsyncTlsStream for AsyncRustlsStream { + type InnerStream = TlsStream; + + fn get_ref(&self) -> &TcpStream { + self.stream.get_ref() + } + + fn mut_ref(&mut self) -> &mut Self::InnerStream { + &mut self.stream + } + + fn tcp_stream(self) -> TcpStream { + self.stream.get_ref().clone() } } diff --git a/suppaftp/src/lib.rs b/suppaftp/src/lib.rs index e273289..6c12c29 100644 --- a/suppaftp/src/lib.rs +++ b/suppaftp/src/lib.rs @@ -21,7 +21,7 @@ //! To get started, first add **suppaftp** to your dependencies: //! //! ```toml -//! suppaftp = "^4.7.0" +//! suppaftp = "^5.0.0" //! ``` //! //! ### Features @@ -31,9 +31,9 @@ //! If you want to enable **support for FTPS**, you must enable the `native-tls` or `rustls` feature in your cargo dependencies, based on the TLS provider you prefer. //! //! ```toml -//! suppaftp = { version = "^4.7.0", features = ["native-tls"] } +//! suppaftp = { version = "^5.0.0", features = ["native-tls"] } //! # or -//! suppaftp = { version = "^4.7.0", features = ["rustls"] } +//! suppaftp = { version = "^5.0.0", features = ["rustls"] } //! ``` //! //! > 💡 If you don't know what to choose, `native-tls` should be preferred for compatibility reasons. @@ -43,7 +43,7 @@ //! If you want to enable **async** support, you must enable `async` feature in your cargo dependencies. //! //! ```toml -//! suppaftp = { version = "^4.7.0", features = ["async"] } +//! suppaftp = { version = "^5.0.0", features = ["async"] } //! ``` //! //! > ⚠️ If you want to enable both **native-tls** and **async** you must use the **async-native-tls** feature ⚠️ @@ -86,19 +86,14 @@ //! ### FTPS Usage //! //! ```rust -//! extern crate suppaftp; -//! -//! use suppaftp::FtpStream; +//! use suppaftp::{NativeTlsFtpStream, NativeTlsConnector}; //! use suppaftp::native_tls::{TlsConnector, TlsStream}; //! -//! let ftp_stream = FtpStream::connect("test.rebex.net:21").unwrap(); +//! let ftp_stream = NativeTlsFtpStream::connect("test.rebex.net:21").unwrap(); //! // Switch to the secure mode -//! let mut ftp_stream = ftp_stream.into_secure(TlsConnector::new().unwrap().into(), "test.rebex.net").unwrap(); +//! let mut ftp_stream = ftp_stream.into_secure(NativeTlsConnector::from(TlsConnector::new().unwrap()), "test.rebex.net").unwrap(); //! ftp_stream.login("demo", "password").unwrap(); //! // Do other secret stuff -//! // Switch back to the insecure mode (if required) -//! let mut ftp_stream = ftp_stream.into_insecure().unwrap(); -//! // Do all public stuff //! assert!(ftp_stream.quit().is_ok()); //! ``` //! @@ -109,17 +104,14 @@ //! Let's quickly see in the example how it works //! //! ```rust -//! extern crate suppaftp; -//! -//! use suppaftp::FtpStream; +//! use suppaftp::{AsyncFtpStream, AsyncNativeTlsConnector}; //! use suppaftp::async_native_tls::{TlsConnector, TlsStream}; //! -//! let ftp_stream = FtpStream::connect("test.rebex.net:21").await.unwrap(); +//! let ftp_stream = AsyncFtpStream::connect("test.rebex.net:21").await.unwrap(); //! // Switch to the secure mode -//! let mut ftp_stream = ftp_stream.into_secure(TlsConnector::new().into(), "test.rebex.net").await.unwrap(); +//! let mut ftp_stream = ftp_stream.into_secure(AsyncNativeTlsConnector::from(TlsConnector::new()), "test.rebex.net").await.unwrap(); //! ftp_stream.login("demo", "password").await.unwrap(); //! // Do other secret stuff -//! // Do all public stuff //! assert!(ftp_stream.quit().await.is_ok()); //! ``` //! @@ -139,12 +131,11 @@ extern crate lazy_regex; extern crate log; // -- private -#[cfg(any(feature = "async", feature = "async-secure"))] +#[cfg(feature = "async")] mod async_ftp; pub(crate) mod command; mod regex; mod status; -#[cfg(any(test, not(any(feature = "async", feature = "async-secure"))))] mod sync_ftp; // -- public @@ -160,22 +151,50 @@ pub extern crate rustls_crate as rustls; #[cfg(feature = "async-native-tls")] pub extern crate async_native_tls_crate as async_native_tls; -// -- export async -#[cfg(any(feature = "async", feature = "async-secure"))] -pub use async_ftp::FtpStream; -// -- export sync -#[cfg(not(any(feature = "async", feature = "async-secure")))] -pub use sync_ftp::FtpStream; -// -- export secure -#[cfg(all(feature = "secure", not(feature = "async-secure")))] -pub use sync_ftp::TlsConnector; -// -- export async secure -#[cfg(all(feature = "async-secure", not(feature = "secure")))] -pub use async_ftp::TlsConnector; // -- export (common) pub use status::Status; pub use types::{FtpError, FtpResult, Mode}; +// -- export sync +use sync_ftp::{ImplFtpStream, NoTlsStream}; +pub type FtpStream = ImplFtpStream; +// -- export secure (native-tls) +#[cfg(feature = "native-tls")] +pub use sync_ftp::NativeTlsConnector; +#[cfg(feature = "native-tls")] +use sync_ftp::NativeTlsStream; +#[cfg(feature = "native-tls")] +pub type NativeTlsFtpStream = ImplFtpStream; +// -- export secure (rustls) +#[cfg(feature = "rustls")] +pub use sync_ftp::RustlsConnector; +#[cfg(feature = "rustls")] +use sync_ftp::RustlsStream; +#[cfg(feature = "rustls")] +pub type RustlsFtpStream = ImplFtpStream; + +// -- export async +#[cfg(feature = "async")] +use async_ftp::AsyncNoTlsStream; +#[cfg(feature = "async")] +use async_ftp::ImplAsyncFtpStream; +#[cfg(feature = "async")] +pub type AsyncFtpStream = ImplAsyncFtpStream; +// -- export async secure (native-tls) +#[cfg(feature = "async-native-tls")] +pub use async_ftp::AsyncNativeTlsConnector; +#[cfg(feature = "async-native-tls")] +use async_ftp::AsyncNativeTlsStream; +#[cfg(feature = "async-native-tls")] +pub type AsyncNativeTlsFtpStream = ImplAsyncFtpStream; +// -- export async secure (rustls) +#[cfg(feature = "async-rustls")] +pub use async_ftp::AsyncRustlsConnector; +#[cfg(feature = "async-rustls")] +use async_ftp::AsyncRustlsStream; +#[cfg(feature = "async-rustls")] +pub type AsyncRustlsFtpStream = ImplAsyncFtpStream; + // -- test logging #[cfg(test)] pub fn log_init() { diff --git a/suppaftp/src/sync_ftp/data_stream.rs b/suppaftp/src/sync_ftp/data_stream.rs index 0f03344..05483b8 100644 --- a/suppaftp/src/sync_ftp/data_stream.rs +++ b/suppaftp/src/sync_ftp/data_stream.rs @@ -2,7 +2,6 @@ //! //! This module exposes the data stream where bytes must be written to/read from -#[cfg(feature = "secure")] use super::tls::TlsStream; use std::io::{Read, Result, Write}; @@ -10,14 +9,19 @@ use std::net::TcpStream; /// Data Stream used for communications. It can be both of type Tcp in case of plain communication or Ssl in case of FTPS #[derive(Debug)] -pub enum DataStream { +pub enum DataStream +where + T: TlsStream, +{ Tcp(TcpStream), - #[cfg(feature = "secure")] - Ssl(Box), + Ssl(Box), } #[cfg(feature = "secure")] -impl DataStream { +impl DataStream +where + T: TlsStream, +{ /// Unwrap the stream into TcpStream. This method is only used in secure connection. pub fn into_tcp_stream(self) -> TcpStream { match self { @@ -27,12 +31,14 @@ impl DataStream { } } -impl DataStream { +impl DataStream +where + T: TlsStream, +{ /// Returns a reference to the underlying TcpStream. pub fn get_ref(&self) -> &TcpStream { match self { DataStream::Tcp(ref stream) => stream, - #[cfg(feature = "secure")] DataStream::Ssl(ref stream) => stream.get_ref(), } } @@ -40,29 +46,35 @@ impl DataStream { // -- sync -impl Read for DataStream { +impl Read for DataStream +where + T: TlsStream, +{ fn read(&mut self, buf: &mut [u8]) -> Result { match self { DataStream::Tcp(ref mut stream) => stream.read(buf), - #[cfg(feature = "secure")] DataStream::Ssl(ref mut stream) => stream.mut_ref().read(buf), } } } -impl Write for DataStream { +impl Write for DataStream +where + T: TlsStream, +{ fn write(&mut self, buf: &[u8]) -> Result { match self { DataStream::Tcp(ref mut stream) => stream.write(buf), - #[cfg(feature = "secure")] DataStream::Ssl(ref mut stream) => stream.mut_ref().write(buf), } } - fn flush(&mut self) -> Result<()> { + fn flush(&mut self) -> Result<()> + where + T: TlsStream, + { match self { DataStream::Tcp(ref mut stream) => stream.flush(), - #[cfg(feature = "secure")] DataStream::Ssl(ref mut stream) => stream.mut_ref().flush(), } } diff --git a/suppaftp/src/sync_ftp/mod.rs b/suppaftp/src/sync_ftp/mod.rs index 70ad276..cc46ef3 100644 --- a/suppaftp/src/sync_ftp/mod.rs +++ b/suppaftp/src/sync_ftp/mod.rs @@ -12,45 +12,60 @@ use crate::command::Command; #[cfg(feature = "secure")] use crate::command::ProtectionLevel; use data_stream::DataStream; - -#[cfg(feature = "secure")] use tls::TlsStream; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; - +use std::fmt::Debug; use std::io::{copy, BufRead, BufReader, Cursor, Read, Write}; +#[cfg(not(feature = "secure"))] +use std::marker::PhantomData; use std::net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream, ToSocketAddrs}; +// export +pub use tls::NoTlsStream; #[cfg(feature = "secure")] pub use tls::TlsConnector; +#[cfg(feature = "native-tls")] +pub use tls::{NativeTlsConnector, NativeTlsStream}; +#[cfg(feature = "rustls")] +pub use tls::{RustlsConnector, RustlsStream}; /// Stream to interface with the FTP server. This interface is only for the command stream. #[derive(Debug)] -pub struct FtpStream { - reader: BufReader, +pub struct ImplFtpStream +where + T: TlsStream, +{ + reader: BufReader>, mode: Mode, nat_workaround: bool, welcome_msg: Option, + #[cfg(not(feature = "secure"))] + marker: PhantomData, #[cfg(feature = "secure")] - tls_ctx: Option, + tls_ctx: Option>>, #[cfg(feature = "secure")] domain: Option, } -impl FtpStream { - /// Creates an FTP Stream. - #[cfg(not(feature = "secure"))] +impl ImplFtpStream +where + T: TlsStream, +{ + #[cfg(feature = "secure")] pub fn connect(addr: A) -> FtpResult { debug!("Connecting to server"); TcpStream::connect(addr) .map_err(FtpError::ConnectionError) .and_then(|stream| { debug!("Established connection with server"); - let mut ftp_stream = FtpStream { + let mut ftp_stream = Self { reader: BufReader::new(DataStream::Tcp(stream)), mode: Mode::Passive, nat_workaround: false, welcome_msg: None, + tls_ctx: None, + domain: None, }; debug!("Reading server response..."); match ftp_stream.read_response(Status::Ready) { @@ -65,21 +80,20 @@ impl FtpStream { }) } + #[cfg(not(feature = "secure"))] /// Creates an FTP Stream. - #[cfg(feature = "secure")] pub fn connect(addr: A) -> FtpResult { debug!("Connecting to server"); TcpStream::connect(addr) .map_err(FtpError::ConnectionError) .and_then(|stream| { debug!("Established connection with server"); - let mut ftp_stream = FtpStream { + let mut ftp_stream = Self { reader: BufReader::new(DataStream::Tcp(stream)), mode: Mode::Passive, nat_workaround: false, welcome_msg: None, - tls_ctx: None, - domain: None, + marker: PhantomData {}, }; debug!("Reading server response..."); match ftp_stream.read_response(Status::Ready) { @@ -93,7 +107,6 @@ impl FtpStream { } }) } - /// Enable active mode for data channel pub fn active_mode(mut self) -> Self { self.mode = Mode::Active; @@ -117,18 +130,22 @@ impl FtpStream { /// ## Example /// /// ```rust,no_run - /// use suppaftp::FtpStream; + /// use suppaftp::{NativeTlsFtpStream, NativeTlsConnector}; /// use suppaftp::native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; /// /// // Create a TlsConnector /// // NOTE: For custom options see - /// let mut ctx = TlsConnector::new().unwrap(); - /// let mut ftp_stream = FtpStream::connect("127.0.0.1:21").unwrap(); + /// let mut ctx = NativeTlsConnector::from(TlsConnector::new().unwrap()); + /// let mut ftp_stream = NativeTlsFtpStream::connect("127.0.0.1:21").unwrap(); /// let mut ftp_stream = ftp_stream.into_secure(ctx, "localhost").unwrap(); /// ``` #[cfg(feature = "secure")] - pub fn into_secure(mut self, tls_connector: TlsConnector, domain: &str) -> FtpResult { + pub fn into_secure( + mut self, + tls_connector: impl TlsConnector + 'static, + domain: &str, + ) -> FtpResult { // Ask the server to start securing data. debug!("Initializing TLS auth"); self.perform(Command::Auth)?; @@ -138,11 +155,11 @@ impl FtpStream { .connect(domain, self.reader.into_inner().into_tcp_stream()) .map_err(|e| FtpError::SecureError(format!("{e}")))?; debug!("TLS Steam OK"); - let mut secured_ftp_tream = FtpStream { + let mut secured_ftp_tream = Self { reader: BufReader::new(DataStream::Ssl(Box::new(stream))), mode: self.mode, nat_workaround: self.nat_workaround, - tls_ctx: Some(tls_connector), + tls_ctx: Some(Box::new(tls_connector)), domain: Some(String::from(domain)), welcome_msg: self.welcome_msg, }; @@ -175,7 +192,7 @@ impl FtpStream { #[cfg(all(feature = "secure", feature = "deprecated"))] pub fn connect_secure_implicit( addr: A, - tls_connector: TlsConnector, + tls_connector: impl TlsConnector + 'static, domain: &str, ) -> FtpResult { debug!("Connecting to server (secure)"); @@ -183,7 +200,7 @@ impl FtpStream { .map_err(FtpError::ConnectionError) .map(|stream| { debug!("Established connection with server"); - FtpStream { + Self { reader: BufReader::new(DataStream::Tcp(stream)), mode: Mode::Passive, nat_workaround: false, @@ -198,11 +215,11 @@ impl FtpStream { .connect(domain, stream.reader.into_inner().into_tcp_stream()) .map_err(|e| FtpError::SecureError(format!("{e}")))?; debug!("TLS Steam OK"); - let mut stream = FtpStream { + let mut stream = Self { reader: BufReader::new(DataStream::Ssl(Box::new(stream))), mode: Mode::Passive, nat_workaround: false, - tls_ctx: Some(tls_connector), + tls_ctx: Some(Box::new(tls_connector)), domain: Some(String::from(domain)), welcome_msg: None, }; @@ -379,14 +396,15 @@ impl FtpStream { /// }).is_ok()); /// # assert!(conn.rm("retr.txt").is_ok()); /// ``` - pub fn retr(&mut self, file_name: &str, mut reader: F) -> FtpResult + pub fn retr(&mut self, file_name: &str, mut reader: F) -> FtpResult where - F: FnMut(&mut dyn Read) -> FtpResult, + F: FnMut(&mut dyn Read) -> FtpResult, { match self.retr_as_stream(file_name) { Ok(mut stream) => { let result = reader(&mut stream)?; - self.finalize_retr_stream(stream).map(|_| result) + self.finalize_retr_stream(stream)?; + Ok(result) } Err(err) => Err(err), } @@ -423,7 +441,7 @@ impl FtpStream { /// The reader returned should be dropped. /// Also you will have to read the response to make sure it has the correct value. /// Once file has been read, call `finalize_retr_stream()` - pub fn retr_as_stream>(&mut self, file_name: S) -> FtpResult { + pub fn retr_as_stream>(&mut self, file_name: S) -> FtpResult> { debug!("Retrieving '{}'", file_name.as_ref()); let data_stream = self.data_command(Command::Retr(file_name.as_ref().to_string()))?; self.read_response_in(&[Status::AboutToSend, Status::AlreadyOpen])?; @@ -472,7 +490,7 @@ impl FtpStream { /// The returned stream must be then correctly manipulated to write the content of the source file to the remote destination /// The stream must be then correctly dropped. /// Once you've finished the write, YOU MUST CALL THIS METHOD: `finalize_put_stream` - pub fn put_with_stream>(&mut self, filename: S) -> FtpResult { + pub fn put_with_stream>(&mut self, filename: S) -> FtpResult> { debug!("Put file {}", filename.as_ref()); let stream = self.data_command(Command::Store(filename.as_ref().to_string()))?; self.read_response_in(&[Status::AlreadyOpen, Status::AboutToSend])?; @@ -494,7 +512,7 @@ impl FtpStream { /// Open specified file for appending data. Returns the stream to append data to specified file. /// Once you've finished the write, YOU MUST CALL THIS METHOD: `finalize_put_stream` - pub fn append_with_stream>(&mut self, filename: S) -> FtpResult { + pub fn append_with_stream>(&mut self, filename: S) -> FtpResult> { debug!("Appending to file {}", filename.as_ref()); let stream = self.data_command(Command::Appe(filename.as_ref().to_string()))?; self.read_response_in(&[Status::AlreadyOpen, Status::AboutToSend])?; @@ -633,7 +651,7 @@ impl FtpStream { // -- private /// Retrieve stream "message" - fn get_lines_from_stream(data_stream: &mut BufReader) -> FtpResult> { + fn get_lines_from_stream(data_stream: &mut BufReader>) -> FtpResult> { let mut lines: Vec = Vec::new(); loop { @@ -729,7 +747,7 @@ impl FtpStream { } /// Execute command which send data back in a separate stream - fn data_command(&mut self, cmd: Command) -> FtpResult { + fn data_command(&mut self, cmd: Command) -> FtpResult> { let stream = match self.mode { Mode::Active => self .active() @@ -755,7 +773,6 @@ impl FtpStream { match self.tls_ctx { Some(ref tls_ctx) => tls_ctx .connect(self.domain.as_ref().unwrap(), stream) - .map(TlsStream::from) .map(|x| DataStream::Ssl(Box::new(x))) .map_err(|e| FtpError::SecureError(format!("{e}"))), None => Ok(DataStream::Tcp(stream)), @@ -772,7 +789,6 @@ impl FtpStream { let ip = match self.reader.get_mut() { DataStream::Tcp(stream) => stream.local_addr().unwrap().ip(), - #[cfg(feature = "secure")] DataStream::Ssl(stream) => stream.get_ref().local_addr().unwrap().ip(), }; @@ -865,12 +881,13 @@ impl FtpStream { mod test { use super::*; + use crate::FtpStream; #[cfg(feature = "with-containers")] use crate::types::FormatControl; #[cfg(feature = "native-tls")] - use native_tls::TlsConnector as NativeTlsConnector; + use native_tls::TlsConnector; #[cfg(any(feature = "with-containers", feature = "secure"))] use pretty_assertions::assert_eq; #[cfg(feature = "with-containers")] @@ -895,9 +912,12 @@ mod test { fn should_connect_ssl_native_tls() { crate::log_init(); use std::time::Duration; - let ftp_stream = FtpStream::connect("test.rebex.net:21").unwrap(); + let ftp_stream = crate::NativeTlsFtpStream::connect("test.rebex.net:21").unwrap(); let mut ftp_stream = ftp_stream - .into_secure(NativeTlsConnector::new().unwrap().into(), "test.rebex.net") + .into_secure( + NativeTlsConnector::from(TlsConnector::new().unwrap()), + "test.rebex.net", + ) .unwrap(); // Set timeout (to test ref to ssl) assert!(ftp_stream @@ -917,9 +937,12 @@ mod test { #[cfg(feature = "native-tls")] fn should_work_after_clear_command_channel_native_tls() { crate::log_init(); - let mut ftp_stream = FtpStream::connect("test.rebex.net:21") + let mut ftp_stream = crate::NativeTlsFtpStream::connect("test.rebex.net:21") .unwrap() - .into_secure(NativeTlsConnector::new().unwrap().into(), "test.rebex.net") + .into_secure( + NativeTlsConnector::from(TlsConnector::new().unwrap()), + "test.rebex.net", + ) .unwrap() .clear_command_channel() .unwrap(); @@ -937,9 +960,9 @@ mod test { fn should_connect_ssl_implicit_native_tls() { use std::time::Duration; crate::log_init(); - let mut ftp_stream = FtpStream::connect_secure_implicit( + let mut ftp_stream = crate::NativeTlsFtpStream::connect_secure_implicit( "test.rebex.net:990", - NativeTlsConnector::new().unwrap().into(), + NativeTlsConnector::from(TlsConnector::new().unwrap()), "test.rebex.net", ) .unwrap(); @@ -960,11 +983,16 @@ mod test { #[serial] #[cfg(feature = "rustls")] fn should_connect_ssl_rustls() { + use super::tls::RustlsConnector; + crate::log_init(); let config = Arc::new(rustls_config()); - let mut ftp_stream = FtpStream::connect("ftp.uni-bayreuth.de:21") + let mut ftp_stream = crate::RustlsFtpStream::connect("ftp.uni-bayreuth.de:21") .unwrap() - .into_secure(Arc::clone(&config).into(), "ftp.uni-bayreuth.de") + .into_secure( + RustlsConnector::from(Arc::clone(&config)), + "ftp.uni-bayreuth.de", + ) .unwrap(); // Quit assert!(ftp_stream.quit().is_ok()); diff --git a/suppaftp/src/sync_ftp/tls.rs b/suppaftp/src/sync_ftp/tls.rs index 96b9bb4..47de45c 100644 --- a/suppaftp/src/sync_ftp/tls.rs +++ b/suppaftp/src/sync_ftp/tls.rs @@ -2,12 +2,56 @@ //! //! Tls wrappers +use std::io::Write; +use std::net::TcpStream; +use std::{fmt::Debug, io::Read}; + #[cfg(feature = "native-tls")] mod native_tls; +use crate::FtpResult; + #[cfg(feature = "native-tls")] -pub use self::native_tls::{TlsConnector, TlsStream}; +pub use self::native_tls::{NativeTlsConnector, NativeTlsStream}; #[cfg(feature = "rustls")] mod rustls; #[cfg(feature = "rustls")] -pub use self::rustls::{TlsConnector, TlsStream}; +pub use self::rustls::{RustlsConnector, RustlsStream}; + +pub trait TlsConnector: Debug { + type Stream: TlsStream; + + fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult; +} + +pub trait TlsStream: Debug { + type InnerStream: Read + Write; + + /// Get underlying tcp stream + fn tcp_stream(self) -> TcpStream; + + /// Get ref to underlying tcp stream + fn get_ref(&self) -> &TcpStream; + + /// Get mutable reference to tls stream + fn mut_ref(&mut self) -> &mut Self::InnerStream; +} + +#[derive(Debug)] +pub struct NoTlsStream; + +impl TlsStream for NoTlsStream { + type InnerStream = TcpStream; + + fn tcp_stream(self) -> TcpStream { + panic!() + } + + fn get_ref(&self) -> &TcpStream { + panic!() + } + + fn mut_ref(&mut self) -> &mut Self::InnerStream { + panic!() + } +} diff --git a/suppaftp/src/sync_ftp/tls/native_tls.rs b/suppaftp/src/sync_ftp/tls/native_tls.rs index 6ee5cc0..e9d5724 100644 --- a/suppaftp/src/sync_ftp/tls/native_tls.rs +++ b/suppaftp/src/sync_ftp/tls/native_tls.rs @@ -2,31 +2,33 @@ //! //! Native tls implementation of TLS types -use native_tls::{ - HandshakeError, TlsConnector as NativeTlsConnector, TlsStream as NativeTlsStream, -}; +use native_tls_crate::{TlsConnector, TlsStream}; use std::io::Write; use std::net::TcpStream; +use super::{TlsConnector as TlsConnectorTrait, TlsStream as TlsStreamTrait}; +use crate::{FtpError, FtpResult}; + #[derive(Debug)] /// A Wrapper for the tls connector -pub struct TlsConnector { - connector: NativeTlsConnector, +pub struct NativeTlsConnector { + connector: TlsConnector, } -impl From for TlsConnector { - fn from(connector: NativeTlsConnector) -> Self { +impl From for NativeTlsConnector { + fn from(connector: TlsConnector) -> Self { Self { connector } } } -impl TlsConnector { - pub fn connect( - &self, - domain: &str, - stream: TcpStream, - ) -> Result> { - self.connector.connect(domain, stream).map(TlsStream::from) +impl TlsConnectorTrait for NativeTlsConnector { + type Stream = NativeTlsStream; + + fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult { + self.connector + .connect(domain, stream) + .map(NativeTlsStream::from) + .map_err(|e| FtpError::SecureError(e.to_string())) } } @@ -35,14 +37,16 @@ impl TlsConnector { /// Tls stream wrapper. This type is a garbage data type used to impl the drop trait for the tls stream. /// This allows me to keep returning `Read` and `Write` traits in stream methods #[derive(Debug)] -pub struct TlsStream { - stream: NativeTlsStream, +pub struct NativeTlsStream { + stream: TlsStream, ssl_shutdown: bool, } -impl TlsStream { +impl TlsStreamTrait for NativeTlsStream { + type InnerStream = TlsStream; + /// Get underlying tcp stream - pub(crate) fn tcp_stream(mut self) -> TcpStream { + fn tcp_stream(mut self) -> TcpStream { let mut stream = self.stream.get_ref().try_clone().unwrap(); // Don't perform shutdown later self.ssl_shutdown = false; @@ -55,18 +59,18 @@ impl TlsStream { } /// Get ref to underlying tcp stream - pub(crate) fn get_ref(&self) -> &TcpStream { + fn get_ref(&self) -> &TcpStream { self.stream.get_ref() } /// Get mutable reference to tls stream - pub(crate) fn mut_ref(&mut self) -> &mut NativeTlsStream { + fn mut_ref(&mut self) -> &mut TlsStream { &mut self.stream } } -impl From> for TlsStream { - fn from(stream: NativeTlsStream) -> Self { +impl From> for NativeTlsStream { + fn from(stream: TlsStream) -> Self { Self { stream, ssl_shutdown: true, @@ -74,7 +78,7 @@ impl From> for TlsStream { } } -impl Drop for TlsStream { +impl Drop for NativeTlsStream { fn drop(&mut self) { if self.ssl_shutdown { if let Err(err) = self.stream.shutdown() { diff --git a/suppaftp/src/sync_ftp/tls/rustls.rs b/suppaftp/src/sync_ftp/tls/rustls.rs index 3f7f00b..08be499 100644 --- a/suppaftp/src/sync_ftp/tls/rustls.rs +++ b/suppaftp/src/sync_ftp/tls/rustls.rs @@ -9,31 +9,35 @@ use std::io::Write; use std::net::TcpStream; use std::sync::Arc; +use super::{TlsConnector, TlsStream}; + /// A Wrapper for the tls connector -pub struct TlsConnector { +pub struct RustlsConnector { connector: Arc, } -impl std::fmt::Debug for TlsConnector { +impl std::fmt::Debug for RustlsConnector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "") } } -impl From> for TlsConnector { +impl From> for RustlsConnector { fn from(connector: Arc) -> Self { Self { connector } } } -impl TlsConnector { - pub fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult { +impl TlsConnector for RustlsConnector { + type Stream = RustlsStream; + + fn connect(&self, domain: &str, stream: TcpStream) -> FtpResult { let server_name = ServerName::try_from(domain).map_err(|e| FtpError::SecureError(e.to_string()))?; let connection = ClientConnection::new(Arc::clone(&self.connector), server_name) .map_err(|e| FtpError::SecureError(e.to_string()))?; let stream = StreamOwned::new(connection, stream); - Ok(TlsStream { stream }) + Ok(RustlsStream { stream }) } } @@ -42,13 +46,15 @@ impl TlsConnector { /// Tls stream wrapper. This type is a garbage data type used to impl the drop trait for the tls stream. /// This allows me to keep returning `Read` and `Write` traits in stream methods #[derive(Debug)] -pub struct TlsStream { +pub struct RustlsStream { stream: StreamOwned, } -impl TlsStream { +impl TlsStream for RustlsStream { + type InnerStream = StreamOwned; + /// Get underlying tcp stream - pub(crate) fn tcp_stream(self) -> TcpStream { + fn tcp_stream(self) -> TcpStream { let mut stream = self.get_ref().try_clone().unwrap(); // flush stream (otherwise can cause bad chars on channel) if let Err(err) = stream.flush() { @@ -59,12 +65,12 @@ impl TlsStream { } /// Get ref to underlying tcp stream - pub(crate) fn get_ref(&self) -> &TcpStream { + fn get_ref(&self) -> &TcpStream { self.stream.get_ref() } /// Get mutable reference to tls stream - pub(crate) fn mut_ref(&mut self) -> &mut StreamOwned { + fn mut_ref(&mut self) -> &mut Self::InnerStream { &mut self.stream } }