deltachat/imap/
session.rs

1use std::collections::BTreeMap;
2use std::ops::{Deref, DerefMut};
3
4use anyhow::{Context as _, Result};
5use async_imap::types::Mailbox;
6use async_imap::Session as ImapSession;
7use futures::TryStreamExt;
8
9use crate::imap::capabilities::Capabilities;
10use crate::net::session::SessionStream;
11
12/// Prefetch:
13/// - Message-ID to check if we already have the message.
14/// - In-Reply-To and References to check if message is a reply to chat message.
15/// - Chat-Version to check if a message is a chat message
16/// - Autocrypt-Setup-Message to check if a message is an autocrypt setup message,
17///   not necessarily sent by Delta Chat.
18const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIELDS (\
19                              MESSAGE-ID \
20                              DATE \
21                              X-MICROSOFT-ORIGINAL-MESSAGE-ID \
22                              FROM \
23                              IN-REPLY-TO REFERENCES \
24                              CHAT-VERSION \
25                              AUTO-SUBMITTED \
26                              AUTOCRYPT-SETUP-MESSAGE\
27                              )])";
28
29#[derive(Debug)]
30pub(crate) struct Session {
31    pub(super) inner: ImapSession<Box<dyn SessionStream>>,
32
33    pub capabilities: Capabilities,
34
35    /// Selected folder name.
36    pub selected_folder: Option<String>,
37
38    /// Mailbox structure returned by IMAP server.
39    pub selected_mailbox: Option<Mailbox>,
40
41    pub selected_folder_needs_expunge: bool,
42
43    /// True if currently selected folder has new messages.
44    ///
45    /// Should be false if no folder is currently selected.
46    pub new_mail: bool,
47}
48
49impl Deref for Session {
50    type Target = ImapSession<Box<dyn SessionStream>>;
51
52    fn deref(&self) -> &Self::Target {
53        &self.inner
54    }
55}
56
57impl DerefMut for Session {
58    fn deref_mut(&mut self) -> &mut Self::Target {
59        &mut self.inner
60    }
61}
62
63impl Session {
64    pub(crate) fn new(
65        inner: ImapSession<Box<dyn SessionStream>>,
66        capabilities: Capabilities,
67    ) -> Self {
68        Self {
69            inner,
70            capabilities,
71            selected_folder: None,
72            selected_mailbox: None,
73            selected_folder_needs_expunge: false,
74            new_mail: false,
75        }
76    }
77
78    pub fn can_idle(&self) -> bool {
79        self.capabilities.can_idle
80    }
81
82    pub fn can_move(&self) -> bool {
83        self.capabilities.can_move
84    }
85
86    pub fn can_check_quota(&self) -> bool {
87        self.capabilities.can_check_quota
88    }
89
90    pub fn can_condstore(&self) -> bool {
91        self.capabilities.can_condstore
92    }
93
94    pub fn can_metadata(&self) -> bool {
95        self.capabilities.can_metadata
96    }
97
98    pub fn can_push(&self) -> bool {
99        self.capabilities.can_push
100    }
101
102    // Returns true if IMAP server has `XCHATMAIL` capability.
103    pub fn is_chatmail(&self) -> bool {
104        self.capabilities.is_chatmail
105    }
106
107    /// Returns the names of all folders on the IMAP server.
108    pub async fn list_folders(&mut self) -> Result<Vec<async_imap::types::Name>> {
109        let list = self.list(Some(""), Some("*")).await?.try_collect().await?;
110        Ok(list)
111    }
112
113    /// Prefetch all messages greater than or equal to `uid_next`. Returns a list of fetch results
114    /// in the order of ascending delivery time to the server (INTERNALDATE).
115    pub(crate) async fn prefetch(
116        &mut self,
117        uid_next: u32,
118    ) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
119        // fetch messages with larger UID than the last one seen
120        let set = format!("{uid_next}:*");
121        let mut list = self
122            .uid_fetch(set, PREFETCH_FLAGS)
123            .await
124            .context("IMAP could not fetch")?;
125
126        let mut msgs = BTreeMap::new();
127        while let Some(msg) = list.try_next().await? {
128            if let Some(msg_uid) = msg.uid {
129                // If the mailbox is not empty, results always include
130                // at least one UID, even if last_seen_uid+1 is past
131                // the last UID in the mailbox.  It happens because
132                // uid:* is interpreted the same way as *:uid.
133                // See <https://tools.ietf.org/html/rfc3501#page-61> for
134                // standard reference. Therefore, sometimes we receive
135                // already seen messages and have to filter them out.
136                if msg_uid >= uid_next {
137                    msgs.insert((msg.internal_date(), msg_uid), msg);
138                }
139            }
140        }
141
142        Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
143    }
144}