From a32ba7209403c486c82af8c0c7408f2c52fc8551 Mon Sep 17 00:00:00 2001
From: veeso
Date: Mon, 14 Oct 2024 14:43:00 +0200
Subject: [PATCH 1/3] fix: Passive mode with custom provided TcpStream
---
.github/workflows/cargo.yml | 39 ++++--------------
.github/workflows/cli.yml | 7 +---
.github/workflows/coverage.yml | 7 +---
CHANGELOG.md | 7 ++++
Cargo.toml | 2 +-
README.md | 2 +-
suppaftp/Cargo.toml | 2 +-
suppaftp/src/async_ftp/mod.rs | 59 +++++++++++++++++++++++---
suppaftp/src/sync_ftp/mod.rs | 64 ++++++++++++++++++++++-------
suppaftp/src/sync_ftp/tls/rustls.rs | 7 ++--
10 files changed, 129 insertions(+), 67 deletions(-)
diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml
index ca39ff5..fade85e 100644
--- a/.github/workflows/cargo.yml
+++ b/.github/workflows/cargo.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - uses: actions-rs/toolchain@v1
+ - uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
override: true
@@ -18,42 +18,19 @@ jobs:
- name: Setup containers
run: docker-compose -f "tests/docker-compose.yml" up -d --build
- name: Build simple
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --package suppaftp
+ run: cargo build --package suppaftp
- name: Build secure (native-tls)
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --features native-tls,deprecated --package suppaftp
+ run: cargo build --features native-tls,deprecated --package suppaftp
- name: Build secure (rustls)
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --features rustls,deprecated --package suppaftp
+ run: cargo build --features rustls,deprecated --package suppaftp
- name: Build async
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --features async,deprecated --package suppaftp
+ run: cargo build --features async,deprecated --package suppaftp
- name: Build async-native-tls
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --features async-native-tls,deprecated --package suppaftp
+ run: cargo build --features async-native-tls,deprecated --package suppaftp
- name: Build all features
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --features deprecated,native-tls,rustls,async-native-tls,async-rustls --package suppaftp
+ run: cargo build --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,native-tls,async-native-tls,async-rustls,with-containers --no-fail-fast
- env:
- RUST_LOG: trace
+ run: cargo test --package suppaftp --no-default-features --features rustls,native-tls,async-native-tls,async-rustls,with-containers --no-fail-fast
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index 471f77c..a6c9718 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -13,16 +13,13 @@ jobs:
working-directory: ./suppaftp-cli
steps:
- uses: actions/checkout@v2
- - uses: actions-rs/toolchain@v1
+ - uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build
- uses: actions-rs/cargo@v1
- with:
- command: build
- args: --package suppaftp-cli
+ run: cargo build --package suppaftp-cli
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 7cf1ffc..2fc0cd5 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -14,15 +14,12 @@ jobs:
- name: Setup containers
run: docker-compose -f "tests/docker-compose.yml" up -d --build
- name: Setup nightly toolchain
- uses: actions-rs/toolchain@v1
+ uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
override: true
- name: Run tests (nightly)
- uses: actions-rs/cargo@v1
- with:
- command: test
- args: --lib --no-default-features --features native-tls,deprecated,async-native-tls,with-containers --no-fail-fast
+ run: cargo test --package suppaftp --no-default-features --features native-tls,deprecated,async-native-tls,with-containers --no-fail-fast
env:
RUST_LOG: trace
CARGO_INCREMENTAL: "0"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c36e95c..ce83279 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
- [Changelog](#changelog)
+ - [6.0.2](#602)
- [6.0.1](#601)
- [6.0.0](#600)
- [5.4.0](#540)
@@ -34,6 +35,12 @@
---
+## 6.0.2
+
+Released on 14/10/2024
+
+- [Issue 89](https://github.com/veeso/suppaftp/issues/89): added new `FtpStream::passive_stream_builder` to provide a function to build the Passive mode `TcpStream` with a custom builder. This is useful if you need to use some proxy.
+
## 6.0.1
Released on 24/05/2024
diff --git a/Cargo.toml b/Cargo.toml
index 316a348..54739b6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ members = ["suppaftp", "suppaftp-cli"]
resolver = "2"
[workspace.package]
-version = "6.0.1"
+version = "6.0.2"
edition = "2021"
authors = [
"Christian Visintin ",
diff --git a/README.md b/README.md
index 0c0d1cb..ec8b883 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
Developed by veeso and Matt McCoy
-Current version: 6.0.1 (24/05/2024)
+Current version: 6.0.2 (14/10/2024)
Pin> + Send>>;
+
/// Stream to interface with the FTP server. This interface is only for the command stream.
pub struct ImplAsyncFtpStream
where
@@ -45,6 +53,7 @@ where
nat_workaround: bool,
welcome_msg: Option,
active_timeout: Duration,
+ passive_stream_builder: Box,
#[cfg(not(feature = "async-secure"))]
marker: PhantomData,
#[cfg(feature = "async-secure")]
@@ -85,6 +94,7 @@ where
marker: PhantomData {},
mode: Mode::Passive,
nat_workaround: false,
+ passive_stream_builder: Self::default_passive_stream_builder(),
welcome_msg: None,
#[cfg(feature = "async-secure")]
tls_ctx: None,
@@ -143,6 +153,7 @@ where
reader: BufReader::new(DataStream::Ssl(Box::new(stream))),
mode: self.mode,
nat_workaround: self.nat_workaround,
+ passive_stream_builder: self.passive_stream_builder,
tls_ctx: Some(Box::new(tls_connector)),
domain: Some(String::from(domain)),
welcome_msg: self.welcome_msg,
@@ -197,6 +208,7 @@ where
mode: Mode::Passive,
nat_workaround: false,
welcome_msg: None,
+ passive_stream_builder: Self::default_passive_stream_builder(),
tls_ctx: None,
domain: None,
active_timeout: Duration::from_secs(60),
@@ -213,6 +225,7 @@ where
reader: BufReader::new(DataStream::Ssl(stream.into())),
mode: Mode::Passive,
nat_workaround: false,
+ passive_stream_builder: Self::default_passive_stream_builder(),
tls_ctx: Some(Box::new(tls_connector)),
domain: Some(String::from(domain)),
welcome_msg: None,
@@ -238,6 +251,18 @@ where
self
}
+ /// Set a custom [`StreamBuilder`] for passive mode.
+ ///
+ /// The stream builder is a function that takes a `SocketAddr` and returns a `TcpStream` and it's used
+ /// to create the [`TcpStream`] for the data connection in passive mode.
+ pub fn passive_stream_builder(mut self, stream_builder: F) -> Self
+ where
+ F: Fn(SocketAddr) -> Pin> + Send>> + 'static,
+ {
+ self.passive_stream_builder = Box::new(stream_builder);
+ self
+ }
+
/// Returns welcome message retrieved from server (if available)
pub fn get_welcome_msg(&self) -> Option<&str> {
self.welcome_msg.as_deref()
@@ -767,16 +792,12 @@ where
Mode::ExtendedPassive => {
let addr = self.epsv().await?;
self.perform(cmd).await?;
- TcpStream::connect(addr)
- .await
- .map_err(FtpError::ConnectionError)?
+ (self.passive_stream_builder)(addr).await?
}
Mode::Passive => {
let addr = self.pasv().await?;
self.perform(cmd).await?;
- TcpStream::connect(addr)
- .await
- .map_err(FtpError::ConnectionError)?
+ (self.passive_stream_builder)(addr).await?
}
};
@@ -1002,6 +1023,16 @@ where
self.finalize_retr_stream(data_stream).await?;
lines
}
+
+ fn default_passive_stream_builder() -> Box {
+ Box::new(|address| {
+ Box::pin(async move {
+ TcpStream::connect(address)
+ .await
+ .map_err(FtpError::ConnectionError)
+ })
+ })
+ }
}
#[cfg(test)]
@@ -1400,6 +1431,22 @@ mod test {
finalize_stream(stream).await;
}
+ #[async_attributes::test]
+ async fn test_should_set_passive_stream_builder() {
+ crate::log_init();
+ let _ftp_stream = AsyncFtpStream::connect("test.rebex.net:21")
+ .await
+ .unwrap()
+ .passive_stream_builder(|addr| {
+ Box::pin(async move {
+ println!("Connecting to {}", addr);
+ TcpStream::connect(addr)
+ .await
+ .map_err(FtpError::ConnectionError)
+ })
+ });
+ }
+
// -- test utils
#[cfg(feature = "with-containers")]
diff --git a/suppaftp/src/sync_ftp/mod.rs b/suppaftp/src/sync_ftp/mod.rs
index 8dca29b..e5ba5fb 100644
--- a/suppaftp/src/sync_ftp/mod.rs
+++ b/suppaftp/src/sync_ftp/mod.rs
@@ -5,7 +5,6 @@
mod data_stream;
mod tls;
-use std::fmt::Debug;
use std::io::{copy, BufRead, BufReader, Cursor, Read, Write};
#[cfg(not(feature = "secure"))]
use std::marker::PhantomData;
@@ -32,8 +31,12 @@ use crate::command::Command;
use crate::command::ProtectionLevel;
use crate::types::Features;
+/// A function that creates a new stream for the data connection in passive mode.
+///
+/// It takes a [`SocketAddr`] and returns a [`TcpStream`].
+pub type PassiveStreamBuilder = dyn Fn(SocketAddr) -> FtpResult + Send;
+
/// Stream to interface with the FTP server. This interface is only for the command stream.
-#[derive(Debug)]
pub struct ImplFtpStream
where
T: TlsStream,
@@ -43,6 +46,7 @@ where
nat_workaround: bool,
welcome_msg: Option,
active_timeout: Duration,
+ passive_stream_builder: Box,
#[cfg(not(feature = "secure"))]
marker: PhantomData,
#[cfg(feature = "secure")]
@@ -80,6 +84,7 @@ where
nat_workaround: false,
welcome_msg: None,
active_timeout: Duration::from_secs(60),
+ passive_stream_builder: Self::default_passive_stream_builder(),
#[cfg(feature = "secure")]
tls_ctx: None,
#[cfg(feature = "secure")]
@@ -106,6 +111,18 @@ where
self
}
+ /// Set a custom [`StreamBuilder`] for passive mode.
+ ///
+ /// The stream builder is a function that takes a `SocketAddr` and returns a `TcpStream` and it's used
+ /// to create the [`TcpStream`] for the data connection in passive mode.
+ pub fn passive_stream_builder(mut self, stream_builder: F) -> Self
+ where
+ F: Fn(SocketAddr) -> FtpResult + Send + 'static,
+ {
+ self.passive_stream_builder = Box::new(stream_builder);
+ self
+ }
+
/// Set the data channel transfer mode
pub fn set_mode(&mut self, mode: Mode) {
debug!("Changed mode to {:?}", mode);
@@ -153,6 +170,7 @@ where
reader: BufReader::new(DataStream::Ssl(Box::new(stream))),
mode: self.mode,
nat_workaround: self.nat_workaround,
+ passive_stream_builder: self.passive_stream_builder,
tls_ctx: Some(Box::new(tls_connector)),
domain: Some(String::from(domain)),
welcome_msg: self.welcome_msg,
@@ -169,7 +187,7 @@ where
/// Connect to remote ftps server using IMPLICIT secure connection.
///
- /// > Warning: mind that implicit ftps should be considered deprecated, if you can use explicit mode with `into_secure()`
+ /// > Warning: mind that implicit ftps should be considered deprecated, if you can use explicit mode with [`ImplFtpStream::into_secure`]
///
///
/// ## Example
@@ -179,8 +197,8 @@ where
/// use suppaftp::native_tls::{TlsConnector, TlsStream};
/// use std::path::Path;
///
- /// // Create a TlsConnector
- /// // NOTE: For custom options see
+ /// //Create a TlsConnector
+ /// //NOTE: For custom options see
/// let mut ctx = TlsConnector::new().unwrap();
/// let mut ftp_stream = FtpStream::connect_secure_implicit("127.0.0.1:990", ctx, "localhost").unwrap();
/// ```
@@ -200,6 +218,7 @@ where
reader: BufReader::new(DataStream::Tcp(stream)),
mode: Mode::Passive,
nat_workaround: false,
+ passive_stream_builder: Self::default_passive_stream_builder(),
welcome_msg: None,
tls_ctx: None,
domain: None,
@@ -217,6 +236,7 @@ where
mode: Mode::Passive,
nat_workaround: false,
tls_ctx: Some(Box::new(tls_connector)),
+ passive_stream_builder: Self::default_passive_stream_builder(),
domain: Some(String::from(domain)),
welcome_msg: None,
active_timeout: Duration::from_secs(60),
@@ -239,7 +259,7 @@ where
self.welcome_msg.as_deref()
}
- /// Returns a reference to the underlying TcpStream.
+ /// Returns a reference to the underlying [`TcpStream`].
///
/// Example:
/// ```no_run
@@ -439,7 +459,7 @@ where
/// This method is a more complicated way to retrieve a file.
/// 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()`
+ /// Once file has been read, call [`ImplFtpStream::finalize_retr_stream`]
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()))?;
@@ -447,7 +467,7 @@ where
Ok(data_stream)
}
- /// Finalize retr stream; must be called once the requested file, got previously with `retr_as_stream()` has been read
+ /// Finalize retr stream; must be called once the requested file, got previously with [`ImplFtpStream::retr_as_stream`] has been read
pub fn finalize_retr_stream(&mut self, stream: impl Read) -> FtpResult<()> {
debug!("Finalizing retr stream");
// Drop stream NOTE: must be done first, otherwise server won't return any response
@@ -475,7 +495,7 @@ where
}
/// This stores a file on the server.
- /// r argument must be any struct which implemenents the Read trait.
+ /// r argument must be any struct which implemenents the [`Read`] trait.
/// Returns amount of written bytes
pub fn put_file, R: Read>(&mut self, filename: S, r: &mut R) -> FtpResult {
// Get stream
@@ -488,7 +508,7 @@ where
/// Send PUT command and returns a BufWriter, which references the file created on the server
/// 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`
+ /// Once you've finished the write, YOU MUST CALL THIS METHOD: [`ImplFtpStream::finalize_put_stream`]
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()))?;
@@ -498,7 +518,7 @@ where
/// Finalize put when using stream
/// This method must be called once the file has been written and
- /// `put_with_stream` has been used to write the file
+ /// [`ImplFtpStream::put_with_stream`] has been used to write the file
pub fn finalize_put_stream(&mut self, stream: impl Write) -> FtpResult<()> {
debug!("Finalizing put stream");
// Drop stream NOTE: must be done first, otherwise server won't return any response
@@ -510,7 +530,7 @@ where
}
/// 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`
+ /// Once you've finished the write, YOU MUST CALL THIS METHOD: [`ImplFtpStream::finalize_put_stream`]
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()))?;
@@ -872,11 +892,11 @@ where
Mode::ExtendedPassive => self
.epsv()
.and_then(|addr| self.perform(cmd).map(|_| addr))
- .and_then(|addr| TcpStream::connect(addr).map_err(FtpError::ConnectionError))?,
+ .and_then(|addr| (self.passive_stream_builder)(addr))?,
Mode::Passive => self
.pasv()
.and_then(|addr| self.perform(cmd).map(|_| addr))
- .and_then(|addr| TcpStream::connect(addr).map_err(FtpError::ConnectionError))?,
+ .and_then(|addr| (self.passive_stream_builder)(addr))?,
};
#[cfg(not(feature = "secure"))]
@@ -992,6 +1012,11 @@ where
self.finalize_retr_stream(data_stream)?;
lines
}
+
+ /// Default stream builder
+ fn default_passive_stream_builder() -> Box {
+ Box::new(|addr| TcpStream::connect(addr).map_err(FtpError::ConnectionError))
+ }
}
#[cfg(test)]
@@ -1461,4 +1486,15 @@ mod test {
.with_no_client_auth()
}
*/
+
+ #[test]
+ fn test_should_set_passive_stream_builder() {
+ crate::log_init();
+ let _ftp_stream = FtpStream::connect("test.rebex.net:21")
+ .unwrap()
+ .passive_stream_builder(|addr| {
+ println!("Connecting to {}", addr);
+ TcpStream::connect(addr).map_err(FtpError::ConnectionError)
+ });
+ }
}
diff --git a/suppaftp/src/sync_ftp/tls/rustls.rs b/suppaftp/src/sync_ftp/tls/rustls.rs
index f630371..b21319f 100644
--- a/suppaftp/src/sync_ftp/tls/rustls.rs
+++ b/suppaftp/src/sync_ftp/tls/rustls.rs
@@ -6,7 +6,8 @@ use std::io::Write;
use std::net::TcpStream;
use std::sync::Arc;
-use rustls::{ClientConfig, ClientConnection, ServerName, StreamOwned};
+use rustls::pki_types::ServerName;
+use rustls::{ClientConfig, ClientConnection, StreamOwned};
use super::{TlsConnector, TlsStream};
use crate::{FtpError, FtpResult};
@@ -32,8 +33,8 @@ 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 server_name = ServerName::try_from(domain.to_string())
+ .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);
From 3c1f4293bba68f3e38fc3e3dbdb65d789fefc911 Mon Sep 17 00:00:00 2001
From: veeso
Date: Mon, 14 Oct 2024 14:43:43 +0200
Subject: [PATCH 2/3] fix: ci
---
.github/workflows/cargo.yml | 2 +-
.github/workflows/coverage.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml
index fade85e..869df3d 100644
--- a/.github/workflows/cargo.yml
+++ b/.github/workflows/cargo.yml
@@ -16,7 +16,7 @@ jobs:
override: true
components: rustfmt, clippy
- name: Setup containers
- run: docker-compose -f "tests/docker-compose.yml" up -d --build
+ run: docker compose -f "tests/docker-compose.yml" up -d --build
- name: Build simple
run: cargo build --package suppaftp
- name: Build secure (native-tls)
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 2fc0cd5..34d1153 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup containers
- run: docker-compose -f "tests/docker-compose.yml" up -d --build
+ run: docker compose -f "tests/docker-compose.yml" up -d --build
- name: Setup nightly toolchain
uses: dtolnay/rust-toolchain@stable
with:
From 10140611cf34812702d6c3cdcb2ccfebc6adb3cd Mon Sep 17 00:00:00 2001
From: veeso
Date: Mon, 14 Oct 2024 14:46:26 +0200
Subject: [PATCH 3/3] fix: ci
---
.github/workflows/cargo.yml | 2 +-
.github/workflows/coverage.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml
index 869df3d..278eb57 100644
--- a/.github/workflows/cargo.yml
+++ b/.github/workflows/cargo.yml
@@ -30,7 +30,7 @@ jobs:
- name: Build all features
run: cargo build --features deprecated,native-tls,rustls,async-native-tls,async-rustls --package suppaftp
- name: Run tests
- run: cargo test --package suppaftp --no-default-features --features rustls,native-tls,async-native-tls,async-rustls,with-containers --no-fail-fast
+ run: cargo test --lib --package suppaftp --no-default-features --features rustls,native-tls,async-native-tls,async-rustls,with-containers --no-fail-fast
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 34d1153..d0501b8 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -19,7 +19,7 @@ jobs:
toolchain: nightly
override: true
- name: Run tests (nightly)
- run: cargo test --package suppaftp --no-default-features --features native-tls,deprecated,async-native-tls,with-containers --no-fail-fast
+ run: cargo test --lib --package suppaftp --no-default-features --features native-tls,deprecated,async-native-tls,with-containers --no-fail-fast
env:
RUST_LOG: trace
CARGO_INCREMENTAL: "0"