1use core::{
8 any::Any,
9 fmt,
10 num::{NonZeroU32, NonZeroU64},
11 sync::atomic::{AtomicBool, Ordering},
12 time::Duration,
13};
14
15#[cfg(any(
16 feature = "rustls-aws",
17 feature = "rustls-ring",
18 feature = "native-tls"
19))]
20use alloc::string::ToString;
21use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, vec, vec::Vec};
22
23use std::{
24 io::{self, Read, Write},
25 sync::{
26 Arc,
27 mpsc::{self, Receiver, RecvTimeoutError, TryRecvError},
28 },
29 thread::{self, JoinHandle},
30};
31
32use imap_codec::{
33 fragmentizer::Fragmentizer,
34 imap_types::{
35 command::SelectParameter,
36 core::{IString, NString, Vec1},
37 extensions::{
38 enable::CapabilityEnable,
39 sort::SortCriterion,
40 thread::{Thread, ThreadingAlgorithm},
41 },
42 fetch::{MacroOrMessageDataItemNames, MessageDataItem},
43 flag::{Flag, StoreType},
44 mailbox::{ListMailbox, Mailbox},
45 response::Capability,
46 search::SearchKey,
47 sequence::SequenceSet,
48 status::{StatusDataItem, StatusDataItemName},
49 },
50};
51#[cfg(feature = "scram")]
52#[cfg(any(
53 feature = "rustls-aws",
54 feature = "rustls-ring",
55 feature = "native-tls"
56))]
57use pimalaya_stream::sasl::SaslScramSha256;
58#[cfg(any(
59 feature = "rustls-aws",
60 feature = "rustls-ring",
61 feature = "native-tls"
62))]
63use pimalaya_stream::{
64 sasl::{Sasl, SaslAnonymous, SaslLogin, SaslOauthbearer, SaslPlain, SaslXoauth2},
65 std::stream::StreamStd,
66 tls::Tls,
67};
68#[cfg(any(
69 feature = "rustls-aws",
70 feature = "rustls-ring",
71 feature = "native-tls"
72))]
73use secrecy::ExposeSecret;
74use thiserror::Error;
75#[cfg(any(
76 feature = "rustls-aws",
77 feature = "rustls-ring",
78 feature = "native-tls"
79))]
80use url::Url;
81
82#[cfg(feature = "scram")]
83use crate::rfc7677::auth_scram_sha_256::*;
84use crate::{
85 coroutine::*,
86 rfc2971::id::*,
87 rfc3501::{
88 append::*, append_stream::*, capability::*, check::*, close::*, copy::*, create::*,
89 delete::*, examine::*, expunge::*, fetch::*, fetch_stream::*, fetch_stream_batch::*,
90 greeting::*, list::*, login::*, logout::*, lsub::*, noop::*, raw::*, rename::*, search::*,
91 select::*, starttls::*, status::*, store::*, subscribe::*, unsubscribe::*,
92 },
93 rfc3691::unselect::*,
94 rfc4315::expunge_uid::*,
95 rfc5161::enable::*,
96 rfc5256::{sort::*, thread::*},
97 rfc6851::r#move::*,
98 rfc7628::auth_oauthbearer::*,
99 sasl::{auth_anonymous::*, auth_login::*, auth_plain::*, auth_xoauth2::*},
100 watch::*,
101};
102
103#[derive(Debug, Error)]
105pub enum ImapClientStdError {
106 #[error(transparent)]
108 Greeting(#[from] ImapGreetingGetError),
109 #[error(transparent)]
111 Login(#[from] ImapLoginError),
112 #[error(transparent)]
114 AuthLogin(#[from] ImapAuthLoginError),
115 #[error(transparent)]
117 AuthPlain(#[from] ImapAuthPlainError),
118 #[error(transparent)]
120 AuthAnonymous(#[from] ImapAuthAnonymousError),
121 #[error(transparent)]
123 AuthOAuthBearer(#[from] ImapAuthOauthbearerError),
124 #[error(transparent)]
126 AuthXOAuth2(#[from] ImapAuthXoauth2Error),
127 #[cfg(feature = "scram")]
129 #[error(transparent)]
130 AuthScramSha256(#[from] ImapAuthScramSha256Error),
131 #[cfg(any(
133 feature = "rustls-aws",
134 feature = "rustls-ring",
135 feature = "native-tls"
136 ))]
137 #[cfg(not(feature = "scram"))]
138 #[error("SCRAM-SHA-256 SASL mechanism requires the `scram` cargo feature")]
139 ScramSha256NotEnabled,
140 #[error(transparent)]
142 Logout(#[from] ImapLogoutError),
143 #[error(transparent)]
145 Capability(#[from] ImapCapabilityGetError),
146 #[error(transparent)]
148 Noop(#[from] ImapNoopError),
149 #[error(transparent)]
151 Raw(#[from] ImapRawError),
152 #[error(transparent)]
154 ServerId(#[from] ImapServerIdError),
155 #[error(transparent)]
157 ExtensionEnable(#[from] ImapExtensionEnableError),
158 #[error(transparent)]
160 MailboxList(#[from] ImapMailboxListError),
161 #[error(transparent)]
163 MailboxLsub(#[from] ImapMailboxLsubError),
164 #[error(transparent)]
166 MailboxStatus(#[from] ImapMailboxStatusError),
167 #[error(transparent)]
169 MailboxCreate(#[from] ImapMailboxCreateError),
170 #[error(transparent)]
172 MailboxDelete(#[from] ImapMailboxDeleteError),
173 #[error(transparent)]
175 MailboxRename(#[from] ImapMailboxRenameError),
176 #[error(transparent)]
178 MailboxSubscribe(#[from] ImapMailboxSubscribeError),
179 #[error(transparent)]
181 MailboxUnsubscribe(#[from] ImapMailboxUnsubscribeError),
182 #[error(transparent)]
184 MailboxSelect(#[from] ImapMailboxSelectError),
185 #[error(transparent)]
187 MailboxExamine(#[from] ImapMailboxExamineError),
188 #[error(transparent)]
190 MailboxWatch(#[from] ImapMailboxWatchError),
191 #[error(transparent)]
193 MailboxClose(#[from] ImapMailboxCloseError),
194 #[error(transparent)]
196 MailboxUnselect(#[from] ImapMailboxUnselectError),
197 #[error(transparent)]
199 MailboxCheck(#[from] ImapMailboxCheckError),
200 #[error(transparent)]
202 MailboxExpunge(#[from] ImapMailboxExpungeError),
203 #[error(transparent)]
205 MessageExpungeUid(#[from] ImapMessageExpungeUidError),
206 #[error(transparent)]
208 MessageSort(#[from] ImapMessageSortError),
209 #[error(transparent)]
211 MessageFetch(#[from] ImapMessageFetchError),
212 #[error(transparent)]
214 MessageFetchStream(#[from] ImapMessageFetchStreamError),
215 #[error(transparent)]
217 MessageFetchStreamBatch(#[from] ImapMessageFetchStreamBatchError),
218 #[error(transparent)]
220 MessageSearch(#[from] ImapMessageSearchError),
221 #[error(transparent)]
223 MessageStore(#[from] ImapMessageStoreError),
224 #[error(transparent)]
226 MessageCopy(#[from] ImapMessageCopyError),
227 #[error(transparent)]
229 MessageMove(#[from] ImapMessageMoveError),
230 #[error(transparent)]
232 MessageAppend(#[from] ImapMessageAppendError),
233 #[error(transparent)]
235 MessageAppendStream(#[from] ImapMessageAppendStreamError),
236 #[error(transparent)]
238 MessageThread(#[from] ImapMessageThreadError),
239 #[error(transparent)]
241 Io(#[from] io::Error),
242 #[error(transparent)]
244 StartTls(#[from] ImapStartTlsError),
245 #[cfg(any(
247 feature = "rustls-aws",
248 feature = "rustls-ring",
249 feature = "native-tls"
250 ))]
251 #[error(transparent)]
252 Tls(#[from] anyhow::Error),
253 #[cfg(any(
255 feature = "rustls-aws",
256 feature = "rustls-ring",
257 feature = "native-tls"
258 ))]
259 #[error("IMAP URL `{0}` has no host")]
260 UrlMissingHost(String),
261 #[cfg(any(
263 feature = "rustls-aws",
264 feature = "rustls-ring",
265 feature = "native-tls"
266 ))]
267 #[error("IMAP URL `{0}` has unsupported scheme `{1}` (expected `imap` or `imaps`)")]
268 UrlUnsupportedScheme(String, String),
269 #[cfg(any(
271 feature = "rustls-aws",
272 feature = "rustls-ring",
273 feature = "native-tls"
274 ))]
275 #[error("STARTTLS requested on an `imaps://` URL: TLS is already active")]
276 StartTlsOverTls,
277 #[error("Invalid IMAP LOGIN credentials")]
279 InvalidLoginCredentials(#[from] imap_codec::imap_types::error::ValidationError),
280 #[error("IMAP server does not advertise QRESYNC capability")]
282 QresyncNotSupported,
283 #[error("Invalid mod-sequence value: 0")]
285 InvalidModSeq,
286}
287
288const READ_BUFFER_SIZE: usize = 16 * 1024;
289const BODY_COPY_BUFFER_SIZE: usize = 128 * 1024;
295const FRAGMENTIZER_MAX_MESSAGE_SIZE: u32 = 100 * 1024 * 1024;
296
297pub fn default_alpn() -> Vec<String> {
299 vec![String::from("imap")]
300}
301
302pub fn default_port(scheme: &str) -> u16 {
304 if scheme.eq_ignore_ascii_case("imaps") {
305 993
306 } else {
307 143
308 }
309}
310
311pub struct ImapClientStd {
314 pub stream: Box<dyn ImapStream>,
316 pub fragmentizer: Fragmentizer,
319 pub auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
325 pub pre_authenticated: bool,
330}
331
332impl ImapClientStd {
333 pub fn new<S: ImapStream + 'static>(stream: S) -> Self {
336 Self {
337 stream: Box::new(stream),
338 fragmentizer: Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE),
339 auto_id: None,
340 pre_authenticated: false,
341 }
342 }
343
344 pub fn set_stream<S: ImapStream + 'static>(&mut self, stream: S) {
346 self.stream = Box::new(stream);
347 }
348
349 pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, ImapClientStdError>
355 where
356 C: ImapCoroutine<Yield = ImapYield, Return = Result<T, E>>,
357 ImapClientStdError: From<E>,
358 {
359 let mut buf = [0u8; READ_BUFFER_SIZE];
360 let mut arg: Option<&[u8]> = None;
361
362 loop {
363 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
364 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
365 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
366 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
367 let n = self.stream.read(&mut buf)?;
368 if n == 0 {
371 let kind = io::ErrorKind::UnexpectedEof;
372 let err = io::Error::new(kind, "IMAP server closed the connection");
373 return Err(err.into());
374 }
375 arg = Some(&buf[..n]);
376 }
377 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
378 self.stream.write_all(&bytes)?;
379 arg = None;
380 }
381 }
382 }
383 }
384
385 pub fn greeting(&mut self) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
388 Ok(self
389 .run(ImapGreetingGet::new(ImapGreetingGetOptions {
390 ensure_capabilities: true,
391 }))?
392 .capability)
393 }
394
395 pub fn login(
397 &mut self,
398 user: impl AsRef<str>,
399 password: impl AsRef<str>,
400 opts: ImapLoginOptions,
401 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
402 self.run(ImapLogin::new(user, password, opts)?)
403 }
404
405 pub fn starttls(&mut self) -> Result<Vec<u8>, ImapClientStdError> {
412 self.run(ImapStartTls::new())
413 }
414
415 pub fn auth_anonymous(
417 &mut self,
418 message: Option<impl AsRef<str>>,
419 opts: ImapAuthAnonymousOptions,
420 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
421 self.run(ImapAuthAnonymous::new(message, opts))
422 }
423
424 pub fn auth_login(
427 &mut self,
428 user: impl AsRef<str>,
429 password: impl AsRef<str>,
430 opts: ImapAuthLoginOptions,
431 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
432 self.run(ImapAuthLogin::new(user, password, opts))
433 }
434
435 pub fn auth_plain(
437 &mut self,
438 authzid: Option<impl AsRef<str>>,
439 authcid: impl AsRef<str>,
440 password: impl AsRef<str>,
441 opts: ImapAuthPlainOptions,
442 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
443 self.run(ImapAuthPlain::new(authzid, authcid, password, opts))
444 }
445
446 pub fn auth_oauthbearer(
449 &mut self,
450 user: impl AsRef<str>,
451 host: impl AsRef<str>,
452 port: u16,
453 token: impl AsRef<str>,
454 opts: ImapAuthOauthbearerOptions,
455 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
456 self.run(ImapAuthOauthbearer::new(user, host, port, token, opts))
457 }
458
459 pub fn auth_xoauth2(
462 &mut self,
463 user: impl AsRef<str>,
464 token: impl AsRef<str>,
465 opts: ImapAuthXoauth2Options,
466 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
467 self.run(ImapAuthXoauth2::new(user, token, opts))
468 }
469
470 #[cfg(feature = "scram")]
472 pub fn auth_scram_sha256(
473 &mut self,
474 user: impl AsRef<str>,
475 password: impl AsRef<str>,
476 opts: ImapAuthScramSha256Options,
477 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
478 self.run(ImapAuthScramSha256::new(user, password, opts))
479 }
480
481 pub fn logout(&mut self) -> Result<(), ImapClientStdError> {
483 self.run(ImapLogout::new())
484 }
485
486 pub fn capability(&mut self) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
488 self.run(ImapCapabilityGet::new())
489 }
490
491 pub fn noop(&mut self) -> Result<(), ImapClientStdError> {
493 self.run(ImapNoop::new())
494 }
495
496 pub fn raw(&mut self, command: impl AsRef<[u8]>) -> Result<String, ImapClientStdError> {
505 self.run(ImapRaw::new(command)?)
506 }
507
508 pub fn id(
510 &mut self,
511 opts: ImapServerIdOptions,
512 ) -> Result<Option<Vec<(IString<'static>, NString<'static>)>>, ImapClientStdError> {
513 self.run(ImapServerId::new(opts))
514 }
515
516 pub fn enable(
518 &mut self,
519 capabilities: Vec1<CapabilityEnable<'static>>,
520 ) -> Result<Option<Vec<CapabilityEnable<'static>>>, ImapClientStdError> {
521 self.run(ImapExtensionEnable::new(capabilities))
522 }
523
524 pub fn list(
526 &mut self,
527 reference: Mailbox<'static>,
528 pattern: ListMailbox<'static>,
529 ) -> Result<ImapMailboxListing, ImapClientStdError> {
530 self.run(ImapMailboxList::new(reference, pattern))
531 }
532
533 pub fn lsub(
536 &mut self,
537 reference: Mailbox<'static>,
538 pattern: ListMailbox<'static>,
539 ) -> Result<ImapMailboxListing, ImapClientStdError> {
540 self.run(ImapMailboxLsub::new(reference, pattern))
541 }
542
543 pub fn status(
545 &mut self,
546 mailbox: Mailbox<'static>,
547 item_names: impl Into<Cow<'static, [StatusDataItemName]>>,
548 ) -> Result<Vec<StatusDataItem>, ImapClientStdError> {
549 self.run(ImapMailboxStatus::new(mailbox, item_names))
550 }
551
552 pub fn create(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
554 self.run(ImapMailboxCreate::new(mailbox))
555 }
556
557 pub fn delete(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
559 self.run(ImapMailboxDelete::new(mailbox))
560 }
561
562 pub fn rename(
564 &mut self,
565 from: Mailbox<'static>,
566 to: Mailbox<'static>,
567 ) -> Result<(), ImapClientStdError> {
568 self.run(ImapMailboxRename::new(from, to))
569 }
570
571 pub fn subscribe(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
573 self.run(ImapMailboxSubscribe::new(mailbox))
574 }
575
576 pub fn unsubscribe(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
578 self.run(ImapMailboxUnsubscribe::new(mailbox))
579 }
580
581 pub fn select(
583 &mut self,
584 mailbox: Mailbox<'static>,
585 opts: ImapMailboxSelectOptions,
586 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
587 self.run(ImapMailboxSelect::new(mailbox, opts))
588 }
589
590 pub fn examine(
592 &mut self,
593 mailbox: Mailbox<'static>,
594 opts: ImapMailboxExamineOptions,
595 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
596 self.run(ImapMailboxExamine::new(mailbox, opts))
597 }
598
599 pub fn select_qresync(
604 &mut self,
605 mailbox: Mailbox<'static>,
606 uid_validity: NonZeroU32,
607 highest_mod_seq: u64,
608 capability: &[Capability<'static>],
609 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
610 if !capability.contains(&Capability::QResync) {
611 return Err(ImapClientStdError::QresyncNotSupported);
612 }
613
614 let Some(highest_mod_seq) = NonZeroU64::new(highest_mod_seq) else {
615 return Err(ImapClientStdError::InvalidModSeq);
616 };
617
618 let parameters = vec![SelectParameter::QResync {
619 uid_validity,
620 mod_sequence_value: highest_mod_seq,
621 known_uids: None,
622 seq_match_data: None,
623 }];
624
625 self.select(mailbox, ImapMailboxSelectOptions { parameters })
626 }
627
628 pub fn close(&mut self) -> Result<(), ImapClientStdError> {
630 self.run(ImapMailboxClose::new())
631 }
632
633 pub fn unselect(&mut self) -> Result<(), ImapClientStdError> {
635 self.run(ImapMailboxUnselect::new())
636 }
637
638 pub fn check(&mut self) -> Result<(), ImapClientStdError> {
640 self.run(ImapMailboxCheck::new())
641 }
642
643 pub fn expunge(&mut self) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
645 self.run(ImapMailboxExpunge::new())
646 }
647
648 pub fn uid_expunge(
655 &mut self,
656 sequence_set: SequenceSet,
657 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
658 self.run(ImapMessageExpungeUid::new(sequence_set))
659 }
660
661 pub fn watch_mailbox(
666 self,
667 mailbox: Mailbox<'static>,
668 capability: &[Capability<'static>],
669 ) -> Result<ImapMailboxWatchStream, ImapClientStdError> {
670 let shutdown = Arc::new(AtomicBool::new(false));
671 let mut watcher = ImapMailboxWatch::new(capability, mailbox, shutdown.clone())?;
672 let mut fragmentizer = self.fragmentizer;
673 let mut stream = self.stream;
674
675 stream.set_read_timeout(Some(Duration::from_secs(5)))?;
679
680 let (tx, rx) = mpsc::sync_channel::<Result<ImapMailboxWatchEvent, ImapClientStdError>>(256);
681 let shutdown_handle = shutdown.clone();
682 let handle = thread::spawn(move || {
683 let mut buf = [0u8; READ_BUFFER_SIZE];
684 let mut arg: Option<Vec<u8>> = None;
685
686 loop {
687 match watcher.resume(&mut fragmentizer, arg.as_deref()) {
688 ImapCoroutineState::Yielded(ImapMailboxWatchYield::Event(e)) => {
689 arg = None;
690 if tx.send(Ok(e)).is_err() {
691 return;
692 }
693 }
694 ImapCoroutineState::Complete(Ok(())) => return,
695 ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead) => {
696 match stream.read(&mut buf) {
697 Ok(0) => {
698 let eof = io::ErrorKind::UnexpectedEof;
699 let err = "IMAP server closed the connection during watch";
700 tx.send(Err(io::Error::new(eof, err).into())).ok();
701 return;
702 }
703 Ok(n) => arg = Some(buf[..n].to_vec()),
704 Err(err)
709 if matches!(
710 err.kind(),
711 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
712 ) =>
713 {
714 if shutdown.load(Ordering::SeqCst) {
715 return;
716 }
717 arg = None;
718 }
719 Err(err) => {
720 tx.send(Err(err.into())).ok();
721 return;
722 }
723 }
724 }
725 ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(bytes)) => {
726 if let Err(err) = stream.write_all(&bytes) {
727 tx.send(Err(err.into())).ok();
728 return;
729 }
730 arg = None;
731 }
732 ImapCoroutineState::Complete(Err(err)) => {
733 tx.send(Err(err.into())).ok();
734 return;
735 }
736 }
737 }
738 });
739
740 Ok(ImapMailboxWatchStream {
741 rx,
742 handle: Some(handle),
743 shutdown: shutdown_handle,
744 })
745 }
746
747 pub fn fetch(
749 &mut self,
750 sequence_set: SequenceSet,
751 items: MacroOrMessageDataItemNames<'static>,
752 opts: ImapMessageFetchOptions,
753 ) -> Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapClientStdError> {
754 self.run(ImapMessageFetch::new(sequence_set, items, opts))
755 }
756
757 pub fn fetch_body_stream(
763 &mut self,
764 id: NonZeroU32,
765 uid: bool,
766 mut sink: impl Write,
767 ) -> Result<(), ImapClientStdError> {
768 let mut coroutine = ImapMessageFetchStream::new(id, uid);
769 let mut buf = [0u8; READ_BUFFER_SIZE];
770 let mut body_buf = vec![0u8; BODY_COPY_BUFFER_SIZE];
773 let mut arg: Option<&[u8]> = None;
774
775 loop {
776 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
777 ImapCoroutineState::Complete(Ok(())) => return Ok(()),
778 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
779 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsRead) => {
780 let n = self.stream.read(&mut buf)?;
781 arg = Some(&buf[..n]);
782 }
783 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsWrite(bytes)) => {
784 self.stream.write_all(&bytes)?;
785 arg = None;
786 }
787 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::BodyChunk(bytes)) => {
788 sink.write_all(&bytes)?;
789 arg = None;
790 }
791 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsStream { len }) => {
792 let mut remaining = len as u64;
795 let mut short = false;
796 while remaining > 0 {
797 let want = remaining.min(body_buf.len() as u64) as usize;
798 let n = self.stream.read(&mut body_buf[..want])?;
799 if n == 0 {
800 short = true;
801 break;
802 }
803 sink.write_all(&body_buf[..n])?;
804 remaining -= n as u64;
805 }
806 arg = short.then_some(&[]);
809 }
810 }
811 }
812 }
813
814 pub fn fetch_bodies_stream<S: Write>(
821 &mut self,
822 sequence_set: SequenceSet,
823 uid: bool,
824 mut open: impl FnMut(u32) -> io::Result<S>,
825 mut done: impl FnMut(u32, S) -> io::Result<()>,
826 ) -> Result<(), ImapClientStdError> {
827 let mut coroutine = ImapMessageFetchStreamBatch::new(sequence_set, uid);
828 let mut buf = [0u8; READ_BUFFER_SIZE];
829 let mut body_buf = vec![0u8; BODY_COPY_BUFFER_SIZE];
830 let mut current: Option<(u32, S)> = None;
833 let mut arg: Option<&[u8]> = None;
834
835 loop {
836 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
837 ImapCoroutineState::Complete(Ok(())) => return Ok(()),
838 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
839 ImapCoroutineState::Yielded(ImapMessageFetchStreamBatchYield::WantsRead) => {
840 let n = self.stream.read(&mut buf)?;
841 arg = Some(&buf[..n]);
842 }
843 ImapCoroutineState::Yielded(ImapMessageFetchStreamBatchYield::WantsWrite(
844 bytes,
845 )) => {
846 self.stream.write_all(&bytes)?;
847 arg = None;
848 }
849 ImapCoroutineState::Yielded(ImapMessageFetchStreamBatchYield::MessageStart {
850 uid,
851 }) => {
852 current = Some((uid, open(uid)?));
853 arg = None;
854 }
855 ImapCoroutineState::Yielded(ImapMessageFetchStreamBatchYield::BodyChunk(bytes)) => {
856 let (_, sink) = current.as_mut().expect("body chunk within a message");
857 sink.write_all(&bytes)?;
858 arg = None;
859 }
860 ImapCoroutineState::Yielded(ImapMessageFetchStreamBatchYield::WantsStream {
861 len,
862 }) => {
863 let (_, sink) = current.as_mut().expect("stream within a message");
864 let mut remaining = len as u64;
865 let mut short = false;
866 while remaining > 0 {
867 let want = remaining.min(body_buf.len() as u64) as usize;
868 let n = self.stream.read(&mut body_buf[..want])?;
869 if n == 0 {
870 short = true;
871 break;
872 }
873 sink.write_all(&body_buf[..n])?;
874 remaining -= n as u64;
875 }
876 arg = short.then_some(&[]);
877 }
878 ImapCoroutineState::Yielded(ImapMessageFetchStreamBatchYield::MessageEnd) => {
879 let (uid, sink) = current.take().expect("message end within a message");
880 done(uid, sink)?;
881 arg = None;
882 }
883 }
884 }
885 }
886
887 pub fn search(
889 &mut self,
890 criteria: Vec1<SearchKey<'static>>,
891 opts: ImapMessageSearchOptions,
892 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
893 self.run(ImapMessageSearch::new(criteria, opts))
894 }
895
896 pub fn store(
898 &mut self,
899 sequence_set: SequenceSet,
900 kind: StoreType,
901 flags: Vec<Flag<'static>>,
902 opts: ImapMessageStoreOptions,
903 ) -> Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapClientStdError> {
904 self.run(ImapMessageStore::new(sequence_set, kind, flags, opts))
905 }
906
907 pub fn copy(
910 &mut self,
911 sequence_set: SequenceSet,
912 mailbox: Mailbox<'static>,
913 opts: ImapMessageCopyOptions,
914 ) -> Result<ImapCopyUid, ImapClientStdError> {
915 self.run(ImapMessageCopy::new(sequence_set, mailbox, opts))
916 }
917
918 pub fn r#move(
921 &mut self,
922 sequence_set: SequenceSet,
923 mailbox: Mailbox<'static>,
924 opts: ImapMessageMoveOptions,
925 ) -> Result<ImapCopyUid, ImapClientStdError> {
926 self.run(ImapMessageMove::new(sequence_set, mailbox, opts))
927 }
928
929 pub fn append(
934 &mut self,
935 mailbox: Mailbox<'static>,
936 message: &[u8],
937 opts: ImapMessageAppendOptions,
938 ) -> Result<ImapMessageAppendOutput, ImapClientStdError> {
939 self.run(ImapMessageAppend::new(mailbox, message.to_vec(), opts))
940 }
941
942 pub fn append_stream(
950 &mut self,
951 mailbox: Mailbox<'static>,
952 mut source: impl Read,
953 len: usize,
954 opts: ImapMessageAppendOptions,
955 ) -> Result<ImapMessageAppendOutput, ImapClientStdError> {
956 let mut coroutine = ImapMessageAppendStream::new(mailbox, len as u32, opts);
957 let mut buf = [0u8; READ_BUFFER_SIZE];
958 let mut arg: Option<&[u8]> = None;
959
960 loop {
961 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
962 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
963 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
964 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsRead) => {
965 let n = self.stream.read(&mut buf)?;
966 arg = Some(&buf[..n]);
967 }
968 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsWrite(bytes)) => {
969 self.stream.write_all(&bytes)?;
970 arg = None;
971 }
972 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsStream) => {
973 let len = len as u64;
974 let mut sink = source.by_ref().take(len);
975 let n = io::copy(&mut sink, &mut self.stream)?;
976 arg = (n != len).then_some(&[]);
979 }
980 }
981 }
982 }
983
984 pub fn sort(
991 &mut self,
992 sort_criteria: Vec1<SortCriterion>,
993 search_criteria: Vec1<SearchKey<'static>>,
994 opts: ImapMessageSortOptions,
995 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
996 self.run(ImapMessageSort::new(sort_criteria, search_criteria, opts))
997 }
998
999 pub fn thread(
1001 &mut self,
1002 algorithm: ThreadingAlgorithm<'static>,
1003 search_criteria: Vec1<SearchKey<'static>>,
1004 opts: ImapMessageThreadOptions,
1005 ) -> Result<Vec<Thread>, ImapClientStdError> {
1006 self.run(ImapMessageThread::new(algorithm, search_criteria, opts))
1007 }
1008}
1009
1010impl fmt::Debug for ImapClientStd {
1011 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 f.debug_struct("ImapClientStd")
1013 .field("fragmentizer", &self.fragmentizer)
1014 .finish_non_exhaustive()
1015 }
1016}
1017
1018pub struct ImapMailboxWatchStream {
1020 rx: Receiver<Result<ImapMailboxWatchEvent, ImapClientStdError>>,
1021 handle: Option<JoinHandle<()>>,
1022 shutdown: Arc<AtomicBool>,
1023}
1024
1025impl ImapMailboxWatchStream {
1026 pub fn try_recv(
1028 &self,
1029 ) -> Result<Result<ImapMailboxWatchEvent, ImapClientStdError>, TryRecvError> {
1030 self.rx.try_recv()
1031 }
1032
1033 pub fn recv_timeout(
1035 &self,
1036 timeout: Duration,
1037 ) -> Result<Result<ImapMailboxWatchEvent, ImapClientStdError>, RecvTimeoutError> {
1038 self.rx.recv_timeout(timeout)
1039 }
1040
1041 pub fn close(mut self) -> Result<(), ImapClientStdError> {
1043 self.shutdown.store(true, Ordering::SeqCst);
1044 if let Some(handle) = self.handle.take() {
1045 handle
1046 .join()
1047 .map_err(|_| io::Error::other("IMAP watch worker panicked"))?;
1048 }
1049 Ok(())
1050 }
1051}
1052
1053impl Iterator for ImapMailboxWatchStream {
1054 type Item = Result<ImapMailboxWatchEvent, ImapClientStdError>;
1055
1056 fn next(&mut self) -> Option<Self::Item> {
1057 self.rx.recv().ok()
1058 }
1059}
1060
1061impl Drop for ImapMailboxWatchStream {
1062 fn drop(&mut self) {
1063 self.shutdown.store(true, Ordering::SeqCst);
1064
1065 if let Some(handle) = self.handle.take() {
1066 handle.join().ok();
1067 }
1068 }
1069}
1070
1071#[cfg(any(
1072 feature = "rustls-aws",
1073 feature = "rustls-ring",
1074 feature = "native-tls"
1075))]
1076impl ImapClientStd {
1077 pub fn connect(
1084 url: &Url,
1085 tls: &Tls,
1086 starttls: bool,
1087 sasl: Option<impl Into<Sasl>>,
1088 auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
1089 ) -> Result<(Self, Vec<Capability<'static>>), ImapClientStdError> {
1090 let (stream, is_tls) = match url.scheme() {
1091 scheme if scheme.eq_ignore_ascii_case("imap") => {
1092 let host = tcp_host(url)?;
1093 (
1094 StreamStd::connect_tcp(host, url.port().unwrap_or(default_port(scheme)))?,
1095 false,
1096 )
1097 }
1098 scheme if scheme.eq_ignore_ascii_case("imaps") => {
1099 let host = tcp_host(url)?;
1100 (
1101 StreamStd::connect_tls(host, url.port().unwrap_or(default_port(scheme)), tls)?,
1102 true,
1103 )
1104 }
1105 scheme if scheme.eq_ignore_ascii_case("unix") => {
1110 (StreamStd::connect_unix(url.path())?, false)
1111 }
1112 scheme => {
1113 let url = url.to_string();
1114 let scheme = scheme.to_string();
1115 return Err(ImapClientStdError::UrlUnsupportedScheme(url, scheme));
1116 }
1117 };
1118
1119 if starttls && is_tls {
1120 return Err(ImapClientStdError::StartTlsOverTls);
1121 }
1122
1123 let stream = if starttls {
1126 let mut stream = stream;
1127 let mut fragmentizer = Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE);
1128 run_starttls(&mut stream, &mut fragmentizer)?;
1129 stream.upgrade_tls(tls)?
1130 } else {
1131 stream
1132 };
1133
1134 let mut client = Self::new(stream);
1135 client.auto_id = auto_id;
1136
1137 let (mut capability, pre_authenticated) = if starttls {
1138 (client.capability()?, false)
1139 } else {
1140 let greeting = client.run(ImapGreetingGet::new(ImapGreetingGetOptions {
1141 ensure_capabilities: true,
1142 }))?;
1143 (greeting.capability, greeting.pre_authenticated)
1144 };
1145 client.pre_authenticated = pre_authenticated;
1146
1147 if let Some(sasl) = sasl.map(Into::into).filter(|_| !pre_authenticated) {
1151 let ir = capability.contains(&Capability::SaslIr);
1152
1153 capability = match sasl {
1154 Sasl::Anonymous(SaslAnonymous { message }) => {
1155 let opts = ImapAuthAnonymousOptions {
1156 initial_request: ir,
1157 ensure_capabilities: true,
1158 auto_id: client.auto_id.take(),
1159 };
1160
1161 client.auth_anonymous(message, opts)?
1162 }
1163 Sasl::Login(SaslLogin { username, password }) => {
1164 let opts = ImapLoginOptions {
1165 ensure_capabilities: true,
1166 auto_id: client.auto_id.take(),
1167 };
1168
1169 client.login(username, password.expose_secret(), opts)?
1170 }
1171 Sasl::Plain(SaslPlain {
1172 authzid,
1173 authcid,
1174 passwd,
1175 }) => {
1176 let opts = ImapAuthPlainOptions {
1177 initial_request: ir,
1178 ensure_capabilities: true,
1179 auto_id: client.auto_id.take(),
1180 };
1181
1182 client.auth_plain(authzid, authcid, passwd.expose_secret(), opts)?
1183 }
1184 Sasl::Oauthbearer(SaslOauthbearer {
1185 username,
1186 host,
1187 port,
1188 token,
1189 }) => {
1190 let opts = ImapAuthOauthbearerOptions {
1191 initial_request: ir,
1192 ensure_capabilities: true,
1193 auto_id: client.auto_id.take(),
1194 };
1195
1196 client.auth_oauthbearer(username, host, port, token.expose_secret(), opts)?
1197 }
1198 Sasl::Xoauth2(SaslXoauth2 { username, token }) => {
1199 let opts = ImapAuthXoauth2Options {
1200 initial_request: ir,
1201 ensure_capabilities: true,
1202 auto_id: client.auto_id.take(),
1203 };
1204
1205 client.auth_xoauth2(username, token.expose_secret(), opts)?
1206 }
1207 #[cfg(feature = "scram")]
1208 Sasl::ScramSha256(SaslScramSha256 { username, password }) => {
1209 let opts = ImapAuthScramSha256Options {
1210 initial_request: ir,
1211 ensure_capabilities: true,
1212 auto_id: client.auto_id.take(),
1213 };
1214
1215 client.auth_scram_sha256(username, password.expose_secret(), opts)?
1216 }
1217 #[cfg(not(feature = "scram"))]
1218 Sasl::ScramSha256(_) => {
1219 return Err(ImapClientStdError::ScramSha256NotEnabled);
1220 }
1221 };
1222 }
1223
1224 Ok((client, capability))
1225 }
1226}
1227
1228fn tcp_host(url: &Url) -> Result<&str, ImapClientStdError> {
1231 url.host_str()
1232 .ok_or_else(|| ImapClientStdError::UrlMissingHost(url.to_string()))
1233}
1234
1235#[cfg(any(
1238 feature = "rustls-aws",
1239 feature = "rustls-ring",
1240 feature = "native-tls"
1241))]
1242fn run_starttls(
1243 stream: &mut StreamStd,
1244 fragmentizer: &mut Fragmentizer,
1245) -> Result<(), ImapClientStdError> {
1246 let mut coroutine = ImapStartTls::new();
1247 let mut buf = [0u8; READ_BUFFER_SIZE];
1248 let mut arg: Option<&[u8]> = None;
1249
1250 loop {
1251 match coroutine.resume(fragmentizer, arg.take()) {
1252 ImapCoroutineState::Complete(Ok(_)) => return Ok(()),
1253 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
1254 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
1255 let n = stream.read(&mut buf)?;
1256 arg = Some(&buf[..n]);
1257 }
1258 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
1259 stream.write_all(&bytes)?;
1260 }
1261 }
1262 }
1263}
1264
1265pub trait ImapStream: Read + Write + Send + Any {
1272 fn as_any_mut(&mut self) -> &mut dyn Any;
1274
1275 fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()>;
1279}
1280
1281impl ImapStream for StreamStd {
1282 fn as_any_mut(&mut self) -> &mut dyn Any {
1283 self
1284 }
1285
1286 fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
1287 StreamStd::set_read_timeout(self, timeout)
1288 }
1289}