//! IMAP connection and small protocol helpers. use std::net::TcpStream; use anyhow::{anyhow, Result}; use native_tls::{TlsConnector, TlsStream}; use crate::config::Account; pub type ImapSession = imap::Session>; /// Opens a logged-in IMAP session, using STARTTLS or implicit TLS as configured. pub fn connect(acc: &Account) -> Result { let (ep, pass) = acc.imap()?; let tls = TlsConnector::new()?; let client = if ep.implicit_tls { imap::connect((ep.host.as_str(), ep.port), ep.host.as_str(), &tls)? } else { imap::connect_starttls((ep.host.as_str(), ep.port), ep.host.as_str(), &tls)? }; client.login(&ep.user, &pass).map_err(|e| { anyhow!( "IMAP login failed for {} (account '{}'): {:?}", ep.user, acc.name, e.0 ) }) } /// Quotes a mailbox name when it needs it (spaces, quotes, parentheses). pub fn quote_mailbox(name: &str) -> String { if name.starts_with('"') || !name.contains([' ', '"', '(', ')']) { name.to_string() } else { format!("\"{}\"", name.replace('"', "\\\"")) } } /// Renders a UID list as an IMAP sequence set. pub fn uid_set(uids: &[u32]) -> String { uids.iter() .map(u32::to_string) .collect::>() .join(",") } /// Moves messages, preferring `UID MOVE` with a COPY+EXPUNGE fallback for /// servers without the MOVE capability. pub fn move_uids(session: &mut ImapSession, uids: &[u32], to: &str) -> Result<()> { let set = uid_set(uids); let dest = quote_mailbox(to); if session.uid_mv(&set, &dest).is_ok() { return Ok(()); } session.uid_copy(&set, &dest)?; session.uid_store(&set, "+FLAGS (\\Deleted)")?; session.expunge()?; Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn plain_names_stay_unquoted() { assert_eq!(quote_mailbox("INBOX"), "INBOX"); assert_eq!(quote_mailbox("Archives/2025"), "Archives/2025"); } #[test] fn names_with_spaces_get_quoted() { assert_eq!(quote_mailbox("Sent Mail"), "\"Sent Mail\""); } #[test] fn embedded_quotes_are_escaped() { assert_eq!(quote_mailbox("a\"b"), "\"a\\\"b\""); } #[test] fn already_quoted_names_pass_through() { assert_eq!(quote_mailbox("\"Sent Mail\""), "\"Sent Mail\""); } #[test] fn uid_sets_join_with_commas() { assert_eq!(uid_set(&[7, 9, 11]), "7,9,11"); assert_eq!(uid_set(&[42]), "42"); } }