1use std::{
7 cmp::max,
8 cmp::min,
9 collections::{BTreeMap, BTreeSet, HashMap},
10 iter::Peekable,
11 mem::take,
12 sync::atomic::Ordering,
13 time::{Duration, UNIX_EPOCH},
14};
15
16use anyhow::{Context as _, Result, bail, ensure, format_err};
17use async_channel::{self, Receiver, Sender};
18use async_imap::types::{Fetch, Flag, Name, NameAttribute, UnsolicitedResponse};
19use deltachat_contact_tools::ContactAddress;
20use futures::{FutureExt as _, TryStreamExt};
21use futures_lite::FutureExt;
22use num_traits::FromPrimitive;
23use rand::Rng;
24use ratelimit::Ratelimit;
25use url::Url;
26
27use crate::calls::{create_fallback_ice_servers, create_ice_servers_from_metadata};
28use crate::chat::{self, ChatId, ChatIdBlocked};
29use crate::chatlist_events;
30use crate::config::Config;
31use crate::constants::{self, Blocked, Chattype, ShowEmails};
32use crate::contact::{Contact, ContactId, Modifier, Origin};
33use crate::context::Context;
34use crate::events::EventType;
35use crate::headerdef::{HeaderDef, HeaderDefMap};
36use crate::log::{LogExt, error, info, warn};
37use crate::login_param::{
38 ConfiguredLoginParam, ConfiguredServerLoginParam, prioritize_server_login_params,
39};
40use crate::message::{self, Message, MessageState, MessengerMessage, MsgId};
41use crate::mimeparser;
42use crate::net::proxy::ProxyConfig;
43use crate::net::session::SessionStream;
44use crate::oauth2::get_oauth2_access_token;
45use crate::push::encrypt_device_token;
46use crate::receive_imf::{
47 ReceivedMsg, from_field_to_contact_id, get_prefetch_parent_message, receive_imf_inner,
48};
49use crate::scheduler::connectivity::ConnectivityStore;
50use crate::stock_str;
51use crate::tools::{self, create_id, duration_to_str, time};
52
53pub(crate) mod capabilities;
54mod client;
55mod idle;
56pub mod scan_folders;
57pub mod select_folder;
58pub(crate) mod session;
59
60use client::{Client, determine_capabilities};
61use mailparse::SingleInfo;
62use session::Session;
63
64pub(crate) const GENERATED_PREFIX: &str = "GEN_";
65
66const RFC724MID_UID: &str = "(UID BODY.PEEK[HEADER.FIELDS (\
67 MESSAGE-ID \
68 X-MICROSOFT-ORIGINAL-MESSAGE-ID\
69 )])";
70const BODY_FULL: &str = "(FLAGS BODY.PEEK[])";
71const BODY_PARTIAL: &str = "(FLAGS RFC822.SIZE BODY.PEEK[HEADER])";
72
73#[derive(Debug)]
74pub(crate) struct Imap {
75 pub(crate) idle_interrupt_receiver: Receiver<()>,
76
77 addr: String,
79
80 lp: Vec<ConfiguredServerLoginParam>,
82
83 password: String,
85
86 proxy_config: Option<ProxyConfig>,
88
89 strict_tls: bool,
90
91 oauth2: bool,
92
93 authentication_failed_once: bool,
94
95 pub(crate) connectivity: ConnectivityStore,
96
97 conn_last_try: tools::Time,
98 conn_backoff_ms: u64,
99
100 ratelimit: Ratelimit,
108}
109
110#[derive(Debug)]
111struct OAuth2 {
112 user: String,
113 access_token: String,
114}
115
116#[derive(Debug)]
117pub(crate) struct ServerMetadata {
118 pub comment: Option<String>,
121
122 pub admin: Option<String>,
125
126 pub iroh_relay: Option<Url>,
127
128 pub ice_servers: String,
135
136 pub ice_servers_expiration_timestamp: i64,
139}
140
141impl async_imap::Authenticator for OAuth2 {
142 type Response = String;
143
144 fn process(&mut self, _data: &[u8]) -> Self::Response {
145 format!(
146 "user={}\x01auth=Bearer {}\x01\x01",
147 self.user, self.access_token
148 )
149 }
150}
151
152#[derive(Debug, Display, PartialEq, Eq, Clone, Copy)]
153pub enum FolderMeaning {
154 Unknown,
155
156 Spam,
158 Inbox,
159 Mvbox,
160 Sent,
161 Trash,
162
163 Virtual,
170}
171
172impl FolderMeaning {
173 pub fn to_config(self) -> Option<Config> {
174 match self {
175 FolderMeaning::Unknown => None,
176 FolderMeaning::Spam => None,
177 FolderMeaning::Inbox => Some(Config::ConfiguredInboxFolder),
178 FolderMeaning::Mvbox => Some(Config::ConfiguredMvboxFolder),
179 FolderMeaning::Sent => Some(Config::ConfiguredSentboxFolder),
180 FolderMeaning::Trash => Some(Config::ConfiguredTrashFolder),
181 FolderMeaning::Virtual => None,
182 }
183 }
184}
185
186struct UidGrouper<T: Iterator<Item = (i64, u32, String)>> {
187 inner: Peekable<T>,
188}
189
190impl<T, I> From<I> for UidGrouper<T>
191where
192 T: Iterator<Item = (i64, u32, String)>,
193 I: IntoIterator<IntoIter = T>,
194{
195 fn from(inner: I) -> Self {
196 Self {
197 inner: inner.into_iter().peekable(),
198 }
199 }
200}
201
202impl<T: Iterator<Item = (i64, u32, String)>> Iterator for UidGrouper<T> {
203 type Item = (String, Vec<i64>, String);
205
206 fn next(&mut self) -> Option<Self::Item> {
207 let (_, _, folder) = self.inner.peek().cloned()?;
208
209 let mut uid_set = String::new();
210 let mut rowid_set = Vec::new();
211
212 while uid_set.len() < 1000 {
213 if let Some((start_rowid, start_uid, _)) = self
215 .inner
216 .next_if(|(_, _, start_folder)| start_folder == &folder)
217 {
218 rowid_set.push(start_rowid);
219 let mut end_uid = start_uid;
220
221 while let Some((next_rowid, next_uid, _)) =
222 self.inner.next_if(|(_, next_uid, next_folder)| {
223 next_folder == &folder && (*next_uid == end_uid + 1 || *next_uid == end_uid)
224 })
225 {
226 end_uid = next_uid;
227 rowid_set.push(next_rowid);
228 }
229
230 let uid_range = UidRange {
231 start: start_uid,
232 end: end_uid,
233 };
234 if !uid_set.is_empty() {
235 uid_set.push(',');
236 }
237 uid_set.push_str(&uid_range.to_string());
238 } else {
239 break;
240 }
241 }
242
243 Some((folder, rowid_set, uid_set))
244 }
245}
246
247impl Imap {
248 pub fn new(
252 lp: Vec<ConfiguredServerLoginParam>,
253 password: String,
254 proxy_config: Option<ProxyConfig>,
255 addr: &str,
256 strict_tls: bool,
257 oauth2: bool,
258 idle_interrupt_receiver: Receiver<()>,
259 ) -> Self {
260 Imap {
261 idle_interrupt_receiver,
262 addr: addr.to_string(),
263 lp,
264 password,
265 proxy_config,
266 strict_tls,
267 oauth2,
268 authentication_failed_once: false,
269 connectivity: Default::default(),
270 conn_last_try: UNIX_EPOCH,
271 conn_backoff_ms: 0,
272 ratelimit: Ratelimit::new(Duration::new(120, 0), 2.0),
274 }
275 }
276
277 pub async fn new_configured(
279 context: &Context,
280 idle_interrupt_receiver: Receiver<()>,
281 ) -> Result<Self> {
282 let param = ConfiguredLoginParam::load(context)
283 .await?
284 .context("Not configured")?;
285 let proxy_config = ProxyConfig::load(context).await?;
286 let strict_tls = param.strict_tls(proxy_config.is_some());
287 let imap = Self::new(
288 param.imap.clone(),
289 param.imap_password.clone(),
290 proxy_config,
291 ¶m.addr,
292 strict_tls,
293 param.oauth2,
294 idle_interrupt_receiver,
295 );
296 Ok(imap)
297 }
298
299 pub(crate) async fn connect(
305 &mut self,
306 context: &Context,
307 configuring: bool,
308 ) -> Result<Session> {
309 let now = tools::Time::now();
310 let until_can_send = max(
311 min(self.conn_last_try, now)
312 .checked_add(Duration::from_millis(self.conn_backoff_ms))
313 .unwrap_or(now),
314 now,
315 )
316 .duration_since(now)?;
317 let ratelimit_duration = max(until_can_send, self.ratelimit.until_can_send());
318 if !ratelimit_duration.is_zero() {
319 warn!(
320 context,
321 "IMAP got rate limited, waiting for {} until can connect.",
322 duration_to_str(ratelimit_duration),
323 );
324 let interrupted = async {
325 tokio::time::sleep(ratelimit_duration).await;
326 false
327 }
328 .race(self.idle_interrupt_receiver.recv().map(|_| true))
329 .await;
330 if interrupted {
331 info!(
332 context,
333 "Connecting to IMAP without waiting for ratelimit due to interrupt."
334 );
335 }
336 }
337
338 info!(context, "Connecting to IMAP server.");
339 self.connectivity.set_connecting(context);
340
341 self.conn_last_try = tools::Time::now();
342 const BACKOFF_MIN_MS: u64 = 2000;
343 const BACKOFF_MAX_MS: u64 = 80_000;
344 self.conn_backoff_ms = min(self.conn_backoff_ms, BACKOFF_MAX_MS / 2);
345 self.conn_backoff_ms = self.conn_backoff_ms.saturating_add(
346 rand::thread_rng().gen_range((self.conn_backoff_ms / 2)..=self.conn_backoff_ms),
347 );
348 self.conn_backoff_ms = max(BACKOFF_MIN_MS, self.conn_backoff_ms);
349
350 let login_params = prioritize_server_login_params(&context.sql, &self.lp, "imap").await?;
351 let mut first_error = None;
352 for lp in login_params {
353 info!(context, "IMAP trying to connect to {}.", &lp.connection);
354 let connection_candidate = lp.connection.clone();
355 let client = match Client::connect(
356 context,
357 self.proxy_config.clone(),
358 self.strict_tls,
359 connection_candidate,
360 )
361 .await
362 .context("IMAP failed to connect")
363 {
364 Ok(client) => client,
365 Err(err) => {
366 warn!(context, "{err:#}.");
367 first_error.get_or_insert(err);
368 continue;
369 }
370 };
371
372 self.conn_backoff_ms = BACKOFF_MIN_MS;
373 self.ratelimit.send();
374
375 let imap_user: &str = lp.user.as_ref();
376 let imap_pw: &str = &self.password;
377
378 let login_res = if self.oauth2 {
379 info!(context, "Logging into IMAP server with OAuth 2.");
380 let addr: &str = self.addr.as_ref();
381
382 let token = get_oauth2_access_token(context, addr, imap_pw, true)
383 .await?
384 .context("IMAP could not get OAUTH token")?;
385 let auth = OAuth2 {
386 user: imap_user.into(),
387 access_token: token,
388 };
389 client.authenticate("XOAUTH2", auth).await
390 } else {
391 info!(context, "Logging into IMAP server with LOGIN.");
392 client.login(imap_user, imap_pw).await
393 };
394
395 match login_res {
396 Ok(mut session) => {
397 let capabilities = determine_capabilities(&mut session).await?;
398
399 let session = if capabilities.can_compress {
400 info!(context, "Enabling IMAP compression.");
401 let compressed_session = session
402 .compress(|s| {
403 let session_stream: Box<dyn SessionStream> = Box::new(s);
404 session_stream
405 })
406 .await
407 .context("Failed to enable IMAP compression")?;
408 Session::new(compressed_session, capabilities)
409 } else {
410 Session::new(session, capabilities)
411 };
412
413 let mut lock = context.server_id.write().await;
415 lock.clone_from(&session.capabilities.server_id);
416
417 self.authentication_failed_once = false;
418 context.emit_event(EventType::ImapConnected(format!(
419 "IMAP-LOGIN as {}",
420 lp.user
421 )));
422 self.connectivity.set_preparing(context);
423 info!(context, "Successfully logged into IMAP server.");
424 return Ok(session);
425 }
426
427 Err(err) => {
428 let imap_user = lp.user.to_owned();
429 let message = stock_str::cannot_login(context, &imap_user).await;
430
431 warn!(context, "IMAP failed to login: {err:#}.");
432 first_error.get_or_insert(format_err!("{message} ({err:#})"));
433
434 let _lock = context.wrong_pw_warning_mutex.lock().await;
436 if err.to_string().to_lowercase().contains("authentication") {
437 if self.authentication_failed_once
438 && !configuring
439 && context.get_config_bool(Config::NotifyAboutWrongPw).await?
440 {
441 let mut msg = Message::new_text(message);
442 if let Err(e) = chat::add_device_msg_with_importance(
443 context,
444 None,
445 Some(&mut msg),
446 true,
447 )
448 .await
449 {
450 warn!(context, "Failed to add device message: {e:#}.");
451 } else {
452 context
453 .set_config_internal(Config::NotifyAboutWrongPw, None)
454 .await
455 .log_err(context)
456 .ok();
457 }
458 } else {
459 self.authentication_failed_once = true;
460 }
461 } else {
462 self.authentication_failed_once = false;
463 }
464 }
465 }
466 }
467
468 Err(first_error.unwrap_or_else(|| format_err!("No IMAP connection candidates provided")))
469 }
470
471 pub(crate) async fn prepare(&mut self, context: &Context) -> Result<Session> {
476 let configuring = false;
477 let mut session = match self.connect(context, configuring).await {
478 Ok(session) => session,
479 Err(err) => {
480 self.connectivity.set_err(context, &err);
481 return Err(err);
482 }
483 };
484
485 let folders_configured = context
486 .sql
487 .get_raw_config_int(constants::DC_FOLDERS_CONFIGURED_KEY)
488 .await?;
489 if folders_configured.unwrap_or_default() < constants::DC_FOLDERS_CONFIGURED_VERSION {
490 let is_chatmail = match context.get_config_bool(Config::FixIsChatmail).await? {
491 false => session.is_chatmail(),
492 true => context.get_config_bool(Config::IsChatmail).await?,
493 };
494 let create_mvbox = !is_chatmail || context.get_config_bool(Config::MvboxMove).await?;
495 self.configure_folders(context, &mut session, create_mvbox)
496 .await?;
497 }
498
499 Ok(session)
500 }
501
502 pub async fn fetch_move_delete(
507 &mut self,
508 context: &Context,
509 session: &mut Session,
510 watch_folder: &str,
511 folder_meaning: FolderMeaning,
512 ) -> Result<()> {
513 if !context.sql.is_open().await {
514 bail!("IMAP operation attempted while it is torn down");
516 }
517
518 let msgs_fetched = self
519 .fetch_new_messages(context, session, watch_folder, folder_meaning)
520 .await
521 .context("fetch_new_messages")?;
522 if msgs_fetched && context.get_config_delete_device_after().await?.is_some() {
523 context.scheduler.interrupt_ephemeral_task().await;
528 }
529
530 session
531 .move_delete_messages(context, watch_folder)
532 .await
533 .context("move_delete_messages")?;
534
535 Ok(())
536 }
537
538 pub(crate) async fn fetch_new_messages(
542 &mut self,
543 context: &Context,
544 session: &mut Session,
545 folder: &str,
546 folder_meaning: FolderMeaning,
547 ) -> Result<bool> {
548 if should_ignore_folder(context, folder, folder_meaning).await? {
549 info!(context, "Not fetching from {folder:?}.");
550 session.new_mail = false;
551 return Ok(false);
552 }
553
554 let create = false;
555 let folder_exists = session
556 .select_with_uidvalidity(context, folder, create)
557 .await
558 .with_context(|| format!("Failed to select folder {folder:?}"))?;
559 if !folder_exists {
560 return Ok(false);
561 }
562
563 if !session.new_mail {
564 info!(context, "No new emails in folder {folder:?}.");
565 return Ok(false);
566 }
567 session.new_mail = false;
568
569 let mut read_cnt = 0;
570 loop {
571 let (n, fetch_more) = self
572 .fetch_new_msg_batch(context, session, folder, folder_meaning)
573 .await?;
574 read_cnt += n;
575 if !fetch_more {
576 return Ok(read_cnt > 0);
577 }
578 }
579 }
580
581 async fn fetch_new_msg_batch(
583 &mut self,
584 context: &Context,
585 session: &mut Session,
586 folder: &str,
587 folder_meaning: FolderMeaning,
588 ) -> Result<(usize, bool)> {
589 let uid_validity = get_uidvalidity(context, folder).await?;
590 let old_uid_next = get_uid_next(context, folder).await?;
591 info!(
592 context,
593 "fetch_new_msg_batch({folder}): UIDVALIDITY={uid_validity}, UIDNEXT={old_uid_next}."
594 );
595
596 let uids_to_prefetch = 500;
597 let msgs = session
598 .prefetch(old_uid_next, uids_to_prefetch)
599 .await
600 .context("prefetch")?;
601 let read_cnt = msgs.len();
602
603 let download_limit = context.download_limit().await?;
604 let mut uids_fetch = Vec::<(u32, bool )>::with_capacity(msgs.len() + 1);
605 let mut uid_message_ids = BTreeMap::new();
606 let mut largest_uid_skipped = None;
607 let delete_target = context.get_delete_msgs_target().await?;
608
609 for (uid, ref fetch_response) in msgs {
611 let headers = match get_fetch_headers(fetch_response) {
612 Ok(headers) => headers,
613 Err(err) => {
614 warn!(context, "Failed to parse FETCH headers: {err:#}.");
615 continue;
616 }
617 };
618
619 let message_id = prefetch_get_message_id(&headers);
620
621 let _target;
634 let target = if let Some(message_id) = &message_id {
635 let msg_info =
636 message::rfc724_mid_exists_ex(context, message_id, "deleted=1").await?;
637 let delete = if let Some((_, _, true)) = msg_info {
638 info!(context, "Deleting locally deleted message {message_id}.");
639 true
640 } else if let Some((_, ts_sent_old, _)) = msg_info {
641 let is_chat_msg = headers.get_header_value(HeaderDef::ChatVersion).is_some();
642 let ts_sent = headers
643 .get_header_value(HeaderDef::Date)
644 .and_then(|v| mailparse::dateparse(&v).ok())
645 .unwrap_or_default();
646 let is_dup = is_dup_msg(is_chat_msg, ts_sent, ts_sent_old);
647 if is_dup {
648 info!(context, "Deleting duplicate message {message_id}.");
649 }
650 is_dup
651 } else {
652 false
653 };
654 if delete {
655 &delete_target
656 } else if context
657 .sql
658 .exists(
659 "SELECT COUNT (*) FROM imap WHERE rfc724_mid=?",
660 (message_id,),
661 )
662 .await?
663 {
664 info!(
665 context,
666 "Not moving the message {} that we have seen before.", &message_id
667 );
668 folder
669 } else {
670 _target = target_folder(context, folder, folder_meaning, &headers).await?;
671 &_target
672 }
673 } else {
674 warn!(
678 context,
679 "Not moving the message that does not have a Message-ID."
680 );
681 folder
682 };
683
684 let message_id = message_id.unwrap_or_else(create_message_id);
687
688 context
689 .sql
690 .execute(
691 "INSERT INTO imap (rfc724_mid, folder, uid, uidvalidity, target)
692 VALUES (?1, ?2, ?3, ?4, ?5)
693 ON CONFLICT(folder, uid, uidvalidity)
694 DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
695 target=excluded.target",
696 (&message_id, &folder, uid, uid_validity, target),
697 )
698 .await?;
699
700 if folder == target
707 && folder_meaning != FolderMeaning::Spam
712 && prefetch_should_download(
713 context,
714 &headers,
715 &message_id,
716 fetch_response.flags(),
717 )
718 .await.context("prefetch_should_download")?
719 {
720 match download_limit {
721 Some(download_limit) => uids_fetch.push((
722 uid,
723 fetch_response.size.unwrap_or_default() > download_limit,
724 )),
725 None => uids_fetch.push((uid, false)),
726 }
727 uid_message_ids.insert(uid, message_id);
728 } else {
729 largest_uid_skipped = Some(uid);
730 }
731 }
732
733 if !uids_fetch.is_empty() {
734 self.connectivity.set_working(context);
735 }
736
737 let (sender, receiver) = async_channel::unbounded();
738
739 let mut received_msgs = Vec::with_capacity(uids_fetch.len());
740 let mailbox_uid_next = session
741 .selected_mailbox
742 .as_ref()
743 .with_context(|| format!("Expected {folder:?} to be selected"))?
744 .uid_next
745 .unwrap_or_default();
746
747 let update_uids_future = async {
748 let mut largest_uid_fetched: u32 = 0;
749
750 while let Ok((uid, received_msg_opt)) = receiver.recv().await {
751 largest_uid_fetched = max(largest_uid_fetched, uid);
752 if let Some(received_msg) = received_msg_opt {
753 received_msgs.push(received_msg)
754 }
755 }
756
757 largest_uid_fetched
758 };
759
760 let actually_download_messages_future = async {
761 let sender = sender;
762 let mut uids_fetch_in_batch = Vec::with_capacity(max(uids_fetch.len(), 1));
763 let mut fetch_partially = false;
764 uids_fetch.push((0, !uids_fetch.last().unwrap_or(&(0, false)).1));
765 for (uid, fp) in uids_fetch {
766 if fp != fetch_partially {
767 session
768 .fetch_many_msgs(
769 context,
770 folder,
771 uid_validity,
772 uids_fetch_in_batch.split_off(0),
773 &uid_message_ids,
774 fetch_partially,
775 sender.clone(),
776 )
777 .await
778 .context("fetch_many_msgs")?;
779 fetch_partially = fp;
780 }
781 uids_fetch_in_batch.push(uid);
782 }
783
784 anyhow::Ok(())
785 };
786
787 let (largest_uid_fetched, fetch_res) =
788 tokio::join!(update_uids_future, actually_download_messages_future);
789
790 let mut new_uid_next = largest_uid_fetched + 1;
796 let fetch_more = fetch_res.is_ok() && {
797 let prefetch_uid_next = old_uid_next + uids_to_prefetch;
798 new_uid_next = max(new_uid_next, min(prefetch_uid_next, mailbox_uid_next));
802
803 new_uid_next = max(new_uid_next, largest_uid_skipped.unwrap_or(0) + 1);
804
805 prefetch_uid_next < mailbox_uid_next
806 };
807 if new_uid_next > old_uid_next {
808 set_uid_next(context, folder, new_uid_next).await?;
809 }
810
811 info!(context, "{} mails read from \"{}\".", read_cnt, folder);
812
813 if !received_msgs.is_empty() {
814 context.emit_event(EventType::IncomingMsgBunch);
815 }
816
817 chat::mark_old_messages_as_noticed(context, received_msgs).await?;
818
819 fetch_res?;
822
823 Ok((read_cnt, fetch_more))
824 }
825
826 pub(crate) async fn fetch_existing_msgs(
832 &mut self,
833 context: &Context,
834 session: &mut Session,
835 ) -> Result<()> {
836 add_all_recipients_as_contacts(context, session, Config::ConfiguredSentboxFolder)
837 .await
838 .context("failed to get recipients from the sentbox")?;
839 add_all_recipients_as_contacts(context, session, Config::ConfiguredMvboxFolder)
840 .await
841 .context("failed to get recipients from the movebox")?;
842 add_all_recipients_as_contacts(context, session, Config::ConfiguredInboxFolder)
843 .await
844 .context("failed to get recipients from the inbox")?;
845
846 info!(context, "Done fetching existing messages.");
847 Ok(())
848 }
849}
850
851impl Session {
852 pub(crate) async fn resync_folders(&mut self, context: &Context) -> Result<()> {
854 let all_folders = self
855 .list_folders()
856 .await
857 .context("listing folders for resync")?;
858 for folder in all_folders {
859 let folder_meaning = get_folder_meaning(&folder);
860 if !matches!(
861 folder_meaning,
862 FolderMeaning::Virtual | FolderMeaning::Unknown
863 ) {
864 self.resync_folder_uids(context, folder.name(), folder_meaning)
865 .await?;
866 }
867 }
868 Ok(())
869 }
870
871 pub(crate) async fn resync_folder_uids(
878 &mut self,
879 context: &Context,
880 folder: &str,
881 folder_meaning: FolderMeaning,
882 ) -> Result<()> {
883 let uid_validity;
884 let mut msgs = BTreeMap::new();
886
887 let create = false;
888 let folder_exists = self
889 .select_with_uidvalidity(context, folder, create)
890 .await?;
891 if folder_exists {
892 let mut list = self
893 .uid_fetch("1:*", RFC724MID_UID)
894 .await
895 .with_context(|| format!("Can't resync folder {folder}"))?;
896 while let Some(fetch) = list.try_next().await? {
897 let headers = match get_fetch_headers(&fetch) {
898 Ok(headers) => headers,
899 Err(err) => {
900 warn!(context, "Failed to parse FETCH headers: {}", err);
901 continue;
902 }
903 };
904 let message_id = prefetch_get_message_id(&headers);
905
906 if let (Some(uid), Some(rfc724_mid)) = (fetch.uid, message_id) {
907 msgs.insert(
908 uid,
909 (
910 rfc724_mid,
911 target_folder(context, folder, folder_meaning, &headers).await?,
912 ),
913 );
914 }
915 }
916
917 info!(
918 context,
919 "resync_folder_uids: Collected {} message IDs in {folder}.",
920 msgs.len(),
921 );
922
923 uid_validity = get_uidvalidity(context, folder).await?;
924 } else {
925 warn!(context, "resync_folder_uids: No folder {folder}.");
926 uid_validity = 0;
927 }
928
929 context
931 .sql
932 .transaction(move |transaction| {
933 transaction.execute("DELETE FROM imap WHERE folder=?", (folder,))?;
934 for (uid, (rfc724_mid, target)) in &msgs {
935 transaction.execute(
938 "INSERT INTO imap (rfc724_mid, folder, uid, uidvalidity, target)
939 VALUES (?1, ?2, ?3, ?4, ?5)
940 ON CONFLICT(folder, uid, uidvalidity)
941 DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
942 target=excluded.target",
943 (rfc724_mid, folder, uid, uid_validity, target),
944 )?;
945 }
946 Ok(())
947 })
948 .await?;
949 Ok(())
950 }
951
952 async fn delete_message_batch(
955 &mut self,
956 context: &Context,
957 uid_set: &str,
958 row_ids: Vec<i64>,
959 ) -> Result<()> {
960 self.add_flag_finalized_with_set(uid_set, "\\Deleted")
962 .await?;
963 context
964 .sql
965 .transaction(|transaction| {
966 let mut stmt = transaction.prepare("DELETE FROM imap WHERE id = ?")?;
967 for row_id in row_ids {
968 stmt.execute((row_id,))?;
969 }
970 Ok(())
971 })
972 .await
973 .context("Cannot remove deleted messages from imap table")?;
974
975 context.emit_event(EventType::ImapMessageDeleted(format!(
976 "IMAP messages {uid_set} marked as deleted"
977 )));
978 Ok(())
979 }
980
981 async fn move_message_batch(
984 &mut self,
985 context: &Context,
986 set: &str,
987 row_ids: Vec<i64>,
988 target: &str,
989 ) -> Result<()> {
990 if self.can_move() {
991 match self.uid_mv(set, &target).await {
992 Ok(()) => {
993 context
995 .sql
996 .transaction(|transaction| {
997 let mut stmt = transaction.prepare("DELETE FROM imap WHERE id = ?")?;
998 for row_id in row_ids {
999 stmt.execute((row_id,))?;
1000 }
1001 Ok(())
1002 })
1003 .await
1004 .context("Cannot delete moved messages from imap table")?;
1005 context.emit_event(EventType::ImapMessageMoved(format!(
1006 "IMAP messages {set} moved to {target}"
1007 )));
1008 return Ok(());
1009 }
1010 Err(err) => {
1011 if context.should_delete_to_trash().await? {
1012 error!(
1013 context,
1014 "Cannot move messages {} to {}, no fallback to COPY/DELETE because \
1015 delete_to_trash is set. Error: {:#}",
1016 set,
1017 target,
1018 err,
1019 );
1020 return Err(err.into());
1021 }
1022 warn!(
1023 context,
1024 "Cannot move messages, fallback to COPY/DELETE {} to {}: {}",
1025 set,
1026 target,
1027 err
1028 );
1029 }
1030 }
1031 }
1032
1033 let copy = !context.is_trash(target).await?;
1036 if copy {
1037 info!(
1038 context,
1039 "Server does not support MOVE, fallback to COPY/DELETE {} to {}", set, target
1040 );
1041 self.uid_copy(&set, &target).await?;
1042 } else {
1043 error!(
1044 context,
1045 "Server does not support MOVE, fallback to DELETE {} to {}", set, target,
1046 );
1047 }
1048 context
1049 .sql
1050 .transaction(|transaction| {
1051 let mut stmt = transaction.prepare("UPDATE imap SET target='' WHERE id = ?")?;
1052 for row_id in row_ids {
1053 stmt.execute((row_id,))?;
1054 }
1055 Ok(())
1056 })
1057 .await
1058 .context("Cannot plan deletion of messages")?;
1059 if copy {
1060 context.emit_event(EventType::ImapMessageMoved(format!(
1061 "IMAP messages {set} copied to {target}"
1062 )));
1063 }
1064 Ok(())
1065 }
1066
1067 async fn move_delete_messages(&mut self, context: &Context, folder: &str) -> Result<()> {
1071 let rows = context
1072 .sql
1073 .query_map(
1074 "SELECT id, uid, target FROM imap
1075 WHERE folder = ?
1076 AND target != folder
1077 ORDER BY target, uid",
1078 (folder,),
1079 |row| {
1080 let rowid: i64 = row.get(0)?;
1081 let uid: u32 = row.get(1)?;
1082 let target: String = row.get(2)?;
1083 Ok((rowid, uid, target))
1084 },
1085 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
1086 )
1087 .await?;
1088
1089 for (target, rowid_set, uid_set) in UidGrouper::from(rows) {
1090 let create = false;
1095 let folder_exists = self
1096 .select_with_uidvalidity(context, folder, create)
1097 .await?;
1098 ensure!(folder_exists, "No folder {folder}");
1099
1100 if target.is_empty() {
1102 self.delete_message_batch(context, &uid_set, rowid_set)
1103 .await
1104 .with_context(|| format!("cannot delete batch of messages {:?}", &uid_set))?;
1105 } else {
1106 self.move_message_batch(context, &uid_set, rowid_set, &target)
1107 .await
1108 .with_context(|| {
1109 format!(
1110 "cannot move batch of messages {:?} to folder {:?}",
1111 &uid_set, target
1112 )
1113 })?;
1114 }
1115 }
1116
1117 if let Err(err) = self.maybe_close_folder(context).await {
1120 warn!(context, "Failed to close folder: {err:#}.");
1121 }
1122
1123 Ok(())
1124 }
1125
1126 pub(crate) async fn send_sync_msgs(&mut self, context: &Context, folder: &str) -> Result<()> {
1128 context.send_sync_msg().await?;
1129 while let Some((id, mime, msg_id, attempts)) = context
1130 .sql
1131 .query_row_optional(
1132 "SELECT id, mime, msg_id, attempts FROM imap_send ORDER BY id LIMIT 1",
1133 (),
1134 |row| {
1135 let id: i64 = row.get(0)?;
1136 let mime: String = row.get(1)?;
1137 let msg_id: MsgId = row.get(2)?;
1138 let attempts: i64 = row.get(3)?;
1139 Ok((id, mime, msg_id, attempts))
1140 },
1141 )
1142 .await
1143 .context("Failed to SELECT from imap_send")?
1144 {
1145 let res = self
1146 .append(folder, Some("(\\Seen)"), None, mime)
1147 .await
1148 .with_context(|| format!("IMAP APPEND to {folder} failed for {msg_id}"))
1149 .log_err(context);
1150 if res.is_ok() {
1151 msg_id.set_delivered(context).await?;
1152 }
1153 const MAX_ATTEMPTS: i64 = 2;
1154 if res.is_ok() || attempts >= MAX_ATTEMPTS - 1 {
1155 context
1156 .sql
1157 .execute("DELETE FROM imap_send WHERE id=?", (id,))
1158 .await
1159 .context("Failed to delete from imap_send")?;
1160 } else {
1161 context
1162 .sql
1163 .execute("UPDATE imap_send SET attempts=attempts+1 WHERE id=?", (id,))
1164 .await
1165 .context("Failed to update imap_send.attempts")?;
1166 res?;
1167 }
1168 }
1169 Ok(())
1170 }
1171
1172 pub(crate) async fn store_seen_flags_on_imap(&mut self, context: &Context) -> Result<()> {
1174 let rows = context
1175 .sql
1176 .query_map(
1177 "SELECT imap.id, uid, folder FROM imap, imap_markseen
1178 WHERE imap.id = imap_markseen.id AND target = folder
1179 ORDER BY folder, uid",
1180 [],
1181 |row| {
1182 let rowid: i64 = row.get(0)?;
1183 let uid: u32 = row.get(1)?;
1184 let folder: String = row.get(2)?;
1185 Ok((rowid, uid, folder))
1186 },
1187 |rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
1188 )
1189 .await?;
1190
1191 for (folder, rowid_set, uid_set) in UidGrouper::from(rows) {
1192 let create = false;
1193 let folder_exists = match self.select_with_uidvalidity(context, &folder, create).await {
1194 Err(err) => {
1195 warn!(
1196 context,
1197 "store_seen_flags_on_imap: Failed to select {folder}, will retry later: {err:#}."
1198 );
1199 continue;
1200 }
1201 Ok(folder_exists) => folder_exists,
1202 };
1203 if !folder_exists {
1204 warn!(context, "store_seen_flags_on_imap: No folder {folder}.");
1205 } else if let Err(err) = self.add_flag_finalized_with_set(&uid_set, "\\Seen").await {
1206 warn!(
1207 context,
1208 "Cannot mark messages {uid_set} in {folder} as seen, will retry later: {err:#}."
1209 );
1210 continue;
1211 } else {
1212 info!(
1213 context,
1214 "Marked messages {} in folder {} as seen.", uid_set, folder
1215 );
1216 }
1217 context
1218 .sql
1219 .transaction(|transaction| {
1220 let mut stmt = transaction.prepare("DELETE FROM imap_markseen WHERE id = ?")?;
1221 for rowid in rowid_set {
1222 stmt.execute((rowid,))?;
1223 }
1224 Ok(())
1225 })
1226 .await
1227 .context("Cannot remove messages marked as seen from imap_markseen table")?;
1228 }
1229
1230 Ok(())
1231 }
1232
1233 pub(crate) async fn sync_seen_flags(&mut self, context: &Context, folder: &str) -> Result<()> {
1235 if !self.can_condstore() {
1236 info!(
1237 context,
1238 "Server does not support CONDSTORE, skipping flag synchronization."
1239 );
1240 return Ok(());
1241 }
1242
1243 let create = false;
1244 let folder_exists = self
1245 .select_with_uidvalidity(context, folder, create)
1246 .await
1247 .context("Failed to select folder")?;
1248 if !folder_exists {
1249 return Ok(());
1250 }
1251
1252 let mailbox = self
1253 .selected_mailbox
1254 .as_ref()
1255 .with_context(|| format!("No mailbox selected, folder: {folder}"))?;
1256
1257 if mailbox.highest_modseq.is_none() {
1260 info!(
1261 context,
1262 "Mailbox {} does not support mod-sequences, skipping flag synchronization.", folder
1263 );
1264 return Ok(());
1265 }
1266
1267 let mut updated_chat_ids = BTreeSet::new();
1268 let uid_validity = get_uidvalidity(context, folder)
1269 .await
1270 .with_context(|| format!("failed to get UID validity for folder {folder}"))?;
1271 let mut highest_modseq = get_modseq(context, folder)
1272 .await
1273 .with_context(|| format!("failed to get MODSEQ for folder {folder}"))?;
1274 let mut list = self
1275 .uid_fetch("1:*", format!("(FLAGS) (CHANGEDSINCE {highest_modseq})"))
1276 .await
1277 .context("failed to fetch flags")?;
1278
1279 let mut got_unsolicited_fetch = false;
1280
1281 while let Some(fetch) = list
1282 .try_next()
1283 .await
1284 .context("failed to get FETCH result")?
1285 {
1286 let uid = if let Some(uid) = fetch.uid {
1287 uid
1288 } else {
1289 info!(context, "FETCH result contains no UID, skipping");
1290 got_unsolicited_fetch = true;
1291 continue;
1292 };
1293 let is_seen = fetch.flags().any(|flag| flag == Flag::Seen);
1294 if is_seen {
1295 if let Some(chat_id) = mark_seen_by_uid(context, folder, uid_validity, uid)
1296 .await
1297 .with_context(|| {
1298 format!("failed to update seen status for msg {folder}/{uid}")
1299 })?
1300 {
1301 updated_chat_ids.insert(chat_id);
1302 }
1303 }
1304
1305 if let Some(modseq) = fetch.modseq {
1306 if modseq > highest_modseq {
1307 highest_modseq = modseq;
1308 }
1309 } else {
1310 warn!(context, "FETCH result contains no MODSEQ");
1311 }
1312 }
1313 drop(list);
1314
1315 if got_unsolicited_fetch {
1316 self.new_mail = true;
1321 }
1322
1323 set_modseq(context, folder, highest_modseq)
1324 .await
1325 .with_context(|| format!("failed to set MODSEQ for folder {folder}"))?;
1326 if !updated_chat_ids.is_empty() {
1327 context.on_archived_chats_maybe_noticed();
1328 }
1329 for updated_chat_id in updated_chat_ids {
1330 context.emit_event(EventType::MsgsNoticed(updated_chat_id));
1331 chatlist_events::emit_chatlist_item_changed(context, updated_chat_id);
1332 }
1333
1334 Ok(())
1335 }
1336
1337 pub async fn get_all_recipients(&mut self, context: &Context) -> Result<Vec<SingleInfo>> {
1339 let mut uids: Vec<_> = self
1340 .uid_search(get_imap_self_sent_search_command(context).await?)
1341 .await?
1342 .into_iter()
1343 .collect();
1344 uids.sort_unstable();
1345
1346 let mut result = Vec::new();
1347 for (_, uid_set) in build_sequence_sets(&uids)? {
1348 let mut list = self
1349 .uid_fetch(uid_set, "(UID BODY.PEEK[HEADER.FIELDS (FROM TO CC BCC)])")
1350 .await
1351 .context("IMAP Could not fetch")?;
1352
1353 while let Some(msg) = list.try_next().await? {
1354 match get_fetch_headers(&msg) {
1355 Ok(headers) => {
1356 if let Some(from) = mimeparser::get_from(&headers) {
1357 if context.is_self_addr(&from.addr).await? {
1358 result.extend(mimeparser::get_recipients(&headers));
1359 }
1360 }
1361 }
1362 Err(err) => {
1363 warn!(context, "{}", err);
1364 continue;
1365 }
1366 };
1367 }
1368 }
1369 Ok(result)
1370 }
1371
1372 #[expect(clippy::too_many_arguments)]
1387 pub(crate) async fn fetch_many_msgs(
1388 &mut self,
1389 context: &Context,
1390 folder: &str,
1391 uidvalidity: u32,
1392 request_uids: Vec<u32>,
1393 uid_message_ids: &BTreeMap<u32, String>,
1394 fetch_partially: bool,
1395 received_msgs_channel: Sender<(u32, Option<ReceivedMsg>)>,
1396 ) -> Result<()> {
1397 if request_uids.is_empty() {
1398 return Ok(());
1399 }
1400
1401 for (request_uids, set) in build_sequence_sets(&request_uids)? {
1402 info!(
1403 context,
1404 "Starting a {} FETCH of message set \"{}\".",
1405 if fetch_partially { "partial" } else { "full" },
1406 set
1407 );
1408 let mut fetch_responses = self
1409 .uid_fetch(
1410 &set,
1411 if fetch_partially {
1412 BODY_PARTIAL
1413 } else {
1414 BODY_FULL
1415 },
1416 )
1417 .await
1418 .with_context(|| {
1419 format!("fetching messages {} from folder \"{}\"", &set, folder)
1420 })?;
1421
1422 let mut uid_msgs = HashMap::with_capacity(request_uids.len());
1425
1426 let mut count = 0;
1427 for &request_uid in &request_uids {
1428 let mut fetch_response = uid_msgs.remove(&request_uid);
1430
1431 while fetch_response.is_none() {
1433 let Some(next_fetch_response) = fetch_responses
1434 .try_next()
1435 .await
1436 .context("Failed to process IMAP FETCH result")?
1437 else {
1438 break;
1440 };
1441
1442 if let Some(next_uid) = next_fetch_response.uid {
1443 if next_uid == request_uid {
1444 fetch_response = Some(next_fetch_response);
1445 } else if !request_uids.contains(&next_uid) {
1446 info!(
1453 context,
1454 "Skipping not requested FETCH response for UID {}.", next_uid
1455 );
1456 } else if uid_msgs.insert(next_uid, next_fetch_response).is_some() {
1457 warn!(context, "Got duplicated UID {}.", next_uid);
1458 }
1459 } else {
1460 info!(context, "Skipping FETCH response without UID.");
1461 }
1462 }
1463
1464 let fetch_response = match fetch_response {
1465 Some(fetch) => fetch,
1466 None => {
1467 warn!(
1468 context,
1469 "Missed UID {} in the server response.", request_uid
1470 );
1471 continue;
1472 }
1473 };
1474 count += 1;
1475
1476 let is_deleted = fetch_response.flags().any(|flag| flag == Flag::Deleted);
1477 let (body, partial) = if fetch_partially {
1478 (fetch_response.header(), fetch_response.size) } else {
1480 (fetch_response.body(), None) };
1482
1483 if is_deleted {
1484 info!(context, "Not processing deleted msg {}.", request_uid);
1485 received_msgs_channel.send((request_uid, None)).await?;
1486 continue;
1487 }
1488
1489 let body = if let Some(body) = body {
1490 body
1491 } else {
1492 info!(
1493 context,
1494 "Not processing message {} without a BODY.", request_uid
1495 );
1496 received_msgs_channel.send((request_uid, None)).await?;
1497 continue;
1498 };
1499
1500 let is_seen = fetch_response.flags().any(|flag| flag == Flag::Seen);
1501
1502 let Some(rfc724_mid) = uid_message_ids.get(&request_uid) else {
1503 error!(
1504 context,
1505 "No Message-ID corresponding to UID {} passed in uid_messsage_ids.",
1506 request_uid
1507 );
1508 continue;
1509 };
1510
1511 info!(
1512 context,
1513 "Passing message UID {} to receive_imf().", request_uid
1514 );
1515 let res = receive_imf_inner(
1516 context,
1517 folder,
1518 uidvalidity,
1519 request_uid,
1520 rfc724_mid,
1521 body,
1522 is_seen,
1523 partial.map(|msg_size| (msg_size, None)),
1524 )
1525 .await;
1526 let received_msg = if let Err(err) = res {
1527 warn!(context, "receive_imf error: {:#}.", err);
1528 if partial.is_some() {
1529 return Err(err);
1530 }
1531 receive_imf_inner(
1532 context,
1533 folder,
1534 uidvalidity,
1535 request_uid,
1536 rfc724_mid,
1537 body,
1538 is_seen,
1539 Some((body.len().try_into()?, Some(format!("{err:#}")))),
1540 )
1541 .await?
1542 } else {
1543 res?
1544 };
1545 received_msgs_channel
1546 .send((request_uid, received_msg))
1547 .await?;
1548 }
1549
1550 while fetch_responses
1557 .try_next()
1558 .await
1559 .context("Failed to drain FETCH responses")?
1560 .is_some()
1561 {}
1562
1563 if count != request_uids.len() {
1564 warn!(
1565 context,
1566 "Failed to fetch all UIDs: got {}, requested {}, we requested the UIDs {:?}.",
1567 count,
1568 request_uids.len(),
1569 request_uids,
1570 );
1571 } else {
1572 info!(
1573 context,
1574 "Successfully received {} UIDs.",
1575 request_uids.len()
1576 );
1577 }
1578 }
1579
1580 Ok(())
1581 }
1582
1583 pub(crate) async fn fetch_metadata(&mut self, context: &Context) -> Result<()> {
1589 if !self.can_metadata() {
1590 return Ok(());
1591 }
1592
1593 let mut lock = context.metadata.write().await;
1594 if let Some(ref mut old_metadata) = *lock {
1595 let now = time();
1596
1597 if now + 3600 * 12 < old_metadata.ice_servers_expiration_timestamp {
1599 return Ok(());
1600 }
1601
1602 info!(context, "ICE servers expired, requesting new credentials.");
1603 let mailbox = "";
1604 let options = "";
1605 let metadata = self
1606 .get_metadata(mailbox, options, "(/shared/vendor/deltachat/turn)")
1607 .await?;
1608 let mut got_turn_server = false;
1609 for m in metadata {
1610 if m.entry == "/shared/vendor/deltachat/turn" {
1611 if let Some(value) = m.value {
1612 match create_ice_servers_from_metadata(context, &value).await {
1613 Ok((parsed_timestamp, parsed_ice_servers)) => {
1614 old_metadata.ice_servers_expiration_timestamp = parsed_timestamp;
1615 old_metadata.ice_servers = parsed_ice_servers;
1616 got_turn_server = false;
1617 }
1618 Err(err) => {
1619 warn!(context, "Failed to parse TURN server metadata: {err:#}.");
1620 }
1621 }
1622 }
1623 }
1624 }
1625
1626 if !got_turn_server {
1627 old_metadata.ice_servers_expiration_timestamp = time() + 3600 * 24 * 7;
1629 old_metadata.ice_servers = create_fallback_ice_servers(context).await?;
1630 }
1631 return Ok(());
1632 }
1633
1634 info!(
1635 context,
1636 "Server supports metadata, retrieving server comment and admin contact."
1637 );
1638
1639 let mut comment = None;
1640 let mut admin = None;
1641 let mut iroh_relay = None;
1642 let mut ice_servers = None;
1643 let mut ice_servers_expiration_timestamp = 0;
1644
1645 let mailbox = "";
1646 let options = "";
1647 let metadata = self
1648 .get_metadata(
1649 mailbox,
1650 options,
1651 "(/shared/comment /shared/admin /shared/vendor/deltachat/irohrelay /shared/vendor/deltachat/turn)",
1652 )
1653 .await?;
1654 for m in metadata {
1655 match m.entry.as_ref() {
1656 "/shared/comment" => {
1657 comment = m.value;
1658 }
1659 "/shared/admin" => {
1660 admin = m.value;
1661 }
1662 "/shared/vendor/deltachat/irohrelay" => {
1663 if let Some(value) = m.value {
1664 if let Ok(url) = Url::parse(&value) {
1665 iroh_relay = Some(url);
1666 } else {
1667 warn!(
1668 context,
1669 "Got invalid URL from iroh relay metadata: {:?}.", value
1670 );
1671 }
1672 }
1673 }
1674 "/shared/vendor/deltachat/turn" => {
1675 if let Some(value) = m.value {
1676 match create_ice_servers_from_metadata(context, &value).await {
1677 Ok((parsed_timestamp, parsed_ice_servers)) => {
1678 ice_servers_expiration_timestamp = parsed_timestamp;
1679 ice_servers = Some(parsed_ice_servers);
1680 }
1681 Err(err) => {
1682 warn!(context, "Failed to parse TURN server metadata: {err:#}.");
1683 }
1684 }
1685 }
1686 }
1687 _ => {}
1688 }
1689 }
1690 let ice_servers = if let Some(ice_servers) = ice_servers {
1691 ice_servers
1692 } else {
1693 ice_servers_expiration_timestamp = time() + 3600 * 24 * 7;
1695 create_fallback_ice_servers(context).await?
1696 };
1697
1698 *lock = Some(ServerMetadata {
1699 comment,
1700 admin,
1701 iroh_relay,
1702 ice_servers,
1703 ice_servers_expiration_timestamp,
1704 });
1705 Ok(())
1706 }
1707
1708 pub(crate) async fn register_token(&mut self, context: &Context) -> Result<()> {
1710 if context.push_subscribed.load(Ordering::Relaxed) {
1711 return Ok(());
1712 }
1713
1714 let Some(device_token) = context.push_subscriber.device_token().await else {
1715 return Ok(());
1716 };
1717
1718 if self.can_metadata() && self.can_push() {
1719 let old_encrypted_device_token =
1720 context.get_config(Config::EncryptedDeviceToken).await?;
1721
1722 let device_token_changed = old_encrypted_device_token.is_none()
1724 || context.get_config(Config::DeviceToken).await?.as_ref() != Some(&device_token);
1725
1726 let new_encrypted_device_token;
1727 if device_token_changed {
1728 let encrypted_device_token = encrypt_device_token(&device_token)
1729 .context("Failed to encrypt device token")?;
1730
1731 let encrypted_device_token_len = encrypted_device_token.len();
1735
1736 context
1742 .set_config_internal(Config::DeviceToken, Some(&device_token))
1743 .await?;
1744 context
1745 .set_config_internal(
1746 Config::EncryptedDeviceToken,
1747 Some(&encrypted_device_token),
1748 )
1749 .await?;
1750
1751 if encrypted_device_token_len <= 4096 {
1752 new_encrypted_device_token = Some(encrypted_device_token);
1753 } else {
1754 warn!(context, "Device token is too long for LITERAL-, ignoring.");
1764 new_encrypted_device_token = None;
1765 }
1766 } else {
1767 new_encrypted_device_token = old_encrypted_device_token;
1768 }
1769
1770 if let Some(encrypted_device_token) = new_encrypted_device_token {
1773 let folder = context
1774 .get_config(Config::ConfiguredInboxFolder)
1775 .await?
1776 .context("INBOX is not configured")?;
1777
1778 self.run_command_and_check_ok(&format_setmetadata(
1779 &folder,
1780 &encrypted_device_token,
1781 ))
1782 .await
1783 .context("SETMETADATA command failed")?;
1784
1785 context.push_subscribed.store(true, Ordering::Relaxed);
1786 }
1787 } else if !context.push_subscriber.heartbeat_subscribed().await {
1788 let context = context.clone();
1789 tokio::spawn(async move { context.push_subscriber.subscribe(&context).await });
1791 }
1792
1793 Ok(())
1794 }
1795}
1796
1797fn format_setmetadata(folder: &str, device_token: &str) -> String {
1798 let device_token_len = device_token.len();
1799 format!(
1800 "SETMETADATA \"{folder}\" (/private/devicetoken {{{device_token_len}+}}\r\n{device_token})"
1801 )
1802}
1803
1804impl Session {
1805 async fn add_flag_finalized_with_set(&mut self, uid_set: &str, flag: &str) -> Result<()> {
1811 if flag == "\\Deleted" {
1812 self.selected_folder_needs_expunge = true;
1813 }
1814 let query = format!("+FLAGS ({flag})");
1815 let mut responses = self
1816 .uid_store(uid_set, &query)
1817 .await
1818 .with_context(|| format!("IMAP failed to store: ({uid_set}, {query})"))?;
1819 while let Some(_response) = responses.try_next().await? {
1820 }
1822 Ok(())
1823 }
1824
1825 async fn configure_mvbox<'a>(
1834 &mut self,
1835 context: &Context,
1836 folders: &[&'a str],
1837 create_mvbox: bool,
1838 ) -> Result<Option<&'a str>> {
1839 self.maybe_close_folder(context).await?;
1842
1843 for folder in folders {
1844 info!(context, "Looking for MVBOX-folder \"{}\"...", &folder);
1845 let res = self.examine(&folder).await;
1846 if res.is_ok() {
1847 info!(
1848 context,
1849 "MVBOX-folder {:?} successfully selected, using it.", &folder
1850 );
1851 self.close().await?;
1852 let create = false;
1855 let folder_exists = self
1856 .select_with_uidvalidity(context, folder, create)
1857 .await?;
1858 ensure!(folder_exists, "No MVBOX folder {:?}??", &folder);
1859 return Ok(Some(folder));
1860 }
1861 }
1862
1863 if !create_mvbox {
1864 return Ok(None);
1865 }
1866 for folder in folders {
1869 match self
1870 .select_with_uidvalidity(context, folder, create_mvbox)
1871 .await
1872 {
1873 Ok(_) => {
1874 info!(context, "MVBOX-folder {} created.", folder);
1875 return Ok(Some(folder));
1876 }
1877 Err(err) => {
1878 warn!(context, "Cannot create MVBOX-folder {:?}: {}", folder, err);
1879 }
1880 }
1881 }
1882 Ok(None)
1883 }
1884}
1885
1886impl Imap {
1887 pub(crate) async fn configure_folders(
1888 &mut self,
1889 context: &Context,
1890 session: &mut Session,
1891 create_mvbox: bool,
1892 ) -> Result<()> {
1893 let mut folders = session
1894 .list(Some(""), Some("*"))
1895 .await
1896 .context("list_folders failed")?;
1897 let mut delimiter = ".".to_string();
1898 let mut delimiter_is_default = true;
1899 let mut folder_configs = BTreeMap::new();
1900
1901 while let Some(folder) = folders.try_next().await? {
1902 info!(context, "Scanning folder: {:?}", folder);
1903
1904 if let Some(d) = folder.delimiter() {
1906 if delimiter_is_default && !d.is_empty() && delimiter != d {
1907 delimiter = d.to_string();
1908 delimiter_is_default = false;
1909 }
1910 }
1911
1912 let folder_meaning = get_folder_meaning_by_attrs(folder.attributes());
1913 let folder_name_meaning = get_folder_meaning_by_name(folder.name());
1914 if let Some(config) = folder_meaning.to_config() {
1915 folder_configs.insert(config, folder.name().to_string());
1917 } else if let Some(config) = folder_name_meaning.to_config() {
1918 folder_configs
1920 .entry(config)
1921 .or_insert_with(|| folder.name().to_string());
1922 }
1923 }
1924 drop(folders);
1925
1926 info!(context, "Using \"{}\" as folder-delimiter.", delimiter);
1927
1928 let fallback_folder = format!("INBOX{delimiter}DeltaChat");
1929 let mvbox_folder = session
1930 .configure_mvbox(context, &["DeltaChat", &fallback_folder], create_mvbox)
1931 .await
1932 .context("failed to configure mvbox")?;
1933
1934 context
1935 .set_config_internal(Config::ConfiguredInboxFolder, Some("INBOX"))
1936 .await?;
1937 if let Some(mvbox_folder) = mvbox_folder {
1938 info!(context, "Setting MVBOX FOLDER TO {}", &mvbox_folder);
1939 context
1940 .set_config_internal(Config::ConfiguredMvboxFolder, Some(mvbox_folder))
1941 .await?;
1942 }
1943 for (config, name) in folder_configs {
1944 context.set_config_internal(config, Some(&name)).await?;
1945 }
1946 context
1947 .sql
1948 .set_raw_config_int(
1949 constants::DC_FOLDERS_CONFIGURED_KEY,
1950 constants::DC_FOLDERS_CONFIGURED_VERSION,
1951 )
1952 .await?;
1953
1954 info!(context, "FINISHED configuring IMAP-folders.");
1955 Ok(())
1956 }
1957}
1958
1959impl Session {
1960 fn drain_unsolicited_responses(&self, context: &Context) -> Result<bool> {
1969 use UnsolicitedResponse::*;
1970 use async_imap::imap_proto::Response;
1971 use async_imap::imap_proto::ResponseCode;
1972
1973 let folder = self.selected_folder.as_deref().unwrap_or_default();
1974 let mut should_refetch = false;
1975 while let Ok(response) = self.unsolicited_responses.try_recv() {
1976 match response {
1977 Exists(_) => {
1978 info!(
1979 context,
1980 "Need to refetch {folder:?}, got unsolicited EXISTS {response:?}"
1981 );
1982 should_refetch = true;
1983 }
1984
1985 Expunge(_) | Recent(_) => {}
1986 Other(ref response_data) => {
1987 match response_data.parsed() {
1988 Response::Fetch { .. } => {
1989 info!(
1990 context,
1991 "Need to refetch {folder:?}, got unsolicited FETCH {response:?}"
1992 );
1993 should_refetch = true;
1994 }
1995
1996 Response::Done {
1999 code: Some(ResponseCode::CopyUid(_, _, _)),
2000 ..
2001 } => {}
2002
2003 _ => {
2004 info!(context, "{folder:?}: got unsolicited response {response:?}")
2005 }
2006 }
2007 }
2008 _ => {
2009 info!(context, "{folder:?}: got unsolicited response {response:?}")
2010 }
2011 }
2012 }
2013 Ok(should_refetch)
2014 }
2015}
2016
2017async fn should_move_out_of_spam(
2018 context: &Context,
2019 headers: &[mailparse::MailHeader<'_>],
2020) -> Result<bool> {
2021 if headers.get_header_value(HeaderDef::ChatVersion).is_some() {
2022 return Ok(true);
2033 }
2034
2035 if let Some(msg) = get_prefetch_parent_message(context, headers).await? {
2036 if msg.chat_blocked != Blocked::Not {
2037 return Ok(false);
2039 }
2040 } else {
2041 let from = match mimeparser::get_from(headers) {
2042 Some(f) => f,
2043 None => return Ok(false),
2044 };
2045 let (from_id, blocked_contact, _origin) =
2047 match from_field_to_contact_id(context, &from, None, true, true)
2048 .await
2049 .context("from_field_to_contact_id")?
2050 {
2051 Some(res) => res,
2052 None => {
2053 warn!(
2054 context,
2055 "Contact with From address {:?} cannot exist, not moving out of spam", from
2056 );
2057 return Ok(false);
2058 }
2059 };
2060 if blocked_contact {
2061 return Ok(false);
2063 }
2064
2065 if let Some(chat_id_blocked) = ChatIdBlocked::lookup_by_contact(context, from_id).await? {
2066 if chat_id_blocked.blocked != Blocked::Not {
2067 return Ok(false);
2068 }
2069 } else if from_id != ContactId::SELF {
2070 return Ok(false);
2072 }
2073 }
2074
2075 Ok(true)
2076}
2077
2078async fn spam_target_folder_cfg(
2083 context: &Context,
2084 headers: &[mailparse::MailHeader<'_>],
2085) -> Result<Option<Config>> {
2086 if !should_move_out_of_spam(context, headers).await? {
2087 return Ok(None);
2088 }
2089
2090 if needs_move_to_mvbox(context, headers).await?
2091 || context.get_config_bool(Config::OnlyFetchMvbox).await?
2094 {
2095 Ok(Some(Config::ConfiguredMvboxFolder))
2096 } else {
2097 Ok(Some(Config::ConfiguredInboxFolder))
2098 }
2099}
2100
2101pub async fn target_folder_cfg(
2104 context: &Context,
2105 folder: &str,
2106 folder_meaning: FolderMeaning,
2107 headers: &[mailparse::MailHeader<'_>],
2108) -> Result<Option<Config>> {
2109 if context.is_mvbox(folder).await? {
2110 return Ok(None);
2111 }
2112
2113 if folder_meaning == FolderMeaning::Spam {
2114 spam_target_folder_cfg(context, headers).await
2115 } else if needs_move_to_mvbox(context, headers).await? {
2116 Ok(Some(Config::ConfiguredMvboxFolder))
2117 } else {
2118 Ok(None)
2119 }
2120}
2121
2122pub async fn target_folder(
2123 context: &Context,
2124 folder: &str,
2125 folder_meaning: FolderMeaning,
2126 headers: &[mailparse::MailHeader<'_>],
2127) -> Result<String> {
2128 match target_folder_cfg(context, folder, folder_meaning, headers).await? {
2129 Some(config) => match context.get_config(config).await? {
2130 Some(target) => Ok(target),
2131 None => Ok(folder.to_string()),
2132 },
2133 None => Ok(folder.to_string()),
2134 }
2135}
2136
2137async fn needs_move_to_mvbox(
2138 context: &Context,
2139 headers: &[mailparse::MailHeader<'_>],
2140) -> Result<bool> {
2141 let has_chat_version = headers.get_header_value(HeaderDef::ChatVersion).is_some();
2142 if !context.get_config_bool(Config::IsChatmail).await?
2143 && has_chat_version
2144 && headers
2145 .get_header_value(HeaderDef::AutoSubmitted)
2146 .filter(|val| val.eq_ignore_ascii_case("auto-generated"))
2147 .is_some()
2148 {
2149 if let Some(from) = mimeparser::get_from(headers) {
2150 if context.is_self_addr(&from.addr).await? {
2151 return Ok(true);
2152 }
2153 }
2154 }
2155 if !context.get_config_bool(Config::MvboxMove).await? {
2156 return Ok(false);
2157 }
2158
2159 if headers
2160 .get_header_value(HeaderDef::AutocryptSetupMessage)
2161 .is_some()
2162 {
2163 return Ok(false);
2166 }
2167
2168 if has_chat_version {
2169 Ok(true)
2170 } else if let Some(parent) = get_prefetch_parent_message(context, headers).await? {
2171 match parent.is_dc_message {
2172 MessengerMessage::No => Ok(false),
2173 MessengerMessage::Yes | MessengerMessage::Reply => Ok(true),
2174 }
2175 } else {
2176 Ok(false)
2177 }
2178}
2179
2180fn get_folder_meaning_by_name(folder_name: &str) -> FolderMeaning {
2187 const SENT_NAMES: &[&str] = &[
2189 "sent",
2190 "sentmail",
2191 "sent objects",
2192 "gesendet",
2193 "Sent Mail",
2194 "Sendte e-mails",
2195 "Enviados",
2196 "Messages envoyés",
2197 "Messages envoyes",
2198 "Posta inviata",
2199 "Verzonden berichten",
2200 "Wyslane",
2201 "E-mails enviados",
2202 "Correio enviado",
2203 "Enviada",
2204 "Enviado",
2205 "Gönderildi",
2206 "Inviati",
2207 "Odeslaná pošta",
2208 "Sendt",
2209 "Skickat",
2210 "Verzonden",
2211 "Wysłane",
2212 "Éléments envoyés",
2213 "Απεσταλμένα",
2214 "Отправленные",
2215 "寄件備份",
2216 "已发送邮件",
2217 "送信済み",
2218 "보낸편지함",
2219 ];
2220 const SPAM_NAMES: &[&str] = &[
2221 "spam",
2222 "junk",
2223 "Correio electrónico não solicitado",
2224 "Correo basura",
2225 "Lixo",
2226 "Nettsøppel",
2227 "Nevyžádaná pošta",
2228 "No solicitado",
2229 "Ongewenst",
2230 "Posta indesiderata",
2231 "Skräp",
2232 "Wiadomości-śmieci",
2233 "Önemsiz",
2234 "Ανεπιθύμητα",
2235 "Спам",
2236 "垃圾邮件",
2237 "垃圾郵件",
2238 "迷惑メール",
2239 "스팸",
2240 ];
2241 const TRASH_NAMES: &[&str] = &[
2242 "Trash",
2243 "Bin",
2244 "Caixote do lixo",
2245 "Cestino",
2246 "Corbeille",
2247 "Papelera",
2248 "Papierkorb",
2249 "Papirkurv",
2250 "Papperskorgen",
2251 "Prullenbak",
2252 "Rubujo",
2253 "Κάδος απορριμμάτων",
2254 "Корзина",
2255 "Кошик",
2256 "ゴミ箱",
2257 "垃圾桶",
2258 "已删除邮件",
2259 "휴지통",
2260 ];
2261 let lower = folder_name.to_lowercase();
2262
2263 if SENT_NAMES.iter().any(|s| s.to_lowercase() == lower) {
2264 FolderMeaning::Sent
2265 } else if SPAM_NAMES.iter().any(|s| s.to_lowercase() == lower) {
2266 FolderMeaning::Spam
2267 } else if TRASH_NAMES.iter().any(|s| s.to_lowercase() == lower) {
2268 FolderMeaning::Trash
2269 } else {
2270 FolderMeaning::Unknown
2271 }
2272}
2273
2274fn get_folder_meaning_by_attrs(folder_attrs: &[NameAttribute]) -> FolderMeaning {
2275 for attr in folder_attrs {
2276 match attr {
2277 NameAttribute::Trash => return FolderMeaning::Trash,
2278 NameAttribute::Sent => return FolderMeaning::Sent,
2279 NameAttribute::Junk => return FolderMeaning::Spam,
2280 NameAttribute::All | NameAttribute::Flagged => return FolderMeaning::Virtual,
2281 NameAttribute::Extension(label) => {
2282 match label.as_ref() {
2283 "\\Spam" => return FolderMeaning::Spam,
2284 "\\Important" => return FolderMeaning::Virtual,
2285 _ => {}
2286 };
2287 }
2288 _ => {}
2289 }
2290 }
2291 FolderMeaning::Unknown
2292}
2293
2294pub(crate) fn get_folder_meaning(folder: &Name) -> FolderMeaning {
2295 match get_folder_meaning_by_attrs(folder.attributes()) {
2296 FolderMeaning::Unknown => get_folder_meaning_by_name(folder.name()),
2297 meaning => meaning,
2298 }
2299}
2300
2301fn get_fetch_headers(prefetch_msg: &Fetch) -> Result<Vec<mailparse::MailHeader<'_>>> {
2303 match prefetch_msg.header() {
2304 Some(header_bytes) => {
2305 let (headers, _) = mailparse::parse_headers(header_bytes)?;
2306 Ok(headers)
2307 }
2308 None => Ok(Vec::new()),
2309 }
2310}
2311
2312pub(crate) fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Option<String> {
2313 headers
2314 .get_header_value(HeaderDef::XMicrosoftOriginalMessageId)
2315 .or_else(|| headers.get_header_value(HeaderDef::MessageId))
2316 .and_then(|msgid| mimeparser::parse_message_id(&msgid).ok())
2317}
2318
2319pub(crate) fn create_message_id() -> String {
2320 format!("{}{}", GENERATED_PREFIX, create_id())
2321}
2322
2323async fn prefetch_get_chat(
2325 context: &Context,
2326 headers: &[mailparse::MailHeader<'_>],
2327) -> Result<Option<chat::Chat>> {
2328 let parent = get_prefetch_parent_message(context, headers).await?;
2329 if let Some(parent) = &parent {
2330 return Ok(Some(
2331 chat::Chat::load_from_db(context, parent.get_chat_id()).await?,
2332 ));
2333 }
2334
2335 Ok(None)
2336}
2337
2338pub(crate) async fn prefetch_should_download(
2340 context: &Context,
2341 headers: &[mailparse::MailHeader<'_>],
2342 message_id: &str,
2343 mut flags: impl Iterator<Item = Flag<'_>>,
2344) -> Result<bool> {
2345 if message::rfc724_mid_exists(context, message_id)
2346 .await?
2347 .is_some()
2348 {
2349 markseen_on_imap_table(context, message_id).await?;
2350 return Ok(false);
2351 }
2352
2353 if let Some(chat) = prefetch_get_chat(context, headers).await? {
2357 if chat.typ == Chattype::Group && !chat.id.is_special() {
2358 return Ok(true);
2361 }
2362 }
2363
2364 let maybe_ndn = if let Some(from) = headers.get_header_value(HeaderDef::From_) {
2365 let from = from.to_ascii_lowercase();
2366 from.contains("mailer-daemon") || from.contains("mail-daemon")
2367 } else {
2368 false
2369 };
2370
2371 let is_autocrypt_setup_message = headers
2373 .get_header_value(HeaderDef::AutocryptSetupMessage)
2374 .is_some();
2375
2376 let from = match mimeparser::get_from(headers) {
2377 Some(f) => f,
2378 None => return Ok(false),
2379 };
2380 let (_from_id, blocked_contact, origin) =
2381 match from_field_to_contact_id(context, &from, None, true, true).await? {
2382 Some(res) => res,
2383 None => return Ok(false),
2384 };
2385 if flags.any(|f| f == Flag::Draft) {
2389 info!(context, "Ignoring draft message");
2390 return Ok(false);
2391 }
2392
2393 let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion).is_some();
2394 let accepted_contact = origin.is_known();
2395 let is_reply_to_chat_message = get_prefetch_parent_message(context, headers)
2396 .await?
2397 .map(|parent| match parent.is_dc_message {
2398 MessengerMessage::No => false,
2399 MessengerMessage::Yes | MessengerMessage::Reply => true,
2400 })
2401 .unwrap_or_default();
2402
2403 let show_emails =
2404 ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?).unwrap_or_default();
2405
2406 let show = is_autocrypt_setup_message
2407 || match show_emails {
2408 ShowEmails::Off => is_chat_message || is_reply_to_chat_message,
2409 ShowEmails::AcceptedContacts => {
2410 is_chat_message || is_reply_to_chat_message || accepted_contact
2411 }
2412 ShowEmails::All => true,
2413 };
2414
2415 let should_download = (show && !blocked_contact) || maybe_ndn;
2416 Ok(should_download)
2417}
2418
2419pub(crate) fn is_dup_msg(is_chat_msg: bool, ts_sent: i64, ts_sent_old: i64) -> bool {
2421 is_chat_msg && ts_sent_old != 0 && ts_sent > ts_sent_old
2426}
2427
2428async fn mark_seen_by_uid(
2432 context: &Context,
2433 folder: &str,
2434 uid_validity: u32,
2435 uid: u32,
2436) -> Result<Option<ChatId>> {
2437 if let Some((msg_id, chat_id)) = context
2438 .sql
2439 .query_row_optional(
2440 "SELECT id, chat_id FROM msgs
2441 WHERE id > 9 AND rfc724_mid IN (
2442 SELECT rfc724_mid FROM imap
2443 WHERE folder=?1
2444 AND uidvalidity=?2
2445 AND uid=?3
2446 LIMIT 1
2447 )",
2448 (&folder, uid_validity, uid),
2449 |row| {
2450 let msg_id: MsgId = row.get(0)?;
2451 let chat_id: ChatId = row.get(1)?;
2452 Ok((msg_id, chat_id))
2453 },
2454 )
2455 .await
2456 .with_context(|| format!("failed to get msg and chat ID for IMAP message {folder}/{uid}"))?
2457 {
2458 let updated = context
2459 .sql
2460 .execute(
2461 "UPDATE msgs SET state=?1
2462 WHERE (state=?2 OR state=?3)
2463 AND id=?4",
2464 (
2465 MessageState::InSeen,
2466 MessageState::InFresh,
2467 MessageState::InNoticed,
2468 msg_id,
2469 ),
2470 )
2471 .await
2472 .with_context(|| format!("failed to update msg {msg_id} state"))?
2473 > 0;
2474
2475 if updated {
2476 msg_id
2477 .start_ephemeral_timer(context)
2478 .await
2479 .with_context(|| format!("failed to start ephemeral timer for message {msg_id}"))?;
2480 Ok(Some(chat_id))
2481 } else {
2482 Ok(None)
2484 }
2485 } else {
2486 Ok(None)
2488 }
2489}
2490
2491pub(crate) async fn markseen_on_imap_table(context: &Context, message_id: &str) -> Result<()> {
2494 context
2495 .sql
2496 .execute(
2497 "INSERT OR IGNORE INTO imap_markseen (id)
2498 SELECT id FROM imap WHERE rfc724_mid=?",
2499 (message_id,),
2500 )
2501 .await?;
2502 context.scheduler.interrupt_inbox().await;
2503
2504 Ok(())
2505}
2506
2507pub(crate) async fn set_uid_next(context: &Context, folder: &str, uid_next: u32) -> Result<()> {
2511 context
2512 .sql
2513 .execute(
2514 "INSERT INTO imap_sync (folder, uid_next) VALUES (?,?)
2515 ON CONFLICT(folder) DO UPDATE SET uid_next=excluded.uid_next",
2516 (folder, uid_next),
2517 )
2518 .await?;
2519 Ok(())
2520}
2521
2522async fn get_uid_next(context: &Context, folder: &str) -> Result<u32> {
2528 Ok(context
2529 .sql
2530 .query_get_value("SELECT uid_next FROM imap_sync WHERE folder=?;", (folder,))
2531 .await?
2532 .unwrap_or(0))
2533}
2534
2535pub(crate) async fn set_uidvalidity(
2536 context: &Context,
2537 folder: &str,
2538 uidvalidity: u32,
2539) -> Result<()> {
2540 context
2541 .sql
2542 .execute(
2543 "INSERT INTO imap_sync (folder, uidvalidity) VALUES (?,?)
2544 ON CONFLICT(folder) DO UPDATE SET uidvalidity=excluded.uidvalidity",
2545 (folder, uidvalidity),
2546 )
2547 .await?;
2548 Ok(())
2549}
2550
2551async fn get_uidvalidity(context: &Context, folder: &str) -> Result<u32> {
2552 Ok(context
2553 .sql
2554 .query_get_value(
2555 "SELECT uidvalidity FROM imap_sync WHERE folder=?;",
2556 (folder,),
2557 )
2558 .await?
2559 .unwrap_or(0))
2560}
2561
2562pub(crate) async fn set_modseq(context: &Context, folder: &str, modseq: u64) -> Result<()> {
2563 context
2564 .sql
2565 .execute(
2566 "INSERT INTO imap_sync (folder, modseq) VALUES (?,?)
2567 ON CONFLICT(folder) DO UPDATE SET modseq=excluded.modseq",
2568 (folder, modseq),
2569 )
2570 .await?;
2571 Ok(())
2572}
2573
2574async fn get_modseq(context: &Context, folder: &str) -> Result<u64> {
2575 Ok(context
2576 .sql
2577 .query_get_value("SELECT modseq FROM imap_sync WHERE folder=?;", (folder,))
2578 .await?
2579 .unwrap_or(0))
2580}
2581
2582pub(crate) async fn get_imap_self_sent_search_command(context: &Context) -> Result<String> {
2584 let mut search_command = format!("FROM \"{}\"", context.get_primary_self_addr().await?);
2586
2587 for item in context.get_secondary_self_addrs().await? {
2588 search_command = format!("OR ({search_command}) (FROM \"{item}\")");
2589 }
2590
2591 Ok(search_command)
2592}
2593
2594pub async fn get_config_last_seen_uid(context: &Context, folder: &str) -> Result<(u32, u32)> {
2596 let key = format!("imap.mailbox.{folder}");
2597 if let Some(entry) = context.sql.get_raw_config(&key).await? {
2598 let mut parts = entry.split(':');
2600 Ok((
2601 parts.next().unwrap_or_default().parse().unwrap_or(0),
2602 parts.next().unwrap_or_default().parse().unwrap_or(0),
2603 ))
2604 } else {
2605 Ok((0, 0))
2606 }
2607}
2608
2609async fn should_ignore_folder(
2614 context: &Context,
2615 folder: &str,
2616 folder_meaning: FolderMeaning,
2617) -> Result<bool> {
2618 if !context.get_config_bool(Config::OnlyFetchMvbox).await? {
2619 return Ok(false);
2620 }
2621 if context.is_sentbox(folder).await? {
2622 return Ok(!context.get_config_bool(Config::SentboxWatch).await?);
2624 }
2625 Ok(!(context.is_mvbox(folder).await? || folder_meaning == FolderMeaning::Spam))
2626}
2627
2628fn build_sequence_sets(uids: &[u32]) -> Result<Vec<(Vec<u32>, String)>> {
2632 let mut ranges: Vec<UidRange> = vec![];
2634
2635 for ¤t in uids {
2636 if let Some(last) = ranges.last_mut() {
2637 if last.end + 1 == current {
2638 last.end = current;
2639 continue;
2640 }
2641 }
2642
2643 ranges.push(UidRange {
2644 start: current,
2645 end: current,
2646 });
2647 }
2648
2649 let mut result = vec![];
2651 let (mut last_uids, mut last_str) = (Vec::new(), String::new());
2652 for range in ranges {
2653 last_uids.reserve((range.end - range.start + 1).try_into()?);
2654 (range.start..=range.end).for_each(|u| last_uids.push(u));
2655 if !last_str.is_empty() {
2656 last_str.push(',');
2657 }
2658 last_str.push_str(&range.to_string());
2659
2660 if last_str.len() > 990 {
2661 result.push((take(&mut last_uids), take(&mut last_str)));
2662 }
2663 }
2664 result.push((last_uids, last_str));
2665
2666 result.retain(|(_, s)| !s.is_empty());
2667 Ok(result)
2668}
2669
2670struct UidRange {
2671 start: u32,
2672 end: u32,
2673 }
2675
2676impl std::fmt::Display for UidRange {
2677 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2678 if self.start == self.end {
2679 write!(f, "{}", self.start)
2680 } else {
2681 write!(f, "{}:{}", self.start, self.end)
2682 }
2683 }
2684}
2685async fn add_all_recipients_as_contacts(
2686 context: &Context,
2687 session: &mut Session,
2688 folder: Config,
2689) -> Result<()> {
2690 let mailbox = if let Some(m) = context.get_config(folder).await? {
2691 m
2692 } else {
2693 info!(
2694 context,
2695 "Folder {} is not configured, skipping fetching contacts from it.", folder
2696 );
2697 return Ok(());
2698 };
2699 let create = false;
2700 let folder_exists = session
2701 .select_with_uidvalidity(context, &mailbox, create)
2702 .await
2703 .with_context(|| format!("could not select {mailbox}"))?;
2704 if !folder_exists {
2705 return Ok(());
2706 }
2707
2708 let recipients = session
2709 .get_all_recipients(context)
2710 .await
2711 .context("could not get recipients")?;
2712
2713 let mut any_modified = false;
2714 for recipient in recipients {
2715 let recipient_addr = match ContactAddress::new(&recipient.addr) {
2716 Err(err) => {
2717 warn!(
2718 context,
2719 "Could not add contact for recipient with address {:?}: {:#}",
2720 recipient.addr,
2721 err
2722 );
2723 continue;
2724 }
2725 Ok(recipient_addr) => recipient_addr,
2726 };
2727
2728 let (_, modified) = Contact::add_or_lookup(
2729 context,
2730 &recipient.display_name.unwrap_or_default(),
2731 &recipient_addr,
2732 Origin::OutgoingTo,
2733 )
2734 .await?;
2735 if modified != Modifier::None {
2736 any_modified = true;
2737 }
2738 }
2739 if any_modified {
2740 context.emit_event(EventType::ContactsChanged(None));
2741 }
2742
2743 Ok(())
2744}
2745
2746#[cfg(test)]
2747mod imap_tests;