From 113a72f1d618c1bef5610001f9ef7fa99caad95b Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Fri, 12 Dec 2025 10:29:00 +0100 Subject: [PATCH] refactor: replace rust-pop3-client with custom POP3 implementation Replace third-party POP3 client with a custom implementation using native-tls for direct TLS socket communication. This change ensures email messages are retrieved as raw bytes without encoding conversions that could corrupt email data. Key improvements: - Direct byte-level message retrieval preserves original email structure - Proper handling of POP3 byte-stuffing (doubled leading dots) - Eliminates dependency on rust-pop3-client which performed unwanted string conversions and line ending modifications - Uses only native-tls for TLS connections (already a project dependency) --- Cargo.lock | 255 ++--------------------------------------- Cargo.toml | 3 +- src/pop3_client/mod.rs | 129 ++++++++++++++++++--- 3 files changed, 123 insertions(+), 264 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e473ca7..a3e09f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,7 +56,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -67,7 +67,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -88,12 +88,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "bitflags" version = "1.3.2" @@ -222,7 +216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -252,17 +246,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "getrandom" version = "0.3.4" @@ -311,7 +294,7 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c617c55def8c42129e0dd503f11d7ee39d73f5c7e01eff55768b3879ff1d107d" dependencies = [ - "base64 0.13.1", + "base64", "bufstream", "chrono", "imap-proto 0.10.2", @@ -566,35 +549,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - [[package]] name = "rs_pop_imap_importer" version = "0.5.0" @@ -604,17 +558,6 @@ dependencies = [ "imap", "imap-proto 0.16.6", "native-tls", - "rust-pop3-client", -] - -[[package]] -name = "rust-pop3-client" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e42fdc52a54878a9331d2b1cb2521092f43432f1fafab16f71337a886fb38" -dependencies = [ - "rustls", - "rustls-native-certs", ] [[package]] @@ -627,40 +570,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", + "windows-sys", ] [[package]] @@ -681,17 +591,7 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", + "windows-sys", ] [[package]] @@ -723,12 +623,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "static_assertions" version = "1.1.0" @@ -759,10 +653,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -771,18 +665,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "utf8parse" version = "0.2.2" @@ -801,12 +683,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -861,48 +737,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "web-sys" -version = "0.3.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.62.2" @@ -962,15 +796,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -980,70 +805,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 8ff7d16..faf2257 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rs_pop_imap_importer" -version = "0.5.0" +version = "0.6.0" edition = "2024" [lib] @@ -21,4 +21,3 @@ dotenvy = "0.15.7" imap = "2.4.1" imap-proto = "0.16.6" native-tls = "0.2.14" -rust-pop3-client = "0.2.2" diff --git a/src/pop3_client/mod.rs b/src/pop3_client/mod.rs index d373cf9..b8868b0 100644 --- a/src/pop3_client/mod.rs +++ b/src/pop3_client/mod.rs @@ -1,43 +1,142 @@ use crate::config::settings::Pop3Config; -use rust_pop3_client::Pop3Connection; +use std::io::{BufRead, BufReader, Write}; +use std::net::TcpStream; +use native_tls::TlsConnector; pub struct Pop3Client { - connection: Pop3Connection, + stream: BufReader>, } impl Pop3Client { pub fn new(config: &Pop3Config) -> Result> { - let connection = Pop3Connection::new(&config.host, config.port)?; - Ok(Pop3Client { connection }) + // Connect to POP3 server + let tcp_stream = TcpStream::connect((&config.host[..], config.port))?; + + // Wrap with TLS + let connector = TlsConnector::new()?; + let tls_stream = connector.connect(&config.host, tcp_stream)?; + let stream = BufReader::new(tls_stream); + + let mut client = Pop3Client { stream }; + + // Read greeting + client.read_response()?; + + Ok(client) } pub fn login(&mut self, config: &Pop3Config) -> Result<(), Box> { - self.connection.login(&config.username, &config.password)?; + // Send USER command + self.send_command(&format!("USER {}\r\n", config.username))?; + self.read_response()?; + + // Send PASS command + self.send_command(&format!("PASS {}\r\n", config.password))?; + self.read_response()?; + Ok(()) } pub fn list_messages(&mut self) -> Result, Box> { - let infos = self.connection.list()?; - let list = infos.into_iter().map(|info| (info.message_id, info.message_size)).collect(); - Ok(list) + self.send_command("LIST\r\n")?; + + let mut messages = Vec::new(); + let mut line = String::new(); + + // Read first response line + self.stream.read_line(&mut line)?; + if !line.starts_with("+OK") { + return Err(format!("LIST failed: {}", line).into()); + } + + // Read message list + loop { + line.clear(); + self.stream.read_line(&mut line)?; + + if line.trim() == "." { + break; + } + + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + let id: u32 = parts[0].parse()?; + let size: u32 = parts[1].parse()?; + messages.push((id, size)); + } + } + + Ok(messages) } pub fn retrieve_message(&mut self, msg_id: u32) -> Result> { - let mut buffer = Vec::new(); - self.connection.retrieve(msg_id, &mut buffer)?; - let message = String::from_utf8(buffer)?; - Ok(message) + self.send_command(&format!("RETR {}\r\n", msg_id))?; + + let mut line = String::new(); + + // Read first response line + self.stream.read_line(&mut line)?; + if !line.starts_with("+OK") { + return Err(format!("RETR failed: {}", line).into()); + } + + // Read message content as raw bytes + let mut message_bytes = Vec::new(); + let mut line_bytes = Vec::new(); + + loop { + line_bytes.clear(); + let bytes_read = self.stream.read_until(b'\n', &mut line_bytes)?; + + if bytes_read == 0 { + break; + } + + // Check for termination (lone period) + if line_bytes == b".\r\n" || line_bytes == b".\n" { + break; + } + + // Handle byte-stuffing (POP3 doubles leading dots) + if line_bytes.starts_with(b"..") { + message_bytes.extend_from_slice(&line_bytes[1..]); + } else { + message_bytes.extend_from_slice(&line_bytes); + } + } + + // Convert to String using lossy conversion + // This preserves the structure while handling any encoding issues + Ok(String::from_utf8_lossy(&message_bytes).into_owned()) } #[allow(dead_code)] pub fn delete_message(&mut self, msg_id: u32) -> Result<(), Box> { - self.connection.delete(msg_id)?; + self.send_command(&format!("DELE {}\r\n", msg_id))?; + self.read_response()?; Ok(()) } pub fn quit(&mut self) -> Result<(), Box> { - // The rust-pop3-client doesn't seem to have an explicit quit method - // The connection should be closed when the object is dropped + self.send_command("QUIT\r\n")?; + self.read_response()?; Ok(()) } + + fn send_command(&mut self, command: &str) -> Result<(), Box> { + self.stream.get_mut().write_all(command.as_bytes())?; + self.stream.get_mut().flush()?; + Ok(()) + } + + fn read_response(&mut self) -> Result> { + let mut line = String::new(); + self.stream.read_line(&mut line)?; + + if !line.starts_with("+OK") { + return Err(format!("POP3 error: {}", line).into()); + } + + Ok(line) + } } \ No newline at end of file