1 //! POP3 Client Example
2 //!
3 //! This example demonstrates how to connect to a POP3 server and perform
4 //! common operations such as authentication, listing messages, retrieving
5 //! messages, and deleting messages.
6 //!
7 //! Usage:
8 //! cargo run --example pop3_client
9 //!
10 //! This example shows:
11 //! - Connecting to a POP3 server
12 //! - Authenticating with USER and PASS commands
13 //! - Getting mailbox statistics with STAT
14 //! - Listing messages with LIST
15 //! - Retrieving messages with RETR
16 //! - Deleting messages with DELE
17 //! - Properly closing connection with QUIT
18
19 use std::io::{BufRead, BufReader, Write};
20 use std::net::TcpStream;
21
22 /// Represents a POP3 client connection
23 struct Pop3Client {
24 stream: TcpStream,
25 reader: BufReader<TcpStream>,
26 }
27
28 impl Pop3Client {
29 /// Connect to a POP3 server
30 fn connect(host: &str, port: u16) -> Result<Self, Box<dyn std::error::Error>> {
31 println!("Connecting to {}:{}...", host, port);
32 let stream = TcpStream::connect(format!("{}:{}", host, port))?;
33 let reader = BufReader::new(stream.try_clone()?);
34
35 let mut client = Pop3Client { stream, reader };
36
37 // Read the server greeting
38 let greeting = client.read_response()?;
39 println!("Server greeting: {}", greeting);
40
41 if !greeting.starts_with("+OK") {
42 return Err(format!("Server did not send +OK greeting: {}", greeting).into());
43 }
44
45 Ok(client)
46 }
47
48 /// Send a command to the server
49 fn send_command(&mut self, command: &str) -> Result<(), Box<dyn std::error::Error>> {
50 println!("> {}", command);
51 writeln!(self.stream, "{}", command)?;
52 self.stream.flush()?;
53 Ok(())
54 }
55
56 /// Read a single-line response from the server
57 fn read_response(&mut self) -> Result<String, Box<dyn std::error::Error>> {
58 let mut response = String::new();
59 self.reader.read_line(&mut response)?;
60 let response = response.trim_end().to_string();
61 println!("< {}", response);
62 Ok(response)
63 }
64
65 /// Read a multi-line response from the server (ends with ".")
66 fn read_multiline_response(&mut self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
67 let mut lines = Vec::new();
68 loop {
69 let mut line = String::new();
70 self.reader.read_line(&mut line)?;
71 let line = line.trim_end().to_string();
72 println!("< {}", line);
73
74 if line == "." {
75 break;
76 }
77 lines.push(line);
78 }
79 Ok(lines)
80 }
81
82 /// Authenticate with username and password
83 fn authenticate(&mut self, username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
84 println!("\n=== Authenticating ===");
85
86 // Send USER command
87 self.send_command(&format!("USER {}", username))?;
88 let response = self.read_response()?;
89 if !response.starts_with("+OK") {
90 return Err(format!("USER command failed: {}", response).into());
91 }
92
93 // Send PASS command
94 self.send_command(&format!("PASS {}", password))?;
95 let response = self.read_response()?;
96 if !response.starts_with("+OK") {
97 return Err(format!("PASS command failed: {}", response).into());
98 }
99
100 println!("Authentication successful!");
101 Ok(())
102 }
103
104 /// Get mailbox statistics (STAT command)
105 fn stat(&mut self) -> Result<(usize, usize), Box<dyn std::error::Error>> {
106 println!("\n=== Getting mailbox statistics ===");
107
108 self.send_command("STAT")?;
109 let response = self.read_response()?;
110
111 if !response.starts_with("+OK") {
112 return Err(format!("STAT command failed: {}", response).into());
113 }
114
115 // Parse response: +OK <message_count> <mailbox_size>
116 let parts: Vec<&str> = response.split_whitespace().collect();
117 if parts.len() >= 3 {
118 let message_count = parts[1].parse::<usize>()?;
119 let mailbox_size = parts[2].parse::<usize>()?;
120 println!("Mailbox has {} messages ({} bytes)", message_count, mailbox_size);
121 Ok((message_count, mailbox_size))
122 } else {
123 Err("Invalid STAT response format".into())
124 }
125 }
126
127 /// List all messages (LIST command)
128 fn list(&mut self) -> Result<Vec<(usize, usize)>, Box<dyn std::error::Error>> {
129 println!("\n=== Listing all messages ===");
130
131 self.send_command("LIST")?;
132 let response = self.read_response()?;
133
134 if !response.starts_with("+OK") {
135 return Err(format!("LIST command failed: {}", response).into());
136 }
137
138 let lines = self.read_multiline_response()?;
139 let mut messages = Vec::new();
140
141 for line in lines {
142 let parts: Vec<&str> = line.split_whitespace().collect();
143 if parts.len() >= 2 {
144 let msg_num = parts[0].parse::<usize>()?;
145 let msg_size = parts[1].parse::<usize>()?;
146 messages.push((msg_num, msg_size));
147 println!(" Message {}: {} bytes", msg_num, msg_size);
148 }
149 }
150
151 Ok(messages)
152 }
153
154 /// List a specific message (LIST command with message number)
155 fn list_message(&mut self, msg_num: usize) -> Result<usize, Box<dyn std::error::Error>> {
156 println!("\n=== Listing message {} ===", msg_num);
157
158 self.send_command(&format!("LIST {}", msg_num))?;
159 let response = self.read_response()?;
160
161 if !response.starts_with("+OK") {
162 return Err(format!("LIST command failed: {}", response).into());
163 }
164
165 // Parse response: +OK <msg_num> <msg_size>
166 let parts: Vec<&str> = response.split_whitespace().collect();
167 if parts.len() >= 3 {
168 let msg_size = parts[2].parse::<usize>()?;
169 println!("Message {} size: {} bytes", msg_num, msg_size);
170 Ok(msg_size)
171 } else {
172 Err("Invalid LIST response format".into())
173 }
174 }
175
176 /// Retrieve a message (RETR command)
177 fn retrieve(&mut self, msg_num: usize) -> Result<String, Box<dyn std::error::Error>> {
178 println!("\n=== Retrieving message {} ===", msg_num);
179
180 self.send_command(&format!("RETR {}", msg_num))?;
181 let response = self.read_response()?;
182
183 if !response.starts_with("+OK") {
184 return Err(format!("RETR command failed: {}", response).into());
185 }
186
187 let lines = self.read_multiline_response()?;
188 let message = lines.join("\n");
189 println!("Retrieved message ({} lines)", lines.len());
190
191 Ok(message)
192 }
193
194 /// Delete a message (DELE command)
195 fn delete(&mut self, msg_num: usize) -> Result<(), Box<dyn std::error::Error>> {
196 println!("\n=== Deleting message {} ===", msg_num);
197
198 self.send_command(&format!("DELE {}", msg_num))?;
199 let response = self.read_response()?;
200
201 if !response.starts_with("+OK") {
202 return Err(format!("DELE command failed: {}", response).into());
203 }
204
205 println!("Message {} marked for deletion", msg_num);
206 Ok(())
207 }
208
209 /// Reset the session (RSET command) - unmarks deleted messages
210 fn reset(&mut self) -> Result<(), Box<dyn std::error::Error>> {
211 println!("\n=== Resetting session ===");
212
213 self.send_command("RSET")?;
214 let response = self.read_response()?;
215
216 if !response.starts_with("+OK") {
217 return Err(format!("RSET command failed: {}", response).into());
218 }
219
220 println!("Session reset - all delete marks removed");
221 Ok(())
222 }
223
224 /// No-op command (NOOP) - keeps connection alive
225 fn noop(&mut self) -> Result<(), Box<dyn std::error::Error>> {
226 println!("\n=== Sending NOOP ===");
227
228 self.send_command("NOOP")?;
229 let response = self.read_response()?;
230
231 if !response.starts_with("+OK") {
232 return Err(format!("NOOP command failed: {}", response).into());
233 }
234
235 println!("NOOP successful");
236 Ok(())
237 }
238
239 /// Close the connection (QUIT command)
240 fn quit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
241 println!("\n=== Closing connection ===");
242
243 self.send_command("QUIT")?;
244 let response = self.read_response()?;
245
246 if !response.starts_with("+OK") {
247 return Err(format!("QUIT command failed: {}", response).into());
248 }
249
250 println!("Connection closed successfully");
251 Ok(())
252 }
253 }
254
255 /// Example 1: Complete email retrieval session
256 fn example_complete_session() -> Result<(), Box<dyn std::error::Error>> {
257 println!("\n========================================");
258 println!("EXAMPLE 1: Complete Email Retrieval Session");
259 println!("========================================\n");
260
261 let mut client = Pop3Client::connect("localhost", 110)?;
262
263 // Authenticate
264 client.authenticate("testuser", "testpass")?;
265
266 // Get mailbox statistics
267 let (message_count, _mailbox_size) = client.stat()?;
268
269 // List all messages
270 let _messages = client.list()?;
271
272 // Retrieve first message if available
273 if message_count > 0 {
274 let message_content = client.retrieve(1)?;
275 println!("\nFirst message preview:");
276 println!("---");
277 for (i, line) in message_content.lines().take(10).enumerate() {
278 println!("{}", line);
279 if i == 9 && message_content.lines().count() > 10 {
280 println!("... (truncated)");
281 }
282 }
283 println!("---");
284 }
285
286 // Close connection
287 client.quit()?;
288
289 Ok(())
290 }
291
292 /// Example 2: Handling multiple messages
293 fn example_multiple_messages() -> Result<(), Box<dyn std::error::Error>> {
294 println!("\n========================================");
295 println!("EXAMPLE 2: Handling Multiple Messages");
296 println!("========================================\n");
297
298 let mut client = Pop3Client::connect("localhost", 110)?;
299 client.authenticate("testuser", "testpass")?;
300
301 // Get all messages
302 let messages = client.list()?;
303
304 println!("\nProcessing {} messages:", messages.len());
305 for (msg_num, msg_size) in messages {
306 println!("\nProcessing message {} ({} bytes):", msg_num, msg_size);
307
308 // Retrieve each message
309 match client.retrieve(msg_num) {
310 Ok(content) => {
311 println!(" Successfully retrieved {} bytes", content.len());
312 // Process message content here
313 }
314 Err(e) => {
315 println!(" Error retrieving message: {}", e);
316 }
317 }
318 }
319
320 client.quit()?;
321 Ok(())
322 }
323
324 /// Example 3: Message deletion workflow
325 fn example_deletion_workflow() -> Result<(), Box<dyn std::error::Error>> {
326 println!("\n========================================");
327 println!("EXAMPLE 3: Message Deletion Workflow");
328 println!("========================================\n");
329
330 let mut client = Pop3Client::connect("localhost", 110)?;
331 client.authenticate("testuser", "testpass")?;
332
333 let (message_count, _) = client.stat()?;
334
335 if message_count > 0 {
336 // Mark first message for deletion
337 client.delete(1)?;
338
339 // Change our mind - reset to unmark
340 client.reset()?;
341
342 // Verify message is still there
343 let messages = client.list()?;
344 println!("\nAfter reset, mailbox still has {} messages", messages.len());
345
346 // Delete again (this time for real)
347 client.delete(1)?;
348
349 // Quit to commit the deletion
350 println!("\nNote: Deletion will be committed when we QUIT");
351 } else {
352 println!("No messages to delete");
353 }
354
355 client.quit()?;
356 Ok(())
357 }
358
359 /// Example 4: Error handling
360 fn example_error_handling() -> Result<(), Box<dyn std::error::Error>> {
361 println!("\n========================================");
362 println!("EXAMPLE 4: Error Handling");
363 println!("========================================\n");
364
365 // Example: Authentication failure
366 println!("--- Testing authentication failure ---");
367 match Pop3Client::connect("localhost", 110) {
368 Ok(mut client) => {
369 match client.authenticate("wronguser", "wrongpass") {
370 Ok(_) => println!("Unexpected: Authentication succeeded"),
371 Err(e) => println!("Expected error: {}", e),
372 }
373 let _ = client.quit();
374 }
375 Err(e) => {
376 println!("Connection failed (expected if server not running): {}", e);
377 }
378 }
379
380 // Example: Invalid message number
381 println!("\n--- Testing invalid message number ---");
382 match Pop3Client::connect("localhost", 110) {
383 Ok(mut client) => {
384 if client.authenticate("testuser", "testpass").is_ok() {
385 match client.retrieve(99999) {
386 Ok(_) => println!("Unexpected: Retrieved non-existent message"),
387 Err(e) => println!("Expected error: {}", e),
388 }
389 let _ = client.quit();
390 }
391 }
392 Err(e) => {
393 println!("Connection failed (expected if server not running): {}", e);
394 }
395 }
396
397 Ok(())
398 }
399
400 /// Example 5: Connection error scenarios
401 fn example_connection_errors() {
402 println!("\n========================================");
403 println!("EXAMPLE 5: Connection Error Scenarios");
404 println!("========================================\n");
405
406 // Try to connect to non-existent server
407 println!("--- Testing connection to non-existent server ---");
408 match Pop3Client::connect("localhost", 9999) {
409 Ok(_) => println!("Unexpected: Connection succeeded"),
410 Err(e) => println!("Expected error: {}", e),
411 }
412
413 // Try to connect with invalid hostname
414 println!("\n--- Testing connection to invalid hostname ---");
415 match Pop3Client::connect("invalid.example.nonexistent", 110) {
416 Ok(_) => println!("Unexpected: Connection succeeded"),
417 Err(e) => println!("Expected error: {}", e),
418 }
419 }
420
421 fn main() {
422 println!("===========================================");
423 println!("POP3 Client Examples");
424 println!("===========================================");
425 println!("\nThis example demonstrates various POP3 operations.");
426 println!("Note: These examples assume a POP3 server is running on localhost:110");
427 println!(" with user 'testuser' and password 'testpass'.");
428 println!("\nIf the server is not running, you'll see connection errors,");
429 println!("which is normal and demonstrates error handling.\n");
430
431 // Run all examples
432 // Note: In real usage, you'd typically only run one session at a time
433
434 if let Err(e) = example_complete_session() {
435 println!("Example 1 error: {}", e);
436 }
437
438 if let Err(e) = example_multiple_messages() {
439 println!("Example 2 error: {}", e);
440 }
441
442 if let Err(e) = example_deletion_workflow() {
443 println!("Example 3 error: {}", e);
444 }
445
446 if let Err(e) = example_error_handling() {
447 println!("Example 4 error: {}", e);
448 }
449
450 example_connection_errors();
451
452 println!("\n===========================================");
453 println!("Examples completed!");
454 println!("===========================================");
455 }