list: multipart message reading

This commit is contained in:
FliegendeWurst 2021-03-31 20:44:32 +02:00 committed by Arne Keller
parent 9282813429
commit f0def7776b
4 changed files with 144 additions and 13 deletions

38
Cargo.lock generated
View File

@ -279,10 +279,12 @@ dependencies = [
"maildir", "maildir",
"mailparse", "mailparse",
"mailproc", "mailproc",
"mime2ext",
"moins", "moins",
"rusqlite", "rusqlite",
"rustls-connector", "rustls-connector",
"rustyline", "rustyline",
"subprocess",
] ]
[[package]] [[package]]
@ -294,6 +296,12 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.50" version = "0.3.50"
@ -398,10 +406,20 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "mime2ext"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88947611258697e12f8602a44003b0885ca5fe30f27132d63c8f47fe98f2f2e"
dependencies = [
"serde",
"serde_json",
]
[[package]] [[package]]
name = "moins" name = "moins"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/FliegendeWurst/moins?branch=master#46df794709b7fdc23e83e61d97c75b11e8248f0f" source = "git+https://github.com/FliegendeWurst/moins?branch=master#d30052f34176336d27c35c8e62fa56fce70b03ea"
dependencies = [ dependencies = [
"termion", "termion",
] ]
@ -501,9 +519,9 @@ dependencies = [
[[package]] [[package]]
name = "quoted_printable" name = "quoted_printable"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" checksum = "1238256b09923649ec89b08104c4dfe9f6cb2fea734a5db5384e44916d59e9c5"
[[package]] [[package]]
name = "radix_trie" name = "radix_trie"
@ -709,6 +727,9 @@ name = "serde"
version = "1.0.125" version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
dependencies = [
"serde_derive",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
@ -721,6 +742,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.6.1" version = "1.6.1"

View File

@ -21,3 +21,5 @@ rustyline = "8.0.0"
moins = { git = "https://github.com/FliegendeWurst/moins", branch = "master" } moins = { git = "https://github.com/FliegendeWurst/moins", branch = "master" }
anyhow = "1.0.40" anyhow = "1.0.40"
mailproc = { git = "https://github.com/FliegendeWurst/mailproc.git", branch = "master" } mailproc = { git = "https://github.com/FliegendeWurst/mailproc.git", branch = "master" }
subprocess = "0.2.6"
mime2ext = "0.1.2"

View File

@ -1,8 +1,9 @@
use std::{array::IntoIter, env}; use std::{array::IntoIter, collections::HashSet, env, fs};
use ascii_table::{Align, AsciiTable, Column}; use ascii_table::{Align, AsciiTable, Column};
use inboxid::*; use inboxid::*;
use itertools::Itertools; use itertools::Itertools;
use mailparse::ParsedMail;
use rustyline::{Editor, error::ReadlineError}; use rustyline::{Editor, error::ReadlineError};
fn main() -> Result<()> { fn main() -> Result<()> {
@ -64,19 +65,71 @@ fn show_listing(mailbox: &str) -> Result<()> {
return Ok(()); return Ok(());
} }
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let mut state = Initial;
let mut to_delete = HashSet::new();
loop { loop {
let readline = rl.readline(">> "); let readline = rl.readline(&match state {
Initial => ">> ".to_owned(),
MailSelected(x) => format!("{} >> ", mails.len() - x),
AwaitingSave(_, _) => "out? >> ".to_owned()
});
match readline { match readline {
Ok(line) => { Ok(line) => {
let idx = mails.len() - line.trim().parse::<usize>().unwrap(); let input_idx = line.trim().parse::<usize>();
let mail = &mails[idx]; match state {
println!("{}", std::str::from_utf8(&mail.get_body_raw().unwrap()).unwrap()); Initial => {
for x in &mail.subparts { if let Ok(idx) = input_idx {
if x.ctype.mimetype == "text/html" { let idx = mails.len() - idx;
continue; // TODO let mail = &mails[idx];
if mail.ctype.mimetype.starts_with("text/") {
let raw_body = mail.get_body_raw();
let content = std::str::from_utf8(raw_body.as_deref().unwrap())?;
moins::Moins::run(content, None);
} else if mail.ctype.mimetype.starts_with("multipart/") {
mail.print_tree_structure(0, &mut 1);
state = MailSelected(idx);
} else {
state = AwaitingSave(&*mail, None);
}
continue;
}
},
MailSelected(mail_idx) => {
let mail = &mails[mail_idx];
if let Ok(idx) = input_idx {
let part = mail.get_tree_part(&mut 1, idx).unwrap();
if part.ctype.mimetype.starts_with("text/") {
let raw_body = part.get_body_raw();
let content = std::str::from_utf8(raw_body.as_deref().unwrap())?;
moins::Moins::run(content, None);
} else {
state = AwaitingSave(part, Some(mail_idx));
}
continue;
} else if line.is_empty() {
state = Initial;
continue;
}
},
AwaitingSave(mail, idx) => {
if line == "open" {
let path = if let Some(ext) = mime2ext::mime2ext(&mail.ctype.mimetype) {
format!("/tmp/mail_content.{}", ext)
} else {
"/tmp/mail_content".to_owned()
};
fs::write(&path, &mail.get_body_raw()?)?;
let mut p = subprocess::Popen::create(&["xdg-open", &path], Default::default())?;
p.wait()?;
to_delete.insert(path);
state = if let Some(idx) = idx {
MailSelected(idx)
} else {
Initial
};
continue;
}
} }
let mut content = x.get_body().unwrap();
moins::Moins::run(&mut content, None);
} }
}, },
Err(ReadlineError::Interrupted) => { Err(ReadlineError::Interrupted) => {
@ -90,7 +143,20 @@ fn show_listing(mailbox: &str) -> Result<()> {
break break
} }
} }
println!("unknown command!");
}
for x in to_delete {
let _ = fs::remove_file(x);
} }
Ok(()) Ok(())
} }
enum State<'a> {
Initial,
MailSelected(usize),
AwaitingSave(&'a ParsedMail<'a>, Option<usize>)
}
use State::*;

View File

@ -109,6 +109,37 @@ impl<'a> Deref for EasyMail<'a> {
} }
} }
pub trait MailExtension {
fn print_tree_structure(&self, depth: usize, counter: &mut usize);
fn get_tree_part(&self, counter: &mut usize, target: usize) -> Option<&ParsedMail>;
}
impl MailExtension for ParsedMail<'_> {
fn print_tree_structure(&self, depth: usize, counter: &mut usize) {
if depth == 0 {
println!("{}", self.ctype.mimetype);
}
for mail in &self.subparts {
println!("{}-> {} [{}]", " ".repeat(depth), mail.ctype.mimetype, counter);
*counter += 1;
mail.print_tree_structure(depth + 1, counter);
}
}
fn get_tree_part(&self, counter: &mut usize, target: usize) -> Option<&ParsedMail> {
for mail in &self.subparts {
if *counter == target {
return Some(mail);
}
*counter += 1;
if let Some(x) = mail.get_tree_part(counter, target) {
return Some(x);
}
}
None
}
}
pub trait MaildirExtension { pub trait MaildirExtension {
fn get_file(&self, name: &str) -> std::result::Result<String, io::Error>; fn get_file(&self, name: &str) -> std::result::Result<String, io::Error>;
fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error>; fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error>;