1mod line_reader;
2
3use std::sync::Arc;
4use std::net::TcpStream;
5use std::error::Error;
6use std::io::{Write};
7
8use rustls::{RootCertStore, ClientConnection, StreamOwned};
9
10use line_reader::LineReader;
11
12pub struct Pop3Connection {
14 tls: StreamOwned<ClientConnection, TcpStream>,
15 reader: LineReader,
16}
17
18pub struct Pop3Stat {
20 pub message_count: u32,
22
23 pub maildrop_size: u32,
25}
26
27pub struct Pop3MessageInfo {
29 pub message_id: u32,
31
32 pub message_size: u32,
34}
35
36pub struct Pop3MessageUidInfo {
38 pub message_id: u32,
40
41 pub unique_id: String,
43}
44
45impl Pop3Connection {
46
47 pub fn new(host: &str, port: u16) -> Result<Pop3Connection, Box<dyn Error>> {
54 let mut root_store = RootCertStore::empty();
55 for cert in rustls_native_certs::load_native_certs()? {
56 root_store.add(&rustls::Certificate(cert.0))?;
57 }
58
59 Pop3Connection::with_custom_certs(host, port, root_store)
60 }
61
62 pub fn with_custom_certs(host: &str, port: u16, root_store: RootCertStore) -> Result<Pop3Connection, Box<dyn Error>> {
84 let config = rustls::ClientConfig::builder()
85 .with_safe_defaults()
86 .with_root_certificates(root_store)
87 .with_no_client_auth();
88
89 let server_name = host.try_into()?;
90
91 let connection = rustls::ClientConnection::new(Arc::new(config), server_name)?;
92 let stream = TcpStream::connect(format!("{}:{}", host, port))?;
93 let tls = rustls::StreamOwned::new(connection, stream);
94
95 let mut client = Pop3Connection {
96 tls,
97 reader: LineReader::new()
98 };
99
100 client.read_status_line()?;
101 Ok(client)
102 }
103
104 fn read_status_line(&mut self) -> Result<String, Box<dyn Error>> {
105 let line = self.reader.read_line(&mut self.tls)?;
106
107 match line.starts_with("+OK") {
108 true => Ok(line),
109 _ => Err(line.into())
110 }
111 }
112
113 fn invoke_single_line(&mut self, command: &str) -> Result<String, Box<dyn Error>> {
114 self.tls.write_all(command.as_bytes())?;
115 self.read_status_line()
116 }
117
118 fn invoke_multi_line(&mut self, command: &str) -> Result<Vec<String>, Box<dyn Error>> {
119 self.tls.write_all(command.as_bytes())?;
120 self.read_status_line()?;
121
122 let mut response : Vec<String> = vec!();
123 loop {
124 let line = self.reader.read_line(&mut self.tls)?;
125 match line {
126 _ if line == "." => { break },
127 _ if line.starts_with(".") => { response.push(line[1..].to_string()); },
128 _ => { response.push(line); }
129 };
130 }
131
132 Ok(response)
133 }
134
135 pub fn login(&mut self, user: &str, password: &str) -> Result<(), Box<dyn Error>> {
145 self.invoke_single_line(&format!("USER {}\r\n", user))?;
146 self.invoke_single_line(&format!("PASS {}\r\n", password))?;
147 Ok(())
148 }
149
150 pub fn stat(&mut self) -> Result<Pop3Stat, Box<dyn Error>> {
152 let stat = self.invoke_single_line("STAT\r\n")?;
153 let mut stat = stat.split(' ');
154 let _ = stat.next();
155 let message_count = stat.next().ok_or("missing message count")?;
156 let message_count = message_count.parse::<u32>()?;
157 let maildrop_size = stat.next().ok_or("missing maildrop size")?;
158 let maildrop_size = maildrop_size.parse::<u32>()?;
159
160 Ok(Pop3Stat { message_count, maildrop_size })
161 }
162
163 pub fn list(&mut self) -> Result<Vec<Pop3MessageInfo>, Box<dyn Error>> {
165 let lines = self.invoke_multi_line("LIST\r\n")?;
166 let mut result = vec!();
167 for line in lines {
168 let mut info = line.split(' ');
169 let message_id = info.next().ok_or("missing id")?.parse::<u32>()?;
170 let message_size = info.next().ok_or("missing size")?.parse::<u32>()?;
171
172 result.push(Pop3MessageInfo { message_id, message_size });
173 }
174
175 Ok(result)
176 }
177
178 pub fn get_message_size(&mut self, message_id: u32) -> Result<u32, Box<dyn Error>> {
184 let line = self.invoke_single_line(&format!("LIST {}\r\n", message_id))?;
185 let mut info = line.split(' ');
186 let _ = info.next(); let _ = info.next(); let message_size = info.next().ok_or("missing size")?.parse::<u32>()?;
189
190 Ok(message_size)
191 }
192
193 pub fn retrieve(&mut self, message_id: u32, writer: &mut impl Write) -> Result<(), Box<dyn Error>> {
200 let lines = self.invoke_multi_line(&format!("RETR {}\r\n", message_id))?;
201 for line in lines {
202 writer.write_all(line.as_bytes())?;
203 writer.write_all(b"\n")?;
204 }
205
206 Ok(())
207 }
208
209 pub fn delete(&mut self, message_id: u32) -> Result<(), Box<dyn Error>> {
215 self.invoke_single_line(&format!("DELE {}\r\n", message_id))?;
216 Ok(())
217 }
218
219 pub fn reset(&mut self) -> Result<(), Box<dyn Error>> {
221 self.invoke_single_line("RSET\r\n")?;
222 Ok(())
223 }
224
225 pub fn top(&mut self, message_id: u32, line_count: u32) -> Result<String, Box<dyn Error>> {
232 let lines = self.invoke_multi_line(&format!("TOP {} {}\r\n", message_id, line_count))?;
233 let mut message = String::new();
234 for line in lines {
235 message.push_str(&line);
236 message.push('\n');
237 }
238
239 Ok(message)
240 }
241
242 pub fn list_unique_ids(&mut self) -> Result<Vec<Pop3MessageUidInfo>, Box<dyn Error>> {
244 let lines = self.invoke_multi_line("UIDL\r\n")?;
245 let mut result = vec!();
246
247 for line in lines {
248 let mut info = line.split(' ');
249 let message_id = info.next().ok_or("missing id")?.parse::<u32>()?;
250 let unique_id = info.next().ok_or("missing unique id")?.to_string();
251
252 result.push(Pop3MessageUidInfo { message_id, unique_id });
253 }
254
255 Ok(result)
256 }
257
258 pub fn get_unique_id(&mut self, message_id :u32) -> Result<String, Box<dyn Error>> {
264 let line = self.invoke_single_line(&format!("UIDL {}\r\n", message_id))?;
265 let mut info = line.split(' ');
266 let _ = info.next(); let _ = info.next(); let unique_id = info.next().ok_or("missing unique id")?.to_string();
269
270 Ok(unique_id)
271 }
272}
273
274impl Drop for Pop3Connection {
275 fn drop(&mut self) {
277 let _ = self.invoke_single_line("QUIT\r\n");
278 }
279}