Show more data on webinterface

This commit is contained in:
Martin 2023-07-17 21:50:20 +02:00
parent e0fd858c2f
commit 6f83f0a62f
Signed by: mawalu
GPG Key ID: BF556F989760A7C8
5 changed files with 124 additions and 17 deletions

43
Cargo.lock generated
View File

@ -111,6 +111,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.26" version = "0.4.26"
@ -147,6 +157,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "data-encoding"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "deflate" name = "deflate"
version = "1.0.0" version = "1.0.0"
@ -157,6 +173,15 @@ dependencies = [
"gzip-header", "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]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.1"
@ -332,6 +357,17 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 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]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -442,6 +478,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "quoted_printable"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -669,6 +711,7 @@ name = "tmpmail"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"mailparse",
"rand", "rand",
"rouille", "rouille",
] ]

View File

@ -7,5 +7,6 @@ edition = "2021"
[dependencies] [dependencies]
chrono = "0.4.26" chrono = "0.4.26"
mailparse = "0.14.0"
rand = "0.8.5" rand = "0.8.5"
rouille = "3.6.2" rouille = "3.6.2"

View File

@ -9,10 +9,22 @@ pub fn http_handler(request: &Request, state: &State) -> Response {
}, },
(GET) (/ffff) => { (GET) (/ffff) => {
rouille::Response::html("<h1>email stuff</h1>") rouille::Response::redirect_302("/ffff/")
}, },
(POST) (/ffff) => { (GET) (/ffff/) => {
rouille::Response::html("
<h1>email stuff</h1>
<form method='POST'>
<select name='domain'>
<option value='test.m5w.de'>test.m5w.de</option>
</select>
<input type='submit'>
</form>
")
},
(POST) (/ffff/) => {
let data = try_or_400!(post_input!(request, { let data = try_or_400!(post_input!(request, {
domain: String 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())); 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); println!("{}", email);
if let Some(mailbox) = state.lock().unwrap().get(&email) { if let Some(mailbox) = state.lock().unwrap().get(&email) {
let body: &Vec<String> = &mailbox.messages.iter().map(|msg| { let body: &Vec<String> = &mailbox.messages.iter().enumerate().map(|(i, msg)| {
return format!("<h3>From {}<h3><br><pre>{}</pre><br>", msg.sender, msg.data) format!(
"<h3>\"{}\" from {}</h3><br><iframe src=\"{}\" sandbox csp=\"default-src 'none'; image-src data:\"></iframe><br>",
escape(&msg.subject),
escape(&msg.sender),
i
)
}).collect(); }).collect();
rouille::Response::html(body.join("")) rouille::Response::html(format!(
"<h1>Mailbox: {}</h1>{}<small>okay bye</small>",
escape(&mailbox.email),
body.join("")
))
} else { } else {
rouille::Response::text("Mailbox not found") 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() _ => rouille::Response::empty_404()
) )
} }
fn escape(data: &str) -> String {
data.replace('>', "&gt;")
.replace('<', "&lt;")
.replace('\"', "&quot;")
.replace('\'', "&apos;")
}

View File

@ -1,6 +1,7 @@
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
use crate::state::{Message, State}; use crate::state::{Message, State};
use mailparse::{parse_mail, MailHeaderMap, ParsedMail};
use std::net::{TcpListener, TcpStream}; use std::net::{TcpListener, TcpStream};
pub fn start_server(state: State) { 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() { } else if let Ok(mut state_inner) = state.lock() {
for mail in connection.messages { for mail in connection.messages {
println!( println!(
"Mail from {} for {}\n{}", "Mail \"{}\" from {} for {}\n{}",
mail.subject,
mail.sender, mail.sender,
mail.recipients.join(", "), mail.recipients.join(", "),
mail.data mail.body
); );
for r in &mail.recipients { for r in &mail.recipients {
@ -92,13 +94,35 @@ impl IncomingMessage {
} }
} }
fn finalize(self) -> Message { fn parse(self) -> Option<Message> {
Message { 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("<empty body>".to_owned()),
};
Some(Message {
body,
sender: self.sender, sender: self.sender,
recipients: self.recipients, recipients: self.recipients,
data: self.data.unwrap_or("<empty>".to_string()), subject: mail
.get_headers()
.get_first_value("Subject")
.unwrap_or("<empty subject>".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 { impl Connection {
@ -126,7 +150,7 @@ impl Connection {
return Err(ERR_READ_FAILED); return Err(ERR_READ_FAILED);
} }
println!("Read line: {}", line); println!("Read line: {}", line.trim_end());
let response = self.handle_line(&line); let response = self.handle_line(&line);
@ -216,10 +240,10 @@ impl Connection {
} }
fn finalize_mail(&mut self) { 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 { if let Some(mail) = current_message {
self.messages.push(mail.finalize()) self.messages.push(mail);
} }
} }

View File

@ -18,7 +18,8 @@ pub struct Mailbox {
pub struct Message { pub struct Message {
pub sender: String, pub sender: String,
pub recipients: Vec<String>, pub recipients: Vec<String>,
pub data: String, pub subject: String,
pub body: String,
} }
impl Mailbox { impl Mailbox {