diff --git a/src/email.rs b/src/email.rs index 62834a1..c6b72c1 100644 --- a/src/email.rs +++ b/src/email.rs @@ -3,7 +3,8 @@ use rfc2047_decoder; use crate::table::{self, DisplayCell, DisplayRow, DisplayTable}; -pub struct Uid(u32); +#[derive(Debug)] +pub struct Uid(pub u32); impl Uid { pub fn from_fetch(fetch: &imap::types::Fetch) -> Self { @@ -21,6 +22,7 @@ impl DisplayCell for Uid { } } +#[derive(Debug)] pub struct Flags<'a>(Vec>); impl Flags<'_> { @@ -67,6 +69,7 @@ impl DisplayCell for Flags<'_> { } } +#[derive(Debug)] pub struct Sender(String); impl Sender { @@ -97,6 +100,7 @@ impl DisplayCell for Sender { } } +#[derive(Debug)] pub struct Subject(String); impl Subject { @@ -124,6 +128,7 @@ impl DisplayCell for Subject { } } +#[derive(Debug)] pub struct Date(String); impl Date { @@ -148,6 +153,7 @@ impl DisplayCell for Date { } } +#[derive(Debug)] pub struct Email<'a> { pub uid: Uid, pub flags: Flags<'a>, diff --git a/src/imap.rs b/src/imap.rs index ce8dd88..35f55b0 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -7,6 +7,60 @@ use crate::config; use crate::email::Email; use crate::mailbox::Mailbox; +// Error wrapper + +#[derive(Debug)] +pub enum Error { + CreateTlsConnectorError(native_tls::Error), + CreateImapSession(imap::Error), + ReadEmailNotFoundError(String), + ReadEmailEmptyPartError(String, String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::CreateTlsConnectorError(err) => err.fmt(f), + Error::CreateImapSession(err) => err.fmt(f), + Error::ReadEmailNotFoundError(uid) => { + write!(f, "No email found for UID {}", uid) + } + Error::ReadEmailEmptyPartError(uid, mime) => { + write!(f, "No {} content found for UID {}", mime, uid) + } + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + Error::CreateTlsConnectorError(ref err) => Some(err), + Error::CreateImapSession(ref err) => Some(err), + Error::ReadEmailNotFoundError(_) => None, + Error::ReadEmailEmptyPartError(_, _) => None, + } + } +} + +impl From for Error { + fn from(err: native_tls::Error) -> Error { + Error::CreateTlsConnectorError(err) + } +} + +impl From for Error { + fn from(err: imap::Error) -> Error { + Error::CreateImapSession(err) + } +} + +// Result wrapper + +type Result = result::Result; + +// Imap connector + #[derive(Debug)] pub struct ImapConnector { pub config: config::ServerInfo, @@ -14,6 +68,26 @@ pub struct ImapConnector { } impl ImapConnector { + fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec) { + match part.subparts.len() { + 0 => { + if part + .get_headers() + .get_first_value("content-type") + .and_then(|v| if v.starts_with(mime) { Some(()) } else { None }) + .is_some() + { + parts.push(part.get_body().unwrap_or(String::new())) + } + } + _ => { + part.subparts + .iter() + .for_each(|p| Self::extract_subparts_by_mime(mime, p, parts)); + } + } + } + pub fn new(config: config::ServerInfo) -> Result { let tls = TlsConnector::new()?; let client = imap::connect(config.get_addr(), &config.host, &tls)?; @@ -57,92 +131,26 @@ impl ImapConnector { Ok(emails) } -} -// Error wrapper + pub fn read_email(&mut self, mbox: &str, uid: &str, mime: &str) -> Result { + self.sess.select(mbox)?; -#[derive(Debug)] -pub enum Error { - CreateTlsConnectorError(native_tls::Error), - CreateImapSession(imap::Error), -} + match self.sess.uid_fetch(uid, "BODY[]")?.first() { + None => return Err(Error::ReadEmailNotFoundError(uid.to_string())), + Some(email_raw) => { + let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap(); + let mut parts = vec![]; + Self::extract_subparts_by_mime(mime, &email, &mut parts); -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::CreateTlsConnectorError(err) => err.fmt(f), - Error::CreateImapSession(err) => err.fmt(f), + if parts.len() == 0 { + Err(Error::ReadEmailEmptyPartError( + uid.to_string(), + mime.to_string(), + )) + } else { + Ok(parts.join("\r\n")) + } + } } } } - -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - Error::CreateTlsConnectorError(ref err) => Some(err), - Error::CreateImapSession(ref err) => Some(err), - } - } -} - -impl From for Error { - fn from(err: native_tls::Error) -> Error { - Error::CreateTlsConnectorError(err) - } -} - -impl From for Error { - fn from(err: imap::Error) -> Error { - Error::CreateImapSession(err) - } -} - -// Result wrapper - -type Result = result::Result; - -// fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec) { -// match part.subparts.len() { -// 0 => { -// if part -// .get_headers() -// .get_first_value("content-type") -// .and_then(|v| if v.starts_with(mime) { Some(()) } else { None }) -// .is_some() -// { -// parts.push(part.get_body().unwrap_or(String::new())) -// } -// } -// _ => { -// part.subparts -// .iter() -// .for_each(|p| extract_subparts_by_mime(mime, p, parts)); -// } -// } -// } - -// pub fn read_email( -// imap_sess: &mut ImapSession, -// mbox: &str, -// uid: &str, -// mime: &str, -// ) -> imap::Result<()> { -// imap_sess.select(mbox)?; - -// match imap_sess.uid_fetch(uid, "BODY[]")?.first() { -// None => println!("No email found in mailbox {} with UID {}", mbox, uid), -// Some(email_raw) => { -// let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap(); -// let mut parts = vec![]; -// extract_subparts_by_mime(mime, &email, &mut parts); - -// if parts.len() == 0 { -// println!("No {} content found for email {}!", mime, uid); -// } else { -// println!("{}", parts.join("\r\n")); -// } -// } -// } - -// Ok(()) -// } diff --git a/src/main.rs b/src/main.rs index 69eac7b..28502de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,14 +6,25 @@ mod smtp; mod table; use clap::{App, Arg, SubCommand}; +use std::env::temp_dir; +use std::fs::{remove_file, File}; +use std::io::{Read, Write}; +use std::process::{exit, Command}; use crate::config::Config; use crate::imap::ImapConnector; use crate::table::DisplayTable; -// fn new_email_tpl() -> String { -// ["To: ", "Subject: ", ""].join("\r\n") -// } +fn main() { + if let Err(err) = dispatch() { + eprintln!("Error: {}", err); + exit(1); + } +} + +fn new_email_tpl() -> String { + ["To: ", "Subject: ", ""].join("\r\n") +} // fn forward_email_tpl() -> String { // ["To: ", "Subject: ", ""].join("\r\n") @@ -35,12 +46,6 @@ fn uid_arg() -> Arg<'static, 'static> { .required(true) } -fn main() { - if let Err(err) = dispatch() { - panic!(err); - } -} - fn dispatch() -> Result<(), imap::Error> { let matches = App::new("Himalaya") .version("0.1.0") @@ -140,63 +145,68 @@ fn dispatch() -> Result<(), imap::Error> { } } - // if let Some(matches) = matches.subcommand_matches("read") { - // let mbox = matches.value_of("mailbox").unwrap(); - // let mime = matches.value_of("mime-type").unwrap(); - // let uid = matches.value_of("uid").unwrap(); + if let Some(matches) = matches.subcommand_matches("read") { + let config = Config::new_from_file(); + let mbox = matches.value_of("mailbox").unwrap(); + let uid = matches.value_of("uid").unwrap(); + let mime = matches.value_of("mime-type").unwrap(); + let email = ImapConnector::new(config.imap)?.read_email(&mbox, &uid, &mime)?; - // imap::read_email(&mut imap_sess, mbox, uid, mime).unwrap(); - // } + println!("{}", email); + } - // if let Some(_) = matches.subcommand_matches("write") { - // let mut draft_path = env::temp_dir(); - // draft_path.push("himalaya-draft.mail"); + if let Some(_) = matches.subcommand_matches("write") { + let config = Config::new_from_file(); - // fs::File::create(&draft_path) - // .expect("Could not create draft file") - // .write(new_email_tpl().as_bytes()) - // .expect("Could not write into draft file"); + let mut draft_path = temp_dir(); + draft_path.push("himalaya-draft.mail"); - // process::Command::new(env!("EDITOR")) - // .arg(&draft_path) - // .status() - // .expect("Could not start $EDITOR"); + File::create(&draft_path) + .expect("Could not create draft file") + .write(new_email_tpl().as_bytes()) + .expect("Could not write into draft file"); - // let mut draft = String::new(); - // fs::File::open(&draft_path) - // .expect("Could not open draft file") - // .read_to_string(&mut draft) - // .expect("Could not read draft file"); + Command::new(env!("EDITOR")) + .arg(&draft_path) + .status() + .expect("Could not start $EDITOR"); - // fs::remove_file(&draft_path).expect("Could not remove draft file"); + let mut draft = String::new(); + File::open(&draft_path) + .expect("Could not open draft file") + .read_to_string(&mut draft) + .expect("Could not read draft file"); - // smtp::send(&config, &draft.as_bytes()); - // } + remove_file(&draft_path).expect("Could not remove draft file"); + + smtp::send(&config, &draft.as_bytes()); + } // if let Some(_) = matches.subcommand_matches("forward") { + // let config = Config::new_from_file(); // let mbox = matches.value_of("mailbox").unwrap(); // let uid = matches.value_of("uid").unwrap(); - // let mut draft_path = env::temp_dir(); + // let mut draft_path = temp_dir(); // draft_path.push("himalaya-draft.mail"); - // fs::File::create(&draft_path) + // File::create(&draft_path) // .expect("Could not create draft file") // .write(forward_email_tpl().as_bytes()) // .expect("Could not write into draft file"); - // process::Command::new(env!("EDITOR")) + // Command::new(env!("EDITOR")) // .arg(&draft_path) // .status() // .expect("Could not start $EDITOR"); // let mut draft = String::new(); - // fs::File::open(&draft_path) + // File::open(&draft_path) // .expect("Could not open draft file") // .read_to_string(&mut draft) // .expect("Could not read draft file"); - // fs::remove_file(&draft_path).expect("Could not remove draft file"); + // remove_file(&draft_path).expect("Could not remove draft file"); // smtp::send(&config, &draft.as_bytes()); // }