Improve IMAP example client with proper async handling and security warnings

Co-authored-by: setoelkahfi <1797197+setoelkahfi@users.noreply.github.com>

copilot-swe-agent[bot] committed Oct 13, 2025 at 16:20 UTC 4a87e3822717db93e211475cbc3dcc0dd3e7eae4
2 files changed +63 -30
Cargo.toml
+1 -1
@@ -9,7 +9,7 @@ futures = "0.3"
9 native-tls = "0.2"
10 openssl = "0.10"
11 regex = "1"
12 -tokio = { version = "1", features = [ "macros","io-util","net","rt", "rt-multi-thread"] }
12 +tokio = { version = "1", features = [ "macros","io-util","net","rt", "rt-multi-thread", "time"] }
13 tokio-native-tls = "0.3"
14 tokio-util = { version = "0.7", features = ["codec"] }
15 tracing = "0.1"
examples/imap_client.rs
+62 -29
@@ -1,52 +1,85 @@
1 -use std::io::{Read, Write};
2 -use std::net::TcpStream;
1 +use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
2 +use tokio::net::TcpStream;
3 +use std::time::Duration;
4
5 /// Simple example demonstrating IMAP client interaction
5 -fn main() -> std::io::Result<()> {
6 +///
7 +/// NOTE: This example uses placeholder credentials ('username'/'password').
8 +/// In production, always use secure authentication methods.
9 +#[tokio::main]
10 +async fn main() -> std::io::Result<()> {
11 println!("Connecting to IMAP server at 127.0.0.1:1143...");
12
8 - let mut stream = TcpStream::connect("127.0.0.1:1143")?;
9 - let mut buffer = [0; 1024];
13 + let stream = TcpStream::connect("127.0.0.1:1143").await?;
14 + let mut reader = BufReader::new(stream);
15
16 // Read greeting
12 - let n = stream.read(&mut buffer)?;
13 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
17 + let greeting = read_response(&mut reader).await?;
18 + println!("Server: {}", greeting);
19
20 // Send CAPABILITY command
16 - stream.write_all(b"a1 CAPABILITY\r\n")?;
17 - let n = stream.read(&mut buffer)?;
18 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
21 + send_command(&mut reader, b"a1 CAPABILITY\r\n").await?;
22
20 - // Login
21 - stream.write_all(b"a2 LOGIN testuser testpass\r\n")?;
22 - let n = stream.read(&mut buffer)?;
23 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
23 + // Login with placeholder credentials
24 + // WARNING: Never hardcode real credentials - use environment variables or config files
25 + send_command(&mut reader, b"a2 LOGIN username password\r\n").await?;
26
27 // List mailboxes
26 - stream.write_all(b"a3 LIST \"\" \"*\"\r\n")?;
27 - let n = stream.read(&mut buffer)?;
28 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
28 + send_command(&mut reader, b"a3 LIST \"\" \"*\"\r\n").await?;
29
30 // Select INBOX
31 - stream.write_all(b"a4 SELECT INBOX\r\n")?;
32 - let n = stream.read(&mut buffer)?;
33 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
31 + send_command(&mut reader, b"a4 SELECT INBOX\r\n").await?;
32
33 // Get status of INBOX
36 - stream.write_all(b"a5 STATUS INBOX (MESSAGES RECENT UNSEEN)\r\n")?;
37 - let n = stream.read(&mut buffer)?;
38 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
34 + send_command(&mut reader, b"a5 STATUS INBOX (MESSAGES RECENT UNSEEN)\r\n").await?;
35
36 // Search all messages
41 - stream.write_all(b"a6 SEARCH ALL\r\n")?;
42 - let n = stream.read(&mut buffer)?;
43 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
37 + send_command(&mut reader, b"a6 SEARCH ALL\r\n").await?;
38
39 // Logout
46 - stream.write_all(b"a7 LOGOUT\r\n")?;
47 - let n = stream.read(&mut buffer)?;
48 - println!("Server: {}", String::from_utf8_lossy(&buffer[..n]));
40 + send_command(&mut reader, b"a7 LOGOUT\r\n").await?;
41
42 println!("IMAP session completed successfully!");
43 Ok(())
44 }
45 +
46 +/// Helper function to send a command and read the complete response
47 +async fn send_command(reader: &mut BufReader<TcpStream>, command: &[u8]) -> std::io::Result<()> {
48 + reader.get_mut().write_all(command).await?;
49 + let response = read_response(reader).await?;
50 + println!("Server: {}", response);
51 + Ok(())
52 +}
53 +
54 +/// Helper function to read a complete IMAP response
55 +/// This handles multi-line responses by reading until we see a tagged completion response
56 +async fn read_response(reader: &mut BufReader<TcpStream>) -> std::io::Result<String> {
57 + let mut response = String::new();
58 + let mut buffer = vec![0u8; 1024];
59 +
60 + // Read data with a timeout to handle the streaming nature of IMAP
61 + loop {
62 + tokio::select! {
63 + result = reader.read(&mut buffer) => {
64 + let n = result?;
65 + if n == 0 {
66 + break;
67 + }
68 + response.push_str(&String::from_utf8_lossy(&buffer[..n]));
69 +
70 + // Check if we have a complete response (ends with a tagged response line)
71 + if response.contains(" OK ") || response.contains(" NO ") || response.contains(" BAD ") {
72 + break;
73 + }
74 + }
75 + _ = tokio::time::sleep(Duration::from_millis(100)) => {
76 + // Timeout - assume response is complete
77 + if !response.is_empty() {
78 + break;
79 + }
80 + }
81 + }
82 + }
83 +
84 + Ok(response.trim_end().to_string())
85 +}