1use core::{num::NonZeroU32, sync::atomic::AtomicBool};
15
16use alloc::{
17 string::{String, ToString},
18 sync::Arc,
19 vec,
20 vec::Vec,
21};
22
23use std::{
24 io::{self, ErrorKind, Read, Write},
25 sync::mpsc::Sender,
26};
27
28use io_imap::{
29 client::{ImapClientStd as InnerImapClientStd, ImapClientStdError as InnerImapClientStdError},
30 coroutine::*,
31 types::{
32 core::{IString, NString},
33 fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
34 mailbox::Mailbox as ImapMailbox,
35 response::Capability,
36 sequence::SequenceSet,
37 },
38};
39#[cfg(any(
40 feature = "rustls-ring",
41 feature = "rustls-aws",
42 feature = "native-tls"
43))]
44use pimalaya_stream::{sasl::Sasl, tls::Tls};
45use thiserror::Error;
46#[cfg(any(
47 feature = "rustls-ring",
48 feature = "rustls-aws",
49 feature = "native-tls"
50))]
51use url::Url;
52
53#[cfg(feature = "search")]
54use crate::{
55 envelope::imap::search::{ImapEnvelopeSearch, ImapEnvelopeSearchError},
56 search::query::SearchEmailsQuery,
57};
58use crate::{
59 envelope::{
60 event::WatchEvent,
61 imap::{
62 diff::{
63 ImapState, envelope_from_items, flag_update_from_items, new_message_item_names,
64 new_message_window,
65 },
66 list::{ImapEnvelopeList, ImapEnvelopeListError},
67 watch::{ImapWatchMailbox, ImapWatchMailboxError, ImapWatchMailboxYield},
68 },
69 types::{Envelope, EnvelopeDiff, FlagUpdate},
70 },
71 flag::{
72 imap::store::{ImapFlagStore, ImapFlagStoreError},
73 types::{Flag, FlagOp},
74 },
75 imap::convert::parse_mailbox,
76 mailbox::{
77 imap::{
78 create::{ImapMailboxCreate, ImapMailboxCreateError},
79 delete::{ImapMailboxDelete, ImapMailboxDeleteError},
80 list::{ImapMailboxList, ImapMailboxListError},
81 },
82 types::Mailbox,
83 },
84 message::imap::{
85 add::{ImapMessageAdd, ImapMessageAddError},
86 copy::{ImapMessageCopy, ImapMessageCopyError},
87 delete::{ImapMessageDelete, ImapMessageDeleteError},
88 get::{ImapMessageGet, ImapMessageGetError},
89 r#move::{ImapMessageMove, ImapMessageMoveError},
90 },
91};
92
93#[derive(Debug, Error)]
97pub enum ImapClientError {
98 #[error(transparent)]
99 Io(#[from] io::Error),
100 #[error(transparent)]
101 MailboxList(#[from] ImapMailboxListError),
102 #[error(transparent)]
103 EnvelopeList(#[from] ImapEnvelopeListError),
104 #[cfg(feature = "search")]
105 #[error(transparent)]
106 EnvelopeSearch(#[from] ImapEnvelopeSearchError),
107 #[error(transparent)]
108 FlagStore(#[from] ImapFlagStoreError),
109 #[error(transparent)]
110 MailboxCreate(#[from] ImapMailboxCreateError),
111 #[error(transparent)]
112 MailboxDelete(#[from] ImapMailboxDeleteError),
113 #[error(transparent)]
114 MessageAdd(#[from] ImapMessageAddError),
115 #[error(transparent)]
116 MessageCopy(#[from] ImapMessageCopyError),
117 #[error(transparent)]
118 MessageDelete(#[from] ImapMessageDeleteError),
119 #[error(transparent)]
120 MessageGet(#[from] ImapMessageGetError),
121 #[error(transparent)]
122 MessageMove(#[from] ImapMessageMoveError),
123 #[error(transparent)]
124 WatchMailbox(#[from] ImapWatchMailboxError),
125 #[error(transparent)]
126 Inner(#[from] InnerImapClientStdError),
127}
128
129const READ_BUFFER_SIZE: usize = 16 * 1024;
130
131pub struct ImapClientStd {
144 pub inner: InnerImapClientStd,
145 pub auto_select: bool,
146 pub capabilities: Vec<Capability<'static>>,
147}
148
149impl ImapClientStd {
150 pub fn new<S: Read + Write + Send + 'static>(stream: S) -> Self {
155 Self {
156 inner: InnerImapClientStd::new(stream),
157 auto_select: true,
158 capabilities: Vec::new(),
159 }
160 }
161
162 pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, ImapClientError>
170 where
171 C: ImapCoroutine<Yield = ImapYield, Return = Result<T, E>>,
172 ImapClientError: From<E>,
173 {
174 let mut buf = [0u8; READ_BUFFER_SIZE];
175 let mut arg: Option<&[u8]> = None;
176
177 loop {
178 match coroutine.resume(&mut self.inner.fragmentizer, arg.take()) {
179 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
180 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
181 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
182 let n = self.inner.stream.read(&mut buf)?;
183 arg = Some(&buf[..n]);
184 }
185 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
186 self.inner.stream.write_all(&bytes)?;
187 }
188 }
189 }
190 }
191
192 pub fn ping(&mut self) -> Result<(), ImapClientError> {
196 Ok(self.inner.noop()?)
197 }
198
199 pub fn list_mailboxes(&mut self, with_counts: bool) -> Result<Vec<Mailbox>, ImapClientError> {
203 self.run(ImapMailboxList::new(with_counts))
204 }
205
206 pub fn list_envelopes(
210 &mut self,
211 mailbox: &str,
212 page: Option<u32>,
213 page_size: Option<u32>,
214 with_attachment: bool,
215 ) -> Result<Vec<Envelope>, ImapClientError> {
216 self.run(ImapEnvelopeList::new(
217 mailbox,
218 page,
219 page_size,
220 with_attachment,
221 )?)
222 }
223
224 #[cfg(feature = "search")]
228 pub fn search_envelopes(
229 &mut self,
230 mailbox: &str,
231 query: Option<&SearchEmailsQuery>,
232 page: Option<u32>,
233 page_size: Option<u32>,
234 with_attachment: bool,
235 ) -> Result<Vec<Envelope>, ImapClientError> {
236 self.run(ImapEnvelopeSearch::new(
237 mailbox,
238 query,
239 page,
240 page_size,
241 with_attachment,
242 )?)
243 }
244
245 pub fn store_flags(
250 &mut self,
251 mailbox: &str,
252 ids: &[&str],
253 flags: &[Flag],
254 op: FlagOp,
255 ) -> Result<(), ImapClientError> {
256 let auto_select = self.auto_select;
257 self.run(ImapFlagStore::new(mailbox, ids, flags, op, auto_select)?)
258 }
259
260 pub fn get_message(&mut self, mailbox: &str, id: &str) -> Result<Vec<u8>, ImapClientError> {
263 let auto_select = self.auto_select;
264 self.run(ImapMessageGet::new(mailbox, id, auto_select)?)
265 }
266
267 pub fn add_message(
271 &mut self,
272 mailbox: &str,
273 flags: &[Flag],
274 raw: Vec<u8>,
275 ) -> Result<String, ImapClientError> {
276 self.run(ImapMessageAdd::new(mailbox, flags, raw)?)
277 }
278
279 pub fn create_mailbox(&mut self, name: &str) -> Result<(), ImapClientError> {
281 self.run(ImapMailboxCreate::new(name)?)
282 }
283
284 pub fn delete_mailbox(&mut self, name: &str) -> Result<(), ImapClientError> {
286 self.run(ImapMailboxDelete::new(name)?)
287 }
288
289 pub fn delete_message(&mut self, mailbox: &str, id: &str) -> Result<(), ImapClientError> {
292 let auto_select = self.auto_select;
293 self.run(ImapMessageDelete::new(mailbox, id, auto_select)?)
294 }
295
296 pub fn copy_messages(
299 &mut self,
300 from: &str,
301 to: &str,
302 ids: &[&str],
303 ) -> Result<(), ImapClientError> {
304 let auto_select = self.auto_select;
305 self.run(ImapMessageCopy::new(from, to, ids, auto_select)?)
306 }
307
308 pub fn move_messages(
311 &mut self,
312 from: &str,
313 to: &str,
314 ids: &[&str],
315 ) -> Result<(), ImapClientError> {
316 let auto_select = self.auto_select;
317 self.run(ImapMessageMove::new(from, to, ids, auto_select)?)
318 }
319
320 pub fn watch_mailbox(
341 &mut self,
342 mailbox: &str,
343 shutdown: Arc<AtomicBool>,
344 tx: Sender<WatchEvent>,
345 ) -> Result<(), ImapClientError> {
346 let mut coroutine = ImapWatchMailbox::new(mailbox, &self.capabilities, shutdown)?;
347 let mut buf = [0u8; READ_BUFFER_SIZE];
348 let mut bytes: Option<&[u8]> = None;
349
350 loop {
351 match coroutine.resume(&mut self.inner.fragmentizer, bytes) {
352 ImapCoroutineState::Complete(result) => return Ok(result?),
353 ImapCoroutineState::Yielded(ImapWatchMailboxYield::WantsRead) => {
354 match self.inner.stream.read(&mut buf) {
355 Ok(n) => bytes = Some(&buf[..n]),
356 Err(err) if err.kind() == ErrorKind::WouldBlock => bytes = None,
357 Err(err) if err.kind() == ErrorKind::TimedOut => bytes = None,
358 Err(err) => return Err(err.into()),
359 }
360 }
361 ImapCoroutineState::Yielded(ImapWatchMailboxYield::WantsWrite(out)) => {
362 self.inner.stream.write_all(&out)?;
363 bytes = None;
364 }
365 ImapCoroutineState::Yielded(ImapWatchMailboxYield::Event(evt)) => {
366 if tx.send(evt).is_err() {
367 return Ok(());
368 }
369 bytes = None;
370 }
371 }
372 }
373 }
374
375 pub fn diff_envelopes(
386 &mut self,
387 mailbox: &str,
388 state: Option<&[u8]>,
389 ) -> Result<EnvelopeDiff, ImapClientError> {
390 let mbox = parse_mailbox(mailbox).map_err(ImapEnvelopeListError::from)?;
391
392 if !self.capabilities.contains(&Capability::QResync) {
393 return Ok(EnvelopeDiff::FullListRequired { new_state: None });
394 }
395
396 let cached = state.and_then(ImapState::decode);
397
398 let Some(cached) = cached else {
399 return self.diff_baseline(mbox);
400 };
401
402 let Some(uid_validity_nz) = NonZeroU32::new(cached.uid_validity) else {
403 return self.diff_baseline(mbox);
404 };
405
406 let capabilities = self.capabilities.clone();
407 let select_data = match self.inner.select_qresync(
408 mbox.clone(),
409 uid_validity_nz,
410 cached.highest_mod_seq,
411 &capabilities,
412 ) {
413 Ok(data) => data,
414 Err(_) => return self.diff_baseline(mbox),
415 };
416
417 let server_uid_validity = select_data
418 .uid_validity
419 .map(NonZeroU32::get)
420 .unwrap_or(cached.uid_validity);
421 if server_uid_validity != cached.uid_validity {
422 return self.diff_baseline(mbox);
423 }
424
425 let flag_updates: Vec<FlagUpdate> = select_data
426 .changed
427 .iter()
428 .filter_map(|fetch| flag_update_from_items(fetch.items.as_ref()))
429 .collect();
430
431 let vanished_ids: Vec<String> = select_data
432 .vanished_earlier
433 .iter()
434 .map(|uid| uid.get().to_string())
435 .collect();
436
437 let mut new_envelopes: Vec<Envelope> = Vec::new();
438 if let Some(window) = new_message_window(cached.highest_uid) {
439 if let Ok(sequence_set) = SequenceSet::try_from(window.as_str()) {
440 let data = self
441 .inner
442 .fetch(sequence_set, new_message_item_names(), true)?;
443 new_envelopes = data
444 .into_iter()
445 .map(|(_, items)| envelope_from_items(items.into_inner()))
446 .collect();
447 }
448 }
449
450 let highest_uid = new_envelopes
451 .iter()
452 .filter_map(|e| e.id.parse::<u32>().ok())
453 .max()
454 .unwrap_or(cached.highest_uid);
455
456 let new_highest_mod_seq = select_data
457 .highest_mod_seq
458 .unwrap_or(cached.highest_mod_seq);
459
460 let new_state = ImapState {
461 uid_validity: server_uid_validity,
462 highest_mod_seq: new_highest_mod_seq,
463 highest_uid,
464 }
465 .encode();
466
467 Ok(EnvelopeDiff::Incremental {
468 new_state,
469 flag_updates,
470 new_envelopes,
471 vanished_ids,
472 })
473 }
474
475 fn diff_baseline(
479 &mut self,
480 mbox: ImapMailbox<'static>,
481 ) -> Result<EnvelopeDiff, ImapClientError> {
482 let select = self.inner.select(mbox)?;
483 let Some(uid_validity) = select.uid_validity.map(NonZeroU32::get) else {
484 return Ok(EnvelopeDiff::FullListRequired { new_state: None });
485 };
486
487 let exists = select.exists.unwrap_or(0);
488 let mut highest_uid: u32 = 0;
489 if exists > 0 {
490 let sequence_set: SequenceSet = "*"
491 .try_into()
492 .expect("`*` is a valid sequence set spelling");
493 let item_names =
494 MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Uid]);
495 let data = self.inner.fetch(sequence_set, item_names, false)?;
496 highest_uid = data
497 .into_values()
498 .flat_map(|items| items.into_inner().into_iter())
499 .filter_map(|item| match item {
500 MessageDataItem::Uid(u) => Some(u.get()),
501 _ => None,
502 })
503 .max()
504 .unwrap_or(0);
505 }
506
507 let highest_mod_seq = select.highest_mod_seq.unwrap_or(0);
508
509 let new_state = ImapState {
510 uid_validity,
511 highest_mod_seq,
512 highest_uid,
513 }
514 .encode();
515
516 Ok(EnvelopeDiff::FullListRequired {
517 new_state: Some(new_state),
518 })
519 }
520}
521
522#[cfg(any(
523 feature = "rustls-ring",
524 feature = "rustls-aws",
525 feature = "native-tls"
526))]
527impl ImapClientStd {
528 pub fn connect(
539 url: &Url,
540 tls: &Tls,
541 starttls: bool,
542 sasl: Option<impl Into<Sasl>>,
543 auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
544 ) -> Result<Self, ImapClientError> {
545 let (inner, capabilities) = InnerImapClientStd::connect(url, tls, starttls, sasl, auto_id)?;
546
547 Ok(Self {
548 inner,
549 auto_select: true,
550 capabilities,
551 })
552 }
553}