diff --git a/Cargo.lock b/Cargo.lock index 59e6439..219ed7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "charset" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e9079d1a12a2cc2bffb5db039c43661836ead4082120d5844f02555aca2d46" +dependencies = [ + "base64", + "encoding_rs", +] + [[package]] name = "chrono" version = "0.4.26" @@ -147,6 +157,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "deflate" version = "1.0.0" @@ -157,6 +173,15 @@ dependencies = [ "gzip-header", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + [[package]] name = "errno" version = "0.3.1" @@ -332,6 +357,17 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "mailparse" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b56570f5f8c0047260d1c8b5b331f62eb9c660b9dd4071a8c46f8c7d3f280aa" +dependencies = [ + "charset", + "data-encoding", + "quoted_printable", +] + [[package]] name = "memchr" version = "2.5.0" @@ -442,6 +478,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49" + [[package]] name = "rand" version = "0.8.5" @@ -669,6 +711,7 @@ name = "tmpmail" version = "0.1.0" dependencies = [ "chrono", + "mailparse", "rand", "rouille", ] diff --git a/Cargo.toml b/Cargo.toml index 9790ac7..453fcc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] chrono = "0.4.26" +mailparse = "0.14.0" rand = "0.8.5" rouille = "3.6.2" diff --git a/src/http.rs b/src/http.rs index f0da425..c817096 100644 --- a/src/http.rs +++ b/src/http.rs @@ -9,10 +9,22 @@ pub fn http_handler(request: &Request, state: &State) -> Response { }, (GET) (/ffff) => { - rouille::Response::html("

email stuff

") + rouille::Response::redirect_302("/ffff/") }, - (POST) (/ffff) => { + (GET) (/ffff/) => { + rouille::Response::html(" +

email stuff

+
+ + +
+ ") + }, + + (POST) (/ffff/) => { let data = try_or_400!(post_input!(request, { domain: String })); @@ -23,23 +35,49 @@ pub fn http_handler(request: &Request, state: &State) -> Response { state.lock().unwrap().insert(email.clone(), Mailbox::new(email.clone())); - rouille::Response::text(email) + rouille::Response::redirect_301(format!("{}/", email)) }, - (GET) (/ffff/{email: String}) => { + (GET) (/ffff/{email: String}/) => { println!("{}", email); if let Some(mailbox) = state.lock().unwrap().get(&email) { - let body: &Vec = &mailbox.messages.iter().map(|msg| { - return format!("

From {}


{}

", msg.sender, msg.data) + let body: &Vec = &mailbox.messages.iter().enumerate().map(|(i, msg)| { + format!( + "

\"{}\" from {}



", + escape(&msg.subject), + escape(&msg.sender), + i + ) }).collect(); - rouille::Response::html(body.join("")) + rouille::Response::html(format!( + "

Mailbox: {}

{}okay bye", + escape(&mailbox.email), + body.join("") + )) } else { rouille::Response::text("Mailbox not found") } }, + (GET) (/ffff/{email: String}/{msg: usize}) => { + if let Some(mailbox) = state.lock().unwrap().get(&email) { + if let Some(mail) = mailbox.messages.get(msg) { + return rouille::Response::html(&mail.body) + } + } + + rouille::Response::text("Message not found") + }, + _ => rouille::Response::empty_404() ) } + +fn escape(data: &str) -> String { + data.replace('>', ">") + .replace('<', "<") + .replace('\"', """) + .replace('\'', "'") +} diff --git a/src/smtp.rs b/src/smtp.rs index b3405bc..21bb29b 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -1,6 +1,7 @@ use std::io::{BufRead, BufReader, Write}; use crate::state::{Message, State}; +use mailparse::{parse_mail, MailHeaderMap, ParsedMail}; use std::net::{TcpListener, TcpStream}; pub fn start_server(state: State) { @@ -20,10 +21,11 @@ pub fn start_server(state: State) { } else if let Ok(mut state_inner) = state.lock() { for mail in connection.messages { println!( - "Mail from {} for {}\n{}", + "Mail \"{}\" from {} for {}\n{}", + mail.subject, mail.sender, mail.recipients.join(", "), - mail.data + mail.body ); for r in &mail.recipients { @@ -92,15 +94,37 @@ impl IncomingMessage { } } - fn finalize(self) -> Message { - Message { + fn parse(self) -> Option { + let data = self.data?; + let mail = parse_mail(data.as_bytes()).ok()?; + + let body_part = part_by_content_type(&mail, "text/html") + .or_else(|| part_by_content_type(&mail, "text/plain")); + + let body = match body_part { + Some(c) => c.get_body().ok()?, + None => mail.get_body().unwrap_or("".to_owned()), + }; + + Some(Message { + body, sender: self.sender, recipients: self.recipients, - data: self.data.unwrap_or("".to_string()), - } + subject: mail + .get_headers() + .get_first_value("Subject") + .unwrap_or("".to_string()), + }) } } +fn part_by_content_type<'a>( + mail: &'a ParsedMail, + content_type: &str, +) -> Option<&'a ParsedMail<'a>> { + mail.parts().find(|p| p.ctype.mimetype == content_type) +} + impl Connection { fn new() -> Self { Connection { @@ -126,7 +150,7 @@ impl Connection { return Err(ERR_READ_FAILED); } - println!("Read line: {}", line); + println!("Read line: {}", line.trim_end()); let response = self.handle_line(&line); @@ -216,10 +240,10 @@ impl Connection { } fn finalize_mail(&mut self) { - let current_message = self.current_message.take(); + let current_message = self.current_message.take().and_then(IncomingMessage::parse); if let Some(mail) = current_message { - self.messages.push(mail.finalize()) + self.messages.push(mail); } } diff --git a/src/state.rs b/src/state.rs index e39dadb..4c463e9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -18,7 +18,8 @@ pub struct Mailbox { pub struct Message { pub sender: String, pub recipients: Vec, - pub data: String, + pub subject: String, + pub body: String, } impl Mailbox {