use std::{
error::Error,
io::{IsTerminal, Read, Write, stdin, stdout},
};
use mail_parser::{HeaderValue, Message, MessageParser, MessagePart};
fn collect_parts<'a>(
msg: &'a Message<'a>,
part: &'a MessagePart<'a>,
depth: usize,
out: &mut Vec<(&'a MessagePart<'a>, usize)>,
) {
out.push((part, depth));
if let Some(subs) = part.sub_parts() {
for &id in subs {
collect_parts(msg, &msg.parts[id as usize], depth + 1, out);
}
}
}
fn get_type<'a>(part: &'a MessagePart<'a>) -> String {
if let Some(ct) = part
.headers()
.iter()
.find_map(|header| match &header.value {
HeaderValue::ContentType(ctype) => Some(ctype),
_ => None,
})
{
let mut s = ct.ctype().to_string();
if let Some(sub) = ct.subtype() {
s.push('/');
s.push_str(sub);
}
return s;
}
match &part.body {
mail_parser::PartType::Text(_) => "text".to_string(),
mail_parser::PartType::Html(_) => "html".to_string(),
mail_parser::PartType::Binary(_) => "binary".to_string(),
mail_parser::PartType::InlineBinary(_) => "inline-binary".to_string(),
mail_parser::PartType::Multipart(_) => "multipart".to_string(),
_ => "other".to_string(),
}
}
fn run() -> Result<(), Box<dyn Error>> {
let mut args = std::env::args().skip(1);
let mut index: Option<usize> = None;
let mut tree = false;
let mut headers_only = false;
let mut body_only = false;
while let Some(arg) = args.next() {
match arg.as_str() {
"--tree" => {
tree = true;
break;
}
"--headers" => {
headers_only = true;
body_only = false
}
"--body" => {
body_only = true;
headers_only = false
}
_ if index.is_none() => {
index = Some(arg.parse()?);
break;
}
_ => {}
}
}
let msg = match args.find_map(|path| {
let bytes = std::fs::read(path).ok()?;
MessageParser::new().parse(&bytes).map(Message::into_owned)
}) {
Some(m) => m,
None if !stdin().is_terminal() => {
let mut buf = Vec::new();
stdin().read_to_end(&mut buf)?;
MessageParser::new()
.parse(&buf)
.map(Message::into_owned)
.ok_or("missing message")?
}
_ => return Err("missing message".into()),
};
let mut parts = Vec::new();
collect_parts(&msg, msg.root_part(), 0, &mut parts);
if tree {
for (i, (part, depth)) in parts.iter().enumerate() {
println!("{:<4}{}{}", i, " ".repeat(*depth), get_type(part));
}
return Ok(());
}
let idx = index.ok_or("missing index")?;
let (part, _) = parts.get(idx).ok_or("index out of range")?;
let raw = msg.raw_message();
let h = part.raw_header_offset() as usize;
let b = part.raw_body_offset() as usize;
let e = part.raw_end_offset() as usize;
let slice = if headers_only {
&raw[h..b]
} else if body_only {
&raw[b..e]
} else {
&raw[h..e]
};
stdout().write_all(slice.trim_ascii())?;
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!("m2part: {e}");
std::process::exit(1);
}
}