From d64670c16eaf3455cc128fea0d76f731dd6650b3 Mon Sep 17 00:00:00 2001 From: veeso Date: Wed, 12 Feb 2025 10:17:39 +0100 Subject: [PATCH 01/16] fix(ci): coverage --- .github/workflows/coverage.yml | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ebbece8..5081b8e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,4 +1,4 @@ -name: Coverage +name: coverage on: [push, pull_request] @@ -6,27 +6,25 @@ env: CARGO_TERM_COLOR: always jobs: - build: + coverage: + name: Generate coverage runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Setup nightly toolchain + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup rust toolchain uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly - - name: Run tests (nightly) - run: cargo test --lib --package suppaftp --no-default-features --features native-tls,deprecated,async-native-tls --no-fail-fast - env: - RUST_LOG: trace - CARGO_INCREMENTAL: "0" - RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests" - RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests" - - name: Coverage with grcov - id: coverage - uses: actions-rs/grcov@v0.1 + toolchain: stable + - uses: taiki-e/install-action@v2 + with: + tool: cargo-llvm-cov + - name: Run tests + run: cargo llvm-cov --no-fail-fast --no-default-features --features native-tls,deprecated,async-native-tls --workspace --lcov --output-path lcov.info - name: Coveralls - uses: coverallsapp/github-action@v1.1.1 + uses: coverallsapp/github-action@v2.3.6 with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ${{ steps.coverage.outputs.report }} + file: lcov.info + # currently we only run one coverage report per build + parallel: false From aed101eec706e63bf5b2f38201a74fe37791aa0c Mon Sep 17 00:00:00 2001 From: "Alan D. Salewski" Date: Sun, 16 Feb 2025 15:26:58 -0500 Subject: [PATCH 02/16] fix: doc: fix minor typos referencing 'termscp' in CONTRIBUTING.md (#98) (#99) Signed-off-by: Alan D. Salewski --- CHANGELOG.md | 7 +++++++ CONTRIBUTING.md | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c7e5f2..8b6d951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [6.0.8](#608) - [6.0.7](#607) - [6.0.6](#606) - [6.0.5](#605) @@ -40,6 +41,12 @@ --- +## 6.0.8 + +Released on ??? (TBD) + +- [Issue 98](https://github.com/veeso/suppaftp/issues/98): doc: fixed minor typos that referenced `termscp` + ## 6.0.7 - [Issue 88](https://github.com/veeso/suppaftp/issues/88): Removed `ip.is_private()` check on NAT workaround, which prevented public IPs to be used for Natting. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1342ab7..e80d1c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ Check the issue is always assigned to `veeso`. ### Bug reports -If you want to report an issue or a bug you've encountered while using termscp, open an issue using the `Bug report` template. +If you want to report an issue or a bug you've encountered while using suppaftp, open an issue using the `Bug report` template. The `Bug` label should already be set and the issue should already be assigned to `veeso`. When you open a bug try to be the most precise as possible in describing your issue. I'm not saying you should always be that precise, since sometimes it's very easy for maintainers to understand what you're talking about. Just try to be reasonable to understand sometimes we might not know what you're talking about or we just don't have the technical knowledge you might think. @@ -53,7 +53,7 @@ Maintainers will may add additional labels to your issue: ### Feature requests Whenever you have a good idea which chould improve the project, it is a good idea to submit it to the project owner. -The first thing you should do though, is not starting to write the code, but is to become concern about how termscp works, what kind +The first thing you should do though, is not starting to write the code, but is to become concern about how suppaftp works, what kind of contribution I appreciate and what kind of contribution I won't consider. Said so, follow these steps: @@ -101,7 +101,7 @@ Let's make it simple and clear: In addition to the process described for the PRs, I've also decided to introduce a list of guidelines to follow when writing the code, that should be followed: -1. **Let's stop the NPM apocalypse**: personally I'm against the abuse of dependencies we make in software projects and I think that NodeJS has opened the way to this drama (and has already gone too far). Nowadays nobody cares about adding hundreds of dependencies to their projects. Don't misunderstand me: I think that package managers are cool, but I'm totally against the abuse we're making of them. I think when we work on a project, we should try to use the minor quantity of dependencies as possible, especially because it's not hard to see how many libraries are getting abandoned right now, causing compatibility issues after a while. So please, when working on termscp, try not to add useless dependencies. +1. **Let's stop the NPM apocalypse**: personally I'm against the abuse of dependencies we make in software projects and I think that NodeJS has opened the way to this drama (and has already gone too far). Nowadays nobody cares about adding hundreds of dependencies to their projects. Don't misunderstand me: I think that package managers are cool, but I'm totally against the abuse we're making of them. I think when we work on a project, we should try to use the minor quantity of dependencies as possible, especially because it's not hard to see how many libraries are getting abandoned right now, causing compatibility issues after a while. So please, when working on suppaftp, try not to add useless dependencies. 2. **No C-bindings**: personally I think that Rust still relies too much on C. And that's bad, really bad. Many libraries in Rust are just wrappers to C libraries, which is a huge problem, especially considering this is a multiplatform project. Everytime you add a C-binding to your project, you're forcing your users to install additional libraries to their systems. Sometimes these libraries are already installed on their systems (as happens for libssh2 or openssl in this case), but sometimes not. So if you really have to add a dependency to this project, please AVOID completely adding C-bounded libraries. 3. **Test units matter**: Whenever you implement something new to this project, always implement test units which cover the most cases as possible. 4. **Comments are useful**: Many people say that the code should be that simple to talk by itself about what it does, and comments should then be useless. I personally don't agree. I'm not saying they're wrong, but I'm just saying that this approach has, in my personal opinion, many aspects which are underrated: From 72c68b493de680a263b3c8054d3475e7946353f7 Mon Sep 17 00:00:00 2001 From: Christian Visintin Date: Mon, 10 Mar 2025 12:04:21 +0100 Subject: [PATCH 03/16] feat(deps): migrated from async-tls to futures-rustls (#101) async-tls is unmaintained and rustls has actually released an official version for async tls, so we should use that instead fix #100 --- CHANGELOG.md | 7 ++++--- Cargo.toml | 2 +- README.md | 2 +- suppaftp/Cargo.toml | 11 ++++++----- suppaftp/src/async_ftp/tls/rustls.rs | 16 +++++++++++----- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6d951..3af7ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog - [Changelog](#changelog) - - [6.0.8](#608) + - [6.1.0](#610) - [6.0.7](#607) - [6.0.6](#606) - [6.0.5](#605) @@ -41,10 +41,11 @@ --- -## 6.0.8 +## 6.1.0 -Released on ??? (TBD) +Released on 10/03/2025 +- [Issue 100](https://github.com/veeso/suppaftp/issues/100): Migrated away from unmaintained `async-tls` to `futures-rustls` - [Issue 98](https://github.com/veeso/suppaftp/issues/98): doc: fixed minor typos that referenced `termscp` ## 6.0.7 diff --git a/Cargo.toml b/Cargo.toml index fec9247..9e9d486 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["suppaftp", "suppaftp-cli"] resolver = "2" [workspace.package] -version = "6.0.7" +version = "6.1.0" edition = "2021" rust-version = "1.71.1" authors = [ diff --git a/README.md b/README.md index bbdb581..201c977 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Developed by veeso and Matt McCoy

-

Current version: 6.0.7 (18/01/2025)

+

Current version: 6.1.0 (10/03/2025)

FtpResult { + let server_name = ServerName::DnsName( + DnsName::try_from(domain.to_string()) + .map_err(|e| FtpError::SecureError(e.to_string()))?, + ); + self.connector - .connect(domain, stream) + .connect(server_name, stream) .await .map(AsyncRustlsStream::from) .map_err(|e| FtpError::SecureError(e.to_string())) @@ -95,7 +101,7 @@ impl AsyncTlsStream for AsyncRustlsStream { type InnerStream = TlsStream; fn get_ref(&self) -> &TcpStream { - self.stream.get_ref() + self.stream.get_ref().0 } fn mut_ref(&mut self) -> &mut Self::InnerStream { @@ -103,6 +109,6 @@ impl AsyncTlsStream for AsyncRustlsStream { } fn tcp_stream(self) -> TcpStream { - self.stream.get_ref().clone() + self.stream.get_ref().0.clone() } } From 48eea0918651ada84a2b5447b767a0f41864b0b7 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 10 Mar 2025 12:08:13 +0100 Subject: [PATCH 04/16] fix(chore): readme styles --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 201c977..6e7335c 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,8 @@ suppaftp = { version = "^6", features = ["native-tls"] } suppaftp = { version = "^6", features = ["rustls"] } ``` -> 💡 If you don't know what to choose, `native-tls` should be preferred for compatibility reasons. +> [!NOTE] +> 💡 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 @@ -148,8 +149,9 @@ If you want to enable **async** support, you must enable `async` feature in your suppaftp = { version = "^6", 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 ⚠️ +> [!CAUTION] +> ⚠️ 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 From f3a5596e49574ec6fcf8a9bd29f4b919d97d63bb Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 17 Mar 2025 10:27:18 +0100 Subject: [PATCH 05/16] refactor(log): added better logs for tracing streams --- CHANGELOG.md | 7 +++ Cargo.toml | 2 +- README.md | 2 +- suppaftp/src/async_ftp/mod.rs | 32 +++++--------- suppaftp/src/lib.rs | 14 ++++-- suppaftp/src/list.rs | 2 +- suppaftp/src/sync_ftp/mod.rs | 81 +++++++++++++++++++++++++++-------- 7 files changed, 94 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3af7ca9..5323e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [6.1.1](#611) - [6.1.0](#610) - [6.0.7](#607) - [6.0.6](#606) @@ -41,6 +42,12 @@ --- +## 6.1.1 + +Released on 17/03/2025 + +- added a couple of logs to debug streams. + ## 6.1.0 Released on 10/03/2025 diff --git a/Cargo.toml b/Cargo.toml index 9e9d486..b50c712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["suppaftp", "suppaftp-cli"] resolver = "2" [workspace.package] -version = "6.1.0" +version = "6.1.1" edition = "2021" rust-version = "1.71.1" authors = [ diff --git a/README.md b/README.md index 6e7335c..d91ef00 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Developed by veeso and Matt McCoy

-

Current version: 6.1.0 (10/03/2025)

+

Current version: 6.1.1 (17/03/2025)

().unwrap(), - caps[2].parse::().unwrap(), - caps[3].parse::().unwrap(), - caps[4].parse::().unwrap(), - ); - let (msb, lsb) = ( - caps[5].parse::().unwrap(), - caps[6].parse::().unwrap(), - ); - let ip = Ipv4Addr::new(oct1, oct2, oct3, oct4); - let port = (u16::from(msb) << 8) | u16::from(lsb); - let addr = SocketAddr::new(ip.into(), port); + let addr = FtpStream::parse_passive_address_from_response(response)?; trace!("Passive address: {}", addr); if self.nat_workaround { let mut remote = self @@ -876,7 +860,7 @@ where .get_ref() .peer_addr() .map_err(FtpError::ConnectionError)?; - remote.set_port(port); + remote.set_port(addr.port()); trace!("Replacing site local address {} with {}", addr, remote); Ok(remote) } else { @@ -922,6 +906,7 @@ where match data_stream.read_line(&mut line).await { Ok(0) => break, Ok(_) => { + trace!("STREAM IN: {:?}", line); if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { @@ -933,7 +918,10 @@ where } lines.push(line); } - Err(_) => return Err(FtpError::BadResponse), + Err(err) => { + error!("failed to get lines from stream: {err}"); + return Err(FtpError::BadResponse); + } } } trace!("Lines from stream {:?}", lines); diff --git a/suppaftp/src/lib.rs b/suppaftp/src/lib.rs index 2b2121c..23ff698 100644 --- a/suppaftp/src/lib.rs +++ b/suppaftp/src/lib.rs @@ -224,8 +224,14 @@ pub type AsyncRustlsFtpStream = ImplAsyncFtpStream; // -- test logging #[cfg(test)] pub fn log_init() { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Debug) - .try_init(); + use std::sync::Once; + + static INIT: Once = Once::new(); + + INIT.call_once(|| { + let _ = env_logger::builder() + .filter_level(log::LevelFilter::Trace) + .is_test(true) + .try_init(); + }); } diff --git a/suppaftp/src/list.rs b/suppaftp/src/list.rs index 3afe8cd..ece6596 100644 --- a/suppaftp/src/list.rs +++ b/suppaftp/src/list.rs @@ -146,7 +146,7 @@ impl File { self.size } - //// Returns the last time the file was modified + /// Returns the last time the file was modified pub fn modified(&self) -> SystemTime { self.modified } diff --git a/suppaftp/src/sync_ftp/mod.rs b/suppaftp/src/sync_ftp/mod.rs index 72da85a..f8de664 100644 --- a/suppaftp/src/sync_ftp/mod.rs +++ b/suppaftp/src/sync_ftp/mod.rs @@ -770,6 +770,7 @@ where match data_stream.read_line(&mut line) { Ok(0) => break, Ok(_) => { + trace!("STREAM IN: {:?}", line); if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { @@ -781,7 +782,10 @@ where } lines.push(line); } - Err(_) => return Err(FtpError::BadResponse), + Err(err) => { + error!("failed to get lines from stream: {err}"); + return Err(FtpError::BadResponse); + } } } trace!("Lines from stream {:?}", lines); @@ -969,8 +973,28 @@ where debug!("PASV command"); self.perform(Command::Pasv)?; // PASV response format : 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). - let response: Response = self.read_response(Status::PassiveMode)?; + let response = self.read_response(Status::PassiveMode)?; + let addr = Self::parse_passive_address_from_response(response)?; + trace!("Passive address: {addr}",); + if self.nat_workaround { + let mut remote = self + .reader + .get_ref() + .get_ref() + .peer_addr() + .map_err(FtpError::ConnectionError)?; + remote.set_port(addr.port()); + trace!("Replacing site local address {} with {}", addr, remote); + Ok(remote) + } else { + Ok(addr) + } + } + + /// Parse passive address from response + pub(crate) fn parse_passive_address_from_response(response: Response) -> FtpResult { let response_str = response.as_string().map_err(|_| FtpError::BadResponse)?; + trace!("PASV response: {response_str}",); let caps = PASV_PORT_RE .captures(&response_str) .ok_or_else(|| FtpError::UnexpectedResponse(response.clone()))?; @@ -988,20 +1012,8 @@ where let ip = Ipv4Addr::new(oct1, oct2, oct3, oct4); let port = (u16::from(msb) << 8) | u16::from(lsb); let addr = SocketAddr::new(ip.into(), port); - trace!("Passive address: {}", addr); - if self.nat_workaround { - let mut remote = self - .reader - .get_ref() - .get_ref() - .peer_addr() - .map_err(FtpError::ConnectionError)?; - remote.set_port(port); - trace!("Replacing site local address {} with {}", addr, remote); - Ok(remote) - } else { - Ok(addr) - } + + Ok(addr) } /// Execute a command which returns list of strings in a separate stream @@ -1022,6 +1034,7 @@ where #[cfg(test)] mod test { + use std::net::IpAddr; use std::sync::Arc; #[cfg(feature = "secure")] @@ -1041,6 +1054,41 @@ mod test { with_test_ftp_stream(|_stream| {}); } + #[test] + fn test_should_parse_passive_address_from_response() { + let response = vec![ + 50, 50, 55, 32, 69, 110, 116, 101, 114, 105, 110, 103, 32, 80, 97, 115, 115, 105, 118, + 101, 32, 77, 111, 100, 101, 32, 40, 49, 50, 55, 44, 48, 44, 48, 44, 49, 44, 49, 49, 55, + 44, 53, 54, 41, 13, 10, + ]; + let response = Response::new(Status::PassiveMode, response); + + let address = FtpStream::parse_passive_address_from_response(response) + .expect("Failed to parse passive address"); + assert_eq!( + address.ip(), + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + "IP address is not correct" + ); + assert_eq!(address.port(), 30008, "Port is not correct"); + + let response = vec![ + 50, 50, 55, 32, 69, 110, 116, 101, 114, 105, 110, 103, 32, 80, 97, 115, 115, 105, 118, + 101, 32, 77, 111, 100, 101, 32, 40, 53, 56, 44, 50, 52, 55, 44, 57, 50, 44, 49, 50, 50, + 44, 49, 52, 54, 44, 50, 51, 57, 41, 46, 13, 10, + ]; + let response = Response::new(Status::PassiveMode, response); + + let address = FtpStream::parse_passive_address_from_response(response) + .expect("Failed to parse passive address"); + assert_eq!( + address.ip(), + IpAddr::V4(Ipv4Addr::new(58, 247, 92, 122)), + "IP address is not correct" + ); + assert_eq!(address.port(), 37615, "Port is not correct"); + } + #[test] #[serial] fn should_change_mode() { @@ -1163,7 +1211,6 @@ mod test { } #[test] - fn should_transfer_file() { with_test_ftp_stream(|stream| { // Set transfer type to Binary From 11030abb6fdc9eb3be2582ef8d34220c77b4bd4f Mon Sep 17 00:00:00 2001 From: Peter <57495324+SaelKimberly@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:18:11 +0500 Subject: [PATCH 06/16] fix: set `get_ref` to sync, to acquire reference to internal tls stream. (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Петр Хрущев --- suppaftp/src/async_ftp/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/suppaftp/src/async_ftp/mod.rs b/suppaftp/src/async_ftp/mod.rs index 07bbadd..a57c69a 100644 --- a/suppaftp/src/async_ftp/mod.rs +++ b/suppaftp/src/async_ftp/mod.rs @@ -285,7 +285,7 @@ where } /// Returns a reference to the underlying TcpStream. - pub async fn get_ref(&self) -> &TcpStream { + pub fn get_ref(&self) -> &TcpStream { self.reader.get_ref().get_ref() } @@ -1107,7 +1107,7 @@ mod test { async fn get_ref() { let (stream, _container) = setup_stream().await; - assert!(stream.get_ref().await.set_ttl(255).is_ok()); + assert!(stream.get_ref().set_ttl(255).is_ok()); finalize_stream(stream).await; } From b0b0151c3f7b5b472e19c615a39abd778e43f596 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 14 Apr 2025 09:23:59 +0200 Subject: [PATCH 07/16] fix: unnecessary to_string --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- suppaftp/src/sync_ftp/mod.rs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5323e12..c9869cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [6.1.2](#612) - [6.1.1](#611) - [6.1.0](#610) - [6.0.7](#607) @@ -42,6 +43,12 @@ --- +## 6.1.2 + +Released on 14/04/2025 + +- [fix: `get_ref` for async tls stream was unnecessarily async](https://github.com/veeso/suppaftp/pull/103) + ## 6.1.1 Released on 17/03/2025 diff --git a/Cargo.toml b/Cargo.toml index b50c712..f51cce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["suppaftp", "suppaftp-cli"] resolver = "2" [workspace.package] -version = "6.1.1" +version = "6.1.2" edition = "2021" rust-version = "1.71.1" authors = [ diff --git a/suppaftp/src/sync_ftp/mod.rs b/suppaftp/src/sync_ftp/mod.rs index f8de664..64c6f94 100644 --- a/suppaftp/src/sync_ftp/mod.rs +++ b/suppaftp/src/sync_ftp/mod.rs @@ -367,7 +367,7 @@ where /// Sets the type of file to be transferred. That is the implementation /// of `TYPE` command. pub fn transfer_type(&mut self, file_type: FileType) -> FtpResult<()> { - debug!("Setting transfer type {}", file_type.to_string()); + debug!("Setting transfer type {}", file_type); self.perform(Command::Type(file_type))?; self.read_response(Status::CommandOk).map(|_| ()) } From b580e4b6e6ffdb26464e2813ead69199d6c9fca9 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 14 Apr 2025 09:30:23 +0200 Subject: [PATCH 08/16] fix: lint --- suppaftp/src/async_ftp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/suppaftp/src/async_ftp/mod.rs b/suppaftp/src/async_ftp/mod.rs index a57c69a..5a5dd8b 100644 --- a/suppaftp/src/async_ftp/mod.rs +++ b/suppaftp/src/async_ftp/mod.rs @@ -384,7 +384,7 @@ where /// Sets the type of file to be transferred. That is the implementation /// of `TYPE` command. pub async fn transfer_type(&mut self, file_type: FileType) -> FtpResult<()> { - debug!("Setting transfer type {}", file_type.to_string()); + debug!("Setting transfer type {}", file_type); self.perform(Command::Type(file_type)).await?; self.read_response(Status::CommandOk).await.map(|_| ()) } From b5b18f7f66223529b89ee3ed5618de75f761d2c5 Mon Sep 17 00:00:00 2001 From: veeso Date: Mon, 14 Apr 2025 09:31:22 +0200 Subject: [PATCH 09/16] feat!: `get_ref` for async tls stream was unnecessarily async it was an async function, but it didn't make sense to be async and created issues when dealing with pooling BREAKING CHANGE: get_ref calls must remove await --- CHANGELOG.md | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9869cf..485cd0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog - [Changelog](#changelog) - - [6.1.2](#612) + - [6.2.0](#620) - [6.1.1](#611) - [6.1.0](#610) - [6.0.7](#607) @@ -43,11 +43,11 @@ --- -## 6.1.2 +## 6.2.0 Released on 14/04/2025 -- [fix: `get_ref` for async tls stream was unnecessarily async](https://github.com/veeso/suppaftp/pull/103) +- [feat (BREAKING): `get_ref` for async tls stream was unnecessarily async](https://github.com/veeso/suppaftp/pull/103) ## 6.1.1 diff --git a/Cargo.toml b/Cargo.toml index f51cce0..72fd1bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["suppaftp", "suppaftp-cli"] resolver = "2" [workspace.package] -version = "6.1.2" +version = "6.2.0" edition = "2021" rust-version = "1.71.1" authors = [ From b40fcbbf301dd64b1e5cf3ae518d645bad973372 Mon Sep 17 00:00:00 2001 From: Simon Repp Date: Sun, 11 May 2025 12:32:33 +0200 Subject: [PATCH 10/16] fix(chore): Update/fix rustls example in readme (#104) --- README.md | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d91ef00..1e385ee 100644 --- a/README.md +++ b/README.md @@ -218,31 +218,28 @@ fn main() { #### Ftp with TLS (rustls) ```rust -use std::str; -use std::io::Cursor; use std::sync::Arc; use suppaftp::{RustlsFtpStream, RustlsConnector}; +use suppaftp::rustls; use suppaftp::rustls::ClientConfig; fn main() { - let mut root_store = rustls::RootCertStore::empty(); - root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| { - rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - })); + let root_store = rustls::RootCertStore::from_iter( + webpki_roots::TLS_SERVER_ROOTS + .iter() + .cloned(), + ); + let config = ClientConfig::builder() - .with_safe_defaults() .with_root_certificates(root_store) .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 = RustlsFtpStream::connect("test.rebex.net:21") .unwrap() - .into_secure(RustlsConnector::from(Arc::clone(&config)), "test.rebex.net") + .into_secure(RustlsConnector::from(Arc::new(config)), "test.rebex.net") .unwrap(); + // Terminate the connection to the server. let _ = ftp_stream.quit(); } From e70b58c5b82190518f21206de4e99e26c590d6e7 Mon Sep 17 00:00:00 2001 From: Simon Repp Date: Sun, 11 May 2025 13:44:51 +0200 Subject: [PATCH 11/16] chore: Add executable rustls example (#105) * chore: Add executable rustls example * fix: fmt and required-features --------- Co-authored-by: veeso --- README.md | 2 ++ suppaftp/Cargo.toml | 4 ++++ suppaftp/examples/rustls.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 suppaftp/examples/rustls.rs diff --git a/README.md b/README.md index 1e385ee..98f019f 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,8 @@ fn main() { #### Ftp with TLS (rustls) +You can also find and run this example in the `suppaftp/examples/` directory (`cargo run --example rustls --features rustls`). + ```rust use std::sync::Arc; use suppaftp::{RustlsFtpStream, RustlsConnector}; diff --git a/suppaftp/Cargo.toml b/suppaftp/Cargo.toml index 130859c..093c728 100644 --- a/suppaftp/Cargo.toml +++ b/suppaftp/Cargo.toml @@ -85,3 +85,7 @@ no-log = ["log/max_level_off"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[[example]] +name = "rustls" +required-features = ["rustls"] diff --git a/suppaftp/examples/rustls.rs b/suppaftp/examples/rustls.rs new file mode 100644 index 0000000..45c80a1 --- /dev/null +++ b/suppaftp/examples/rustls.rs @@ -0,0 +1,28 @@ +//! To run this example enable the rustls feature (--features rustls). +//! If you use this code in your own project you need to enable suppaftp's +//! rustls feature through Cargo.toml and also include the webpki-roots crate +//! as a dependency (this includes Mozilla's root certificates for use with +//! rustls). + +use std::sync::Arc; + +use suppaftp::rustls::ClientConfig; +use suppaftp::{rustls, RustlsConnector, RustlsFtpStream}; + +fn main() { + let root_store = + rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + // Create a connection to an FTP server and authenticate to it. + let mut ftp_stream = RustlsFtpStream::connect("test.rebex.net:21") + .unwrap() + .into_secure(RustlsConnector::from(Arc::new(config)), "test.rebex.net") + .unwrap(); + + // Terminate the connection to the server. + let _ = ftp_stream.quit(); +} From 75975a09a359b643eb8ce4d7d33a757e0362fcd2 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 11 May 2025 13:45:28 +0200 Subject: [PATCH 12/16] chore: Code of conduct --- CODE_OF_CONDUCT.md | 152 +++++++++++++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 48 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 98e0a60..67fe8ce 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,75 +2,131 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment -include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission +* Publishing others' private information, such as a physical or email address, + without their explicit permission * Other conduct which could reasonably be considered inappropriate in a - professional setting + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at . All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. -[homepage]: https://www.contributor-covenant.org +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. -For answers to common questions about this code of conduct, see - +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations From 15ede956c1a8cd34dc45205035ea8fcdeb372a55 Mon Sep 17 00:00:00 2001 From: Christian Visintin Date: Tue, 13 May 2025 12:33:15 +0200 Subject: [PATCH 13/16] fix: Fixed `list` related commands which failed if the file name contained non UTF8 characters (#107) Changed the logic of get stream lines: use read_until and then convert to UTF8 lossy closes #106 --- .github/workflows/cargo.yml | 2 +- CHANGELOG.md | 8 +++++ Cargo.toml | 4 +-- suppaftp/src/async_ftp/mod.rs | 40 ++++++++++++++++----- suppaftp/src/lib.rs | 6 ++-- suppaftp/src/list.rs | 10 +++++- suppaftp/src/sync_ftp/mod.rs | 66 ++++++++++++++++++++-------------- suppaftp/src/test_container.rs | 30 +++++++++++++++- 8 files changed, 123 insertions(+), 43 deletions(-) diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml index 2033860..1ce3bbe 100644 --- a/.github/workflows/cargo.yml +++ b/.github/workflows/cargo.yml @@ -27,7 +27,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 --lib --package suppaftp --no-default-features --features rustls,native-tls,async-native-tls,async-rustls --no-fail-fast + run: cargo test --package suppaftp --no-default-features --features rustls,native-tls,async-native-tls,async-rustls --no-fail-fast - name: Format run: cargo fmt --all -- --check - name: Clippy diff --git a/CHANGELOG.md b/CHANGELOG.md index 485cd0e..8a0f961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [6.2.1](#621) - [6.2.0](#620) - [6.1.1](#611) - [6.1.0](#610) @@ -43,6 +44,13 @@ --- +## 6.2.1 + +Released on 13/05/2025 + +- [Issue 106](https://github.com/veeso/suppaftp/issues/106): Fixed `list` related commands which failed if the file name contained non UTF-8 characters. +- MSRV updated to 1.80.1 + ## 6.2.0 Released on 14/04/2025 diff --git a/Cargo.toml b/Cargo.toml index 72fd1bc..9187755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,9 @@ members = ["suppaftp", "suppaftp-cli"] resolver = "2" [workspace.package] -version = "6.2.0" +version = "6.2.1" edition = "2021" -rust-version = "1.71.1" +rust-version = "1.80.1" authors = [ "Christian Visintin ", "Matt McCoy ", diff --git a/suppaftp/src/async_ftp/mod.rs b/suppaftp/src/async_ftp/mod.rs index 5a5dd8b..90c6541 100644 --- a/suppaftp/src/async_ftp/mod.rs +++ b/suppaftp/src/async_ftp/mod.rs @@ -121,7 +121,7 @@ where /// /// ## Example /// - /// ```rust,no_run + /// ```rust,ignore /// use suppaftp::ImplAsyncFtpStream; /// use suppaftp::async_native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; @@ -179,7 +179,7 @@ where /// /// ## Example /// - /// ```rust,no_run + /// ```rust,ignore /// use suppaftp::ImplAsyncFtpStream; /// use suppaftp::native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; @@ -902,16 +902,17 @@ where let mut lines: Vec = Vec::new(); loop { - let mut line = String::new(); - match data_stream.read_line(&mut line).await { + let mut line_buf = vec![]; + match data_stream.read_until(b'\n', &mut line_buf).await { Ok(0) => break, - Ok(_) => { + Ok(len) => { + let mut line = String::from_utf8_lossy(&line_buf[..len]).to_string(); trace!("STREAM IN: {:?}", line); if line.ends_with('\n') { line.pop(); - if line.ends_with('\r') { - line.pop(); - } + } + if line.ends_with('\r') { + line.pop(); } if line.is_empty() { continue; @@ -1030,6 +1031,7 @@ where #[cfg(test)] mod test { + use std::str::FromStr as _; use std::sync::Arc; #[cfg(feature = "async-secure")] @@ -1341,6 +1343,28 @@ mod test { }); } + #[async_attributes::test] + async fn test_should_list_files_with_non_utf8_names() { + let (mut stream, container) = setup_stream().await; + let files = stream + .nlst(Some("/invalid-utf8/")) + .await + .expect("Failed to list files"); + assert_eq!(files.len(), 1); + + // list file and parse + let files = stream + .list(Some("/invalid-utf8/")) + .await + .expect("Failed to list files"); + assert_eq!(files.len(), 1); + // parse + crate::list::File::from_str(files[0].as_str()).expect("Failed to parse file"); + + finalize_stream(stream).await; + drop(container); + } + /// Test if the stream is Send fn is_send(_send: T) {} diff --git a/suppaftp/src/lib.rs b/suppaftp/src/lib.rs index 23ff698..33c1dcf 100644 --- a/suppaftp/src/lib.rs +++ b/suppaftp/src/lib.rs @@ -62,7 +62,7 @@ //! //! Here is a basic usage example: //! -//! ```rust +//! ```rust,ignore //! use suppaftp::FtpStream; //! let mut ftp_stream = FtpStream::connect("127.0.0.1:10021").unwrap_or_else(|err| //! panic!("{}", err) @@ -86,7 +86,7 @@ //! //! ### FTPS Usage //! -//! ```rust +//! ```rust,ignore //! use suppaftp::{NativeTlsFtpStream, NativeTlsConnector}; //! use suppaftp::native_tls::{TlsConnector, TlsStream}; //! @@ -104,7 +104,7 @@ //! Basically there's no difference in the function you can use when using the async version of suppaftp. //! Let's quickly see in the example how it works //! -//! ```rust +//! ```rust,ignore //! use suppaftp::{AsyncFtpStream, AsyncNativeTlsConnector}; //! use suppaftp::async_native_tls::{TlsConnector, TlsStream}; //! diff --git a/suppaftp/src/list.rs b/suppaftp/src/list.rs index ece6596..70c39f3 100644 --- a/suppaftp/src/list.rs +++ b/suppaftp/src/list.rs @@ -12,7 +12,7 @@ //! Whenever you receive the output for your LIST command, all you have to do is to iterate over lines and //! call `File::from_line()` function as shown in the example. //! -//! ```rust +//! ```rust,ignore //! use std::convert::TryFrom; //! use suppaftp::{FtpStream, list::File}; //! @@ -514,6 +514,14 @@ impl TryFrom<&str> for File { } } +impl TryFrom<&String> for File { + type Error = ParseError; + + fn try_from(line: &String) -> Result { + Self::try_from(line.as_str()) + } +} + impl TryFrom for File { type Error = ParseError; diff --git a/suppaftp/src/sync_ftp/mod.rs b/suppaftp/src/sync_ftp/mod.rs index 64c6f94..a7ceaa6 100644 --- a/suppaftp/src/sync_ftp/mod.rs +++ b/suppaftp/src/sync_ftp/mod.rs @@ -139,7 +139,7 @@ where /// /// ## Example /// - /// ```rust,no_run + /// ```rust,ignore /// use suppaftp::{NativeTlsFtpStream, NativeTlsConnector}; /// use suppaftp::native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; @@ -192,7 +192,7 @@ where /// /// ## Example /// - /// ```rust,no_run + /// ```rust,ignore /// use suppaftp::FtpStream; /// use suppaftp::native_tls::{TlsConnector, TlsStream}; /// use std::path::Path; @@ -262,7 +262,7 @@ where /// Returns a reference to the underlying [`TcpStream`]. /// /// Example: - /// ```no_run + /// ```ignore /// use suppaftp::FtpStream; /// use std::net::TcpStream; /// use std::time::Duration; @@ -399,21 +399,21 @@ where /// to download from FTP and `reader` is the function which operates with the /// data stream opened. /// - /// ``` - /// # use suppaftp::{FtpStream, FtpError}; - /// # use std::io::Cursor; - /// # let mut conn = FtpStream::connect("127.0.0.1:10021").unwrap(); - /// # conn.login("test", "test").and_then(|_| { - /// # let mut reader = Cursor::new("hello, world!".as_bytes()); - /// # conn.put_file("retr.txt", &mut reader) - /// # }).unwrap(); + /// ```rust,ignore + /// use suppaftp::{FtpStream, FtpError}; + /// use std::io::Cursor; + /// let mut conn = FtpStream::connect("127.0.0.1:10021").unwrap(); + /// conn.login("test", "test").and_then(|_| { + /// let mut reader = Cursor::new("hello, world!".as_bytes()); + /// conn.put_file("retr.txt", &mut reader) + /// }).unwrap(); /// assert!(conn.retr("retr.txt", |stream| { /// let mut buf = Vec::new(); /// stream.read_to_end(&mut buf).map(|_| /// assert_eq!(buf, "hello, world!".as_bytes()) /// ).map_err(|e| FtpError::ConnectionError(e)) /// }).is_ok()); - /// # assert!(conn.rm("retr.txt").is_ok()); + /// assert!(conn.rm("retr.txt").is_ok()); /// ``` pub fn retr(&mut self, file_name: &str, mut reader: F) -> FtpResult where @@ -431,7 +431,7 @@ where /// Simple way to retr a file from the server. This stores the file in a buffer in memory. /// - /// ``` + /// ```rust,ignore /// # use suppaftp::{FtpStream, FtpError}; /// # use std::io::Cursor; /// # let mut conn = FtpStream::connect("127.0.0.1:10021").unwrap(); @@ -766,16 +766,17 @@ where let mut lines: Vec = Vec::new(); loop { - let mut line = String::new(); - match data_stream.read_line(&mut line) { + let mut line_buf = vec![]; + match data_stream.read_until(b'\n', &mut line_buf) { Ok(0) => break, - Ok(_) => { + Ok(len) => { + let mut line = String::from_utf8_lossy(&line_buf[..len]).to_string(); trace!("STREAM IN: {:?}", line); if line.ends_with('\n') { line.pop(); - if line.ends_with('\r') { - line.pop(); - } + } + if line.ends_with('\r') { + line.pop(); } if line.is_empty() { continue; @@ -1035,6 +1036,7 @@ where mod test { use std::net::IpAddr; + use std::str::FromStr; use std::sync::Arc; #[cfg(feature = "secure")] @@ -1103,7 +1105,6 @@ mod test { } #[test] - fn should_connect_with_timeout() { crate::log_init(); let container = SyncPureFtpRunner::start(); @@ -1120,7 +1121,6 @@ mod test { } #[test] - fn welcome_message() { crate::log_init(); with_test_ftp_stream(|stream| { @@ -1132,7 +1132,6 @@ mod test { } #[test] - fn should_set_passive_nat_workaround() { with_test_ftp_stream(|stream| { stream.set_passive_nat_workaround(true); @@ -1152,7 +1151,6 @@ mod test { } #[test] - fn change_wrkdir() { with_test_ftp_stream(|stream| { let wrkdir: String = stream.pwd().unwrap(); @@ -1163,7 +1161,6 @@ mod test { } #[test] - fn cd_up() { with_test_ftp_stream(|stream| { let wrkdir: String = stream.pwd().unwrap(); @@ -1174,7 +1171,6 @@ mod test { } #[test] - fn noop() { with_test_ftp_stream(|stream| { assert!(stream.noop().is_ok()); @@ -1182,7 +1178,6 @@ mod test { } #[test] - fn make_and_remove_dir() { with_test_ftp_stream(|stream| { // Make directory @@ -1200,7 +1195,6 @@ mod test { } #[test] - fn set_transfer_type() { with_test_ftp_stream(|stream| { assert!(stream.transfer_type(FileType::Binary).is_ok()); @@ -1210,6 +1204,24 @@ mod test { }) } + #[test] + fn test_should_list_files_with_non_utf8_names() { + with_test_ftp_stream(|stream| { + let files = stream + .nlst(Some("/invalid-utf8/")) + .expect("Failed to list files"); + assert_eq!(files.len(), 1); + + // list file and parse + let files = stream + .list(Some("/invalid-utf8/")) + .expect("Failed to list files"); + assert_eq!(files.len(), 1); + // parse + crate::list::File::from_str(files[0].as_str()).expect("Failed to parse file"); + }); + } + #[test] fn should_transfer_file() { with_test_ftp_stream(|stream| { diff --git a/suppaftp/src/test_container.rs b/suppaftp/src/test_container.rs index 2d07cc5..2cb63f5 100644 --- a/suppaftp/src/test_container.rs +++ b/suppaftp/src/test_container.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; -use testcontainers::core::WaitFor; +use testcontainers::core::{CmdWaitFor, ExecCommand, WaitFor}; use testcontainers::{Container, ContainerAsync, Image}; #[derive(Debug, Default, Clone)] @@ -61,6 +61,34 @@ impl SyncPureFtpRunner { use testcontainers::runners::SyncRunner; let container = PureFtpImage::default().start().unwrap(); + let resp = container + .exec( + ExecCommand::new(["/bin/mkdir", "-p", "/home/test/invalid-utf8"]) + .with_cmd_ready_condition(CmdWaitFor::ExitCode { code: 0 }), + ) + .expect("Failed to create directory"); + assert_eq!( + resp.exit_code() + .expect("failed to get exit code for mkdir") + .expect("no exit code for mkdir"), + 0 + ); + let resp = container + .exec( + ExecCommand::new([ + "/usr/bin/touch", + "/home/test/invalid-utf8/caf\\303\\251.txt", + ]) + .with_cmd_ready_condition(CmdWaitFor::ExitCode { code: 0 }), + ) + .expect("Failed to create file"); + assert_eq!( + resp.exit_code() + .expect("failed to get exit code for touch") + .expect("no exit code for touch"), + 0 + ); + Self { container } } From abb16134577f6e4ef8880f95e751a37e301091be Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 13 May 2025 12:37:24 +0200 Subject: [PATCH 14/16] build: Updated dev-dependencies --- suppaftp/Cargo.toml | 6 +++--- suppaftp/src/async_ftp/mod.rs | 6 +++--- suppaftp/src/sync_ftp/mod.rs | 6 +++--- suppaftp/src/test_container.rs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/suppaftp/Cargo.toml b/suppaftp/Cargo.toml index 093c728..ac40270 100644 --- a/suppaftp/Cargo.toml +++ b/suppaftp/Cargo.toml @@ -50,10 +50,10 @@ futures-lite = "2" async-attributes = "1.1.2" env_logger = "^0.11" pretty_assertions = "^1.0.0" -rand = "^0.8.4" +rand = "^0.9" serial_test = "^3.0" -testcontainers = { version = "0.23", features = ["blocking"] } -webpki-roots = "0.26" +testcontainers = { version = "0.24", features = ["blocking"] } +webpki-roots = "1" [features] default = [] diff --git a/suppaftp/src/async_ftp/mod.rs b/suppaftp/src/async_ftp/mod.rs index 90c6541..532ea0c 100644 --- a/suppaftp/src/async_ftp/mod.rs +++ b/suppaftp/src/async_ftp/mod.rs @@ -1036,8 +1036,8 @@ mod test { #[cfg(feature = "async-secure")] use pretty_assertions::assert_eq; - use rand::distributions::Alphanumeric; - use rand::{thread_rng, Rng}; + use rand::distr::Alphanumeric; + use rand::{rng, Rng}; use serial_test::serial; use super::*; @@ -1456,7 +1456,7 @@ mod test { } fn generate_tempdir() -> String { - let mut rng = thread_rng(); + let mut rng = rng(); let name: String = std::iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) .map(char::from) diff --git a/suppaftp/src/sync_ftp/mod.rs b/suppaftp/src/sync_ftp/mod.rs index a7ceaa6..b7fa00e 100644 --- a/suppaftp/src/sync_ftp/mod.rs +++ b/suppaftp/src/sync_ftp/mod.rs @@ -1041,8 +1041,8 @@ mod test { #[cfg(feature = "secure")] use pretty_assertions::assert_eq; - use rand::distributions::Alphanumeric; - use rand::{thread_rng, Rng}; + use rand::distr::Alphanumeric; + use rand::{rng, Rng}; use serial_test::serial; use super::*; @@ -1403,7 +1403,7 @@ mod test { } fn generate_tempdir() -> String { - let mut rng = thread_rng(); + let mut rng = rng(); let name: String = std::iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) .map(char::from) diff --git a/suppaftp/src/test_container.rs b/suppaftp/src/test_container.rs index 2cb63f5..d6ace43 100644 --- a/suppaftp/src/test_container.rs +++ b/suppaftp/src/test_container.rs @@ -64,7 +64,7 @@ impl SyncPureFtpRunner { let resp = container .exec( ExecCommand::new(["/bin/mkdir", "-p", "/home/test/invalid-utf8"]) - .with_cmd_ready_condition(CmdWaitFor::ExitCode { code: 0 }), + .with_cmd_ready_condition(CmdWaitFor::Exit { code: Some(0) }), ) .expect("Failed to create directory"); assert_eq!( @@ -79,7 +79,7 @@ impl SyncPureFtpRunner { "/usr/bin/touch", "/home/test/invalid-utf8/caf\\303\\251.txt", ]) - .with_cmd_ready_condition(CmdWaitFor::ExitCode { code: 0 }), + .with_cmd_ready_condition(CmdWaitFor::Exit { code: Some(0) }), ) .expect("Failed to create file"); assert_eq!( From d9b0a1ba447b7cc83673da8811f7306fbf0a3e88 Mon Sep 17 00:00:00 2001 From: veeso Date: Tue, 13 May 2025 12:37:53 +0200 Subject: [PATCH 15/16] chore: bump version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 98f019f..f25d67d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Developed by veeso and Matt McCoy

-

Current version: 6.1.1 (17/03/2025)

+

Current version: 6.2.1 (13/05/2025)

Date: Tue, 13 May 2025 12:38:29 +0200 Subject: [PATCH 16/16] chore: Include examples in manifest --- suppaftp/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/suppaftp/Cargo.toml b/suppaftp/Cargo.toml index ac40270..dbb6c84 100644 --- a/suppaftp/Cargo.toml +++ b/suppaftp/Cargo.toml @@ -11,6 +11,7 @@ repository = { workspace = true } documentation = "https://docs.rs/suppaftp/" description = "A super FTP/FTPS client library for Rust" include = [ + "examples/**/*", "src/**/*", "../LICENSE-APACHE", "../LICENSE-MIT",