asimov_imap_module/
reader.rs

1// This is free and unencumbered software released into the public domain.
2
3use super::{
4    ImapCapabilities, ImapError, ImapIterator, ImapLocalCursor, ImapMessage, ImapOrderBy,
5    ImapRemoteCursor, ImapUrl,
6};
7use asimov_module::tracing;
8use core::error::Error;
9use imap::{ClientBuilder, ConnectionMode, ImapConnection, Session, TlsKind, types::Mailbox};
10use know::datatypes::EmailMessageId;
11use secrecy::ExposeSecret;
12
13#[allow(unused)]
14pub struct ImapReader {
15    session: Session<Box<dyn ImapConnection + 'static>>,
16    capabilities: ImapCapabilities,
17    mailbox: Mailbox,
18}
19
20impl ImapReader {
21    pub fn open(url: &ImapUrl) -> imap::Result<Self> {
22        let client = ClientBuilder::new(&url.host, url.port)
23            .mode(ConnectionMode::Tls)
24            .tls_kind(TlsKind::Rust)
25            .danger_skip_tls_verify(true)
26            .connect()?;
27
28        let mut session = client
29            .login(
30                url.user.clone().unwrap_or_else(|| "anonymous".to_string()),
31                url.password
32                    .as_ref()
33                    .map(|password| password.expose_secret())
34                    .unwrap_or_default(),
35            )
36            .map_err(|e| e.0)?;
37
38        let capabilities: ImapCapabilities = session.capabilities()?.into();
39        tracing::trace!("{:?}", capabilities);
40
41        if capabilities.utf8_accept {
42            // This is for now blocked by a decoding bug in the `imap` crate.
43            // See: https://github.com/asimov-modules/asimov-imap-module/issues/3
44            // See: https://github.com/jonhoo/rust-imap/issues/311
45            //session.run_command_and_check_ok("ENABLE UTF8=ACCEPT")?;
46        }
47
48        let mailbox = session.select(&url.mailbox)?;
49        Ok(Self {
50            session,
51            capabilities,
52            mailbox,
53        })
54    }
55
56    pub fn close(&mut self) -> imap::Result<()> {
57        self.session.logout()
58    }
59
60    pub fn iter(
61        &mut self,
62        order_by: ImapOrderBy,
63        limit: Option<usize>,
64    ) -> imap::Result<impl Iterator<Item = Result<ImapMessage, Box<dyn Error>>>> {
65        let fetch_query = "(UID FLAGS ENVELOPE)";
66        Ok(match (self.capabilities.sort, &order_by) {
67            (_, ImapOrderBy::None) => {
68                ImapIterator::new(self.session.fetch("1:*", fetch_query)?, None)
69            },
70            (true, _) => {
71                let cursor = ImapRemoteCursor::by(&mut self.session, order_by)?.limit(limit);
72                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
73                ImapIterator::new(fetches, Some(cursor.to_vec()))
74            },
75            (false, ImapOrderBy::Timestamp) => {
76                let cursor = ImapLocalCursor::<i64>::by_timestamp(&mut self.session)?.limit(limit);
77                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
78                ImapIterator::new(fetches, Some(cursor.to_vec()))
79            },
80            (false, ImapOrderBy::Date) => {
81                let cursor = ImapLocalCursor::<i64>::by_date(&mut self.session)?.limit(limit);
82                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
83                ImapIterator::new(fetches, Some(cursor.to_vec()))
84            },
85            (false, ImapOrderBy::From) => {
86                let cursor = ImapLocalCursor::<String>::by_from(&mut self.session)?.limit(limit);
87                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
88                ImapIterator::new(fetches, Some(cursor.to_vec()))
89            },
90            (false, ImapOrderBy::To) => {
91                let cursor = ImapLocalCursor::<String>::by_to(&mut self.session)?.limit(limit);
92                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
93                ImapIterator::new(fetches, Some(cursor.to_vec()))
94            },
95            (false, ImapOrderBy::Cc) => {
96                let cursor = ImapLocalCursor::<String>::by_cc(&mut self.session)?.limit(limit);
97                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
98                ImapIterator::new(fetches, Some(cursor.to_vec()))
99            },
100            (false, ImapOrderBy::Size) => {
101                let cursor = ImapLocalCursor::<usize>::by_size(&mut self.session)?.limit(limit);
102                let fetches = self.session.uid_fetch(cursor.to_string(), fetch_query)?;
103                ImapIterator::new(fetches, Some(cursor.to_vec()))
104            },
105        })
106    }
107
108    pub fn fetch(&mut self, message_id: &EmailMessageId) -> Result<Option<ImapMessage>, ImapError> {
109        let message_uid = self
110            .session
111            .uid_search(format!("HEADER Message-ID <{}>", message_id.as_str()))?
112            .into_iter()
113            .next();
114        let Some(message_uid) = message_uid else {
115            return Ok(None);
116        };
117        let fetch_results = self
118            .session
119            .uid_fetch(message_uid.to_string(), "(BODY[])")?;
120        let Some(fetch_result) = fetch_results.get(0) else {
121            return Ok(None);
122        };
123        Ok(Some(fetch_result.try_into()?))
124    }
125}