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::*, greeting::*, list::*,
90 login::*, logout::*, lsub::*, noop::*, raw::*, rename::*, search::*, select::*,
91 starttls::*, status::*, store::*, subscribe::*, unsubscribe::*,
92 },
93 rfc3691::unselect::*,
94 rfc5161::enable::*,
95 rfc5256::{sort::*, thread::*},
96 rfc6851::r#move::*,
97 rfc7628::auth_oauthbearer::*,
98 sasl::{auth_anonymous::*, auth_login::*, auth_plain::*, auth_xoauth2::*},
99 watch::*,
100};
101
102#[derive(Debug, Error)]
104pub enum ImapClientStdError {
105 #[error(transparent)]
107 Greeting(#[from] ImapGreetingGetError),
108 #[error(transparent)]
110 Login(#[from] ImapLoginError),
111 #[error(transparent)]
113 AuthLogin(#[from] ImapAuthLoginError),
114 #[error(transparent)]
116 AuthPlain(#[from] ImapAuthPlainError),
117 #[error(transparent)]
119 AuthAnonymous(#[from] ImapAuthAnonymousError),
120 #[error(transparent)]
122 AuthOAuthBearer(#[from] ImapAuthOauthbearerError),
123 #[error(transparent)]
125 AuthXOAuth2(#[from] ImapAuthXoauth2Error),
126 #[cfg(feature = "scram")]
128 #[error(transparent)]
129 AuthScramSha256(#[from] ImapAuthScramSha256Error),
130 #[cfg(any(
132 feature = "rustls-aws",
133 feature = "rustls-ring",
134 feature = "native-tls"
135 ))]
136 #[cfg(not(feature = "scram"))]
137 #[error("SCRAM-SHA-256 SASL mechanism requires the `scram` cargo feature")]
138 ScramSha256NotEnabled,
139 #[error(transparent)]
141 Logout(#[from] ImapLogoutError),
142 #[error(transparent)]
144 Capability(#[from] ImapCapabilityGetError),
145 #[error(transparent)]
147 Noop(#[from] ImapNoopError),
148 #[error(transparent)]
150 Raw(#[from] ImapRawError),
151 #[error(transparent)]
153 ServerId(#[from] ImapServerIdError),
154 #[error(transparent)]
156 ExtensionEnable(#[from] ImapExtensionEnableError),
157 #[error(transparent)]
159 MailboxList(#[from] ImapMailboxListError),
160 #[error(transparent)]
162 MailboxLsub(#[from] ImapMailboxLsubError),
163 #[error(transparent)]
165 MailboxStatus(#[from] ImapMailboxStatusError),
166 #[error(transparent)]
168 MailboxCreate(#[from] ImapMailboxCreateError),
169 #[error(transparent)]
171 MailboxDelete(#[from] ImapMailboxDeleteError),
172 #[error(transparent)]
174 MailboxRename(#[from] ImapMailboxRenameError),
175 #[error(transparent)]
177 MailboxSubscribe(#[from] ImapMailboxSubscribeError),
178 #[error(transparent)]
180 MailboxUnsubscribe(#[from] ImapMailboxUnsubscribeError),
181 #[error(transparent)]
183 MailboxSelect(#[from] ImapMailboxSelectError),
184 #[error(transparent)]
186 MailboxExamine(#[from] ImapMailboxExamineError),
187 #[error(transparent)]
189 MailboxWatch(#[from] ImapMailboxWatchError),
190 #[error(transparent)]
192 MailboxClose(#[from] ImapMailboxCloseError),
193 #[error(transparent)]
195 MailboxUnselect(#[from] ImapMailboxUnselectError),
196 #[error(transparent)]
198 MailboxCheck(#[from] ImapMailboxCheckError),
199 #[error(transparent)]
201 MailboxExpunge(#[from] ImapMailboxExpungeError),
202 #[error(transparent)]
204 MessageSort(#[from] ImapMessageSortError),
205 #[error(transparent)]
207 MessageFetch(#[from] ImapMessageFetchError),
208 #[error(transparent)]
210 MessageFetchStream(#[from] ImapMessageFetchStreamError),
211 #[error(transparent)]
213 MessageSearch(#[from] ImapMessageSearchError),
214 #[error(transparent)]
216 MessageStore(#[from] ImapMessageStoreError),
217 #[error(transparent)]
219 MessageCopy(#[from] ImapMessageCopyError),
220 #[error(transparent)]
222 MessageMove(#[from] ImapMessageMoveError),
223 #[error(transparent)]
225 MessageAppend(#[from] ImapMessageAppendError),
226 #[error(transparent)]
228 MessageAppendStream(#[from] ImapMessageAppendStreamError),
229 #[error(transparent)]
231 MessageThread(#[from] ImapMessageThreadError),
232 #[error(transparent)]
234 Io(#[from] io::Error),
235 #[error(transparent)]
237 StartTls(#[from] ImapStartTlsError),
238 #[cfg(any(
240 feature = "rustls-aws",
241 feature = "rustls-ring",
242 feature = "native-tls"
243 ))]
244 #[error(transparent)]
245 Tls(#[from] anyhow::Error),
246 #[cfg(any(
248 feature = "rustls-aws",
249 feature = "rustls-ring",
250 feature = "native-tls"
251 ))]
252 #[error("IMAP URL `{0}` has no host")]
253 UrlMissingHost(String),
254 #[cfg(any(
256 feature = "rustls-aws",
257 feature = "rustls-ring",
258 feature = "native-tls"
259 ))]
260 #[error("IMAP URL `{0}` has unsupported scheme `{1}` (expected `imap` or `imaps`)")]
261 UrlUnsupportedScheme(String, String),
262 #[cfg(any(
264 feature = "rustls-aws",
265 feature = "rustls-ring",
266 feature = "native-tls"
267 ))]
268 #[error("STARTTLS requested on an `imaps://` URL: TLS is already active")]
269 StartTlsOverTls,
270 #[error("Invalid IMAP LOGIN credentials")]
272 InvalidLoginCredentials(#[from] imap_codec::imap_types::error::ValidationError),
273 #[error("IMAP server does not advertise QRESYNC capability")]
275 QresyncNotSupported,
276 #[error("Invalid mod-sequence value: 0")]
278 InvalidModSeq,
279}
280
281const READ_BUFFER_SIZE: usize = 16 * 1024;
282const FRAGMENTIZER_MAX_MESSAGE_SIZE: u32 = 100 * 1024 * 1024;
283
284pub fn default_alpn() -> Vec<String> {
286 vec![String::from("imap")]
287}
288
289pub struct ImapClientStd {
292 pub stream: Box<dyn ImapStream>,
294 pub fragmentizer: Fragmentizer,
297 pub auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
303}
304
305impl ImapClientStd {
306 pub fn new<S: Read + Write + Send + 'static>(stream: S) -> Self {
309 Self {
310 stream: Box::new(stream),
311 fragmentizer: Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE),
312 auto_id: None,
313 }
314 }
315
316 pub fn set_stream<S: Read + Write + Send + 'static>(&mut self, stream: S) {
318 self.stream = Box::new(stream);
319 }
320
321 pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, ImapClientStdError>
327 where
328 C: ImapCoroutine<Yield = ImapYield, Return = Result<T, E>>,
329 ImapClientStdError: From<E>,
330 {
331 let mut buf = [0u8; READ_BUFFER_SIZE];
332 let mut arg: Option<&[u8]> = None;
333
334 loop {
335 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
336 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
337 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
338 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
339 let n = self.stream.read(&mut buf)?;
340 arg = Some(&buf[..n]);
341 }
342 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
343 self.stream.write_all(&bytes)?;
344 arg = None;
345 }
346 }
347 }
348 }
349
350 pub fn greeting(&mut self) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
353 Ok(self
354 .run(ImapGreetingGet::new(ImapGreetingGetOptions {
355 ensure_capabilities: true,
356 }))?
357 .capability)
358 }
359
360 pub fn login(
362 &mut self,
363 user: impl AsRef<str>,
364 password: impl AsRef<str>,
365 opts: ImapLoginOptions,
366 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
367 self.run(ImapLogin::new(user, password, opts)?)
368 }
369
370 pub fn starttls(&mut self) -> Result<Vec<u8>, ImapClientStdError> {
377 self.run(ImapStartTls::new())
378 }
379
380 pub fn auth_anonymous(
382 &mut self,
383 message: Option<impl AsRef<str>>,
384 opts: ImapAuthAnonymousOptions,
385 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
386 self.run(ImapAuthAnonymous::new(message, opts))
387 }
388
389 pub fn auth_login(
392 &mut self,
393 user: impl AsRef<str>,
394 password: impl AsRef<str>,
395 opts: ImapAuthLoginOptions,
396 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
397 self.run(ImapAuthLogin::new(user, password, opts))
398 }
399
400 pub fn auth_plain(
402 &mut self,
403 authzid: Option<impl AsRef<str>>,
404 authcid: impl AsRef<str>,
405 password: impl AsRef<str>,
406 opts: ImapAuthPlainOptions,
407 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
408 self.run(ImapAuthPlain::new(authzid, authcid, password, opts))
409 }
410
411 pub fn auth_oauthbearer(
414 &mut self,
415 user: impl AsRef<str>,
416 host: impl AsRef<str>,
417 port: u16,
418 token: impl AsRef<str>,
419 opts: ImapAuthOauthbearerOptions,
420 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
421 self.run(ImapAuthOauthbearer::new(user, host, port, token, opts))
422 }
423
424 pub fn auth_xoauth2(
427 &mut self,
428 user: impl AsRef<str>,
429 token: impl AsRef<str>,
430 opts: ImapAuthXoauth2Options,
431 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
432 self.run(ImapAuthXoauth2::new(user, token, opts))
433 }
434
435 #[cfg(feature = "scram")]
437 pub fn auth_scram_sha256(
438 &mut self,
439 user: impl AsRef<str>,
440 password: impl AsRef<str>,
441 opts: ImapAuthScramSha256Options,
442 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
443 self.run(ImapAuthScramSha256::new(user, password, opts))
444 }
445
446 pub fn logout(&mut self) -> Result<(), ImapClientStdError> {
448 self.run(ImapLogout::new())
449 }
450
451 pub fn capability(&mut self) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
453 self.run(ImapCapabilityGet::new())
454 }
455
456 pub fn noop(&mut self) -> Result<(), ImapClientStdError> {
458 self.run(ImapNoop::new())
459 }
460
461 pub fn raw(&mut self, command: impl AsRef<str>) -> Result<String, ImapClientStdError> {
467 self.run(ImapRaw::new(command))
468 }
469
470 pub fn id(
472 &mut self,
473 opts: ImapServerIdOptions,
474 ) -> Result<Option<Vec<(IString<'static>, NString<'static>)>>, ImapClientStdError> {
475 self.run(ImapServerId::new(opts))
476 }
477
478 pub fn enable(
480 &mut self,
481 capabilities: Vec1<CapabilityEnable<'static>>,
482 ) -> Result<Option<Vec<CapabilityEnable<'static>>>, ImapClientStdError> {
483 self.run(ImapExtensionEnable::new(capabilities))
484 }
485
486 pub fn list(
488 &mut self,
489 reference: Mailbox<'static>,
490 pattern: ListMailbox<'static>,
491 ) -> Result<ImapMailboxListing, ImapClientStdError> {
492 self.run(ImapMailboxList::new(reference, pattern))
493 }
494
495 pub fn lsub(
498 &mut self,
499 reference: Mailbox<'static>,
500 pattern: ListMailbox<'static>,
501 ) -> Result<ImapMailboxListing, ImapClientStdError> {
502 self.run(ImapMailboxLsub::new(reference, pattern))
503 }
504
505 pub fn status(
507 &mut self,
508 mailbox: Mailbox<'static>,
509 item_names: impl Into<Cow<'static, [StatusDataItemName]>>,
510 ) -> Result<Vec<StatusDataItem>, ImapClientStdError> {
511 self.run(ImapMailboxStatus::new(mailbox, item_names))
512 }
513
514 pub fn create(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
516 self.run(ImapMailboxCreate::new(mailbox))
517 }
518
519 pub fn delete(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
521 self.run(ImapMailboxDelete::new(mailbox))
522 }
523
524 pub fn rename(
526 &mut self,
527 from: Mailbox<'static>,
528 to: Mailbox<'static>,
529 ) -> Result<(), ImapClientStdError> {
530 self.run(ImapMailboxRename::new(from, to))
531 }
532
533 pub fn subscribe(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
535 self.run(ImapMailboxSubscribe::new(mailbox))
536 }
537
538 pub fn unsubscribe(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
540 self.run(ImapMailboxUnsubscribe::new(mailbox))
541 }
542
543 pub fn select(
545 &mut self,
546 mailbox: Mailbox<'static>,
547 opts: ImapMailboxSelectOptions,
548 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
549 self.run(ImapMailboxSelect::new(mailbox, opts))
550 }
551
552 pub fn examine(
554 &mut self,
555 mailbox: Mailbox<'static>,
556 opts: ImapMailboxExamineOptions,
557 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
558 self.run(ImapMailboxExamine::new(mailbox, opts))
559 }
560
561 pub fn select_qresync(
566 &mut self,
567 mailbox: Mailbox<'static>,
568 uid_validity: NonZeroU32,
569 highest_mod_seq: u64,
570 capability: &[Capability<'static>],
571 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
572 if !capability.contains(&Capability::QResync) {
573 return Err(ImapClientStdError::QresyncNotSupported);
574 }
575
576 let Some(highest_mod_seq) = NonZeroU64::new(highest_mod_seq) else {
577 return Err(ImapClientStdError::InvalidModSeq);
578 };
579
580 let parameters = vec![SelectParameter::QResync {
581 uid_validity,
582 mod_sequence_value: highest_mod_seq,
583 known_uids: None,
584 seq_match_data: None,
585 }];
586
587 self.select(mailbox, ImapMailboxSelectOptions { parameters })
588 }
589
590 pub fn close(&mut self) -> Result<(), ImapClientStdError> {
592 self.run(ImapMailboxClose::new())
593 }
594
595 pub fn unselect(&mut self) -> Result<(), ImapClientStdError> {
597 self.run(ImapMailboxUnselect::new())
598 }
599
600 pub fn check(&mut self) -> Result<(), ImapClientStdError> {
602 self.run(ImapMailboxCheck::new())
603 }
604
605 pub fn expunge(&mut self) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
607 self.run(ImapMailboxExpunge::new())
608 }
609
610 pub fn watch_mailbox(
615 self,
616 mailbox: Mailbox<'static>,
617 capability: &[Capability<'static>],
618 ) -> Result<ImapMailboxWatchStream, ImapClientStdError> {
619 let shutdown = Arc::new(AtomicBool::new(false));
620 let mut watcher = ImapMailboxWatch::new(capability, mailbox, shutdown.clone())?;
621 let mut fragmentizer = self.fragmentizer;
622 let mut stream = self.stream;
623
624 let (tx, rx) = mpsc::sync_channel::<Result<ImapMailboxWatchEvent, ImapClientStdError>>(256);
625 let shutdown_handle = shutdown.clone();
626 let handle = thread::spawn(move || {
627 let mut buf = [0u8; READ_BUFFER_SIZE];
628 let mut arg: Option<Vec<u8>> = None;
629
630 loop {
631 match watcher.resume(&mut fragmentizer, arg.as_deref()) {
632 ImapCoroutineState::Yielded(ImapMailboxWatchYield::Event(e)) => {
633 arg = None;
634 if tx.send(Ok(e)).is_err() {
635 return;
636 }
637 }
638 ImapCoroutineState::Complete(Ok(())) => return,
639 ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead) => {
640 match stream.read(&mut buf) {
641 Ok(0) => {
642 let eof = io::ErrorKind::UnexpectedEof;
643 let err = "IMAP server closed the connection during watch";
644 tx.send(Err(io::Error::new(eof, err).into())).ok();
645 return;
646 }
647 Ok(n) => arg = Some(buf[..n].to_vec()),
648 Err(err) => {
649 tx.send(Err(err.into())).ok();
650 return;
651 }
652 }
653 }
654 ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(bytes)) => {
655 if let Err(err) = stream.write_all(&bytes) {
656 tx.send(Err(err.into())).ok();
657 return;
658 }
659 arg = None;
660 }
661 ImapCoroutineState::Complete(Err(err)) => {
662 tx.send(Err(err.into())).ok();
663 return;
664 }
665 }
666 }
667 });
668
669 Ok(ImapMailboxWatchStream {
670 rx,
671 handle: Some(handle),
672 shutdown: shutdown_handle,
673 })
674 }
675
676 pub fn fetch(
678 &mut self,
679 sequence_set: SequenceSet,
680 items: MacroOrMessageDataItemNames<'static>,
681 opts: ImapMessageFetchOptions,
682 ) -> Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapClientStdError> {
683 self.run(ImapMessageFetch::new(sequence_set, items, opts))
684 }
685
686 pub fn fetch_body_stream(
692 &mut self,
693 id: NonZeroU32,
694 uid: bool,
695 mut sink: impl Write,
696 ) -> Result<(), ImapClientStdError> {
697 let mut coroutine = ImapMessageFetchStream::new(id, uid);
698 let mut buf = [0u8; READ_BUFFER_SIZE];
699 let mut arg: Option<&[u8]> = None;
700
701 loop {
702 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
703 ImapCoroutineState::Complete(Ok(())) => return Ok(()),
704 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
705 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsRead) => {
706 let n = self.stream.read(&mut buf)?;
707 arg = Some(&buf[..n]);
708 }
709 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsWrite(bytes)) => {
710 self.stream.write_all(&bytes)?;
711 arg = None;
712 }
713 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::BodyChunk(bytes)) => {
714 sink.write_all(&bytes)?;
715 arg = None;
716 }
717 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsStream { len }) => {
718 let len = len as u64;
719 let mut stream = (&mut self.stream).take(len);
720 let n = io::copy(&mut stream, &mut sink)?;
721 arg = (n != len).then_some(&[]);
724 }
725 }
726 }
727 }
728
729 pub fn search(
731 &mut self,
732 criteria: Vec1<SearchKey<'static>>,
733 opts: ImapMessageSearchOptions,
734 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
735 self.run(ImapMessageSearch::new(criteria, opts))
736 }
737
738 pub fn store(
740 &mut self,
741 sequence_set: SequenceSet,
742 kind: StoreType,
743 flags: Vec<Flag<'static>>,
744 opts: ImapMessageStoreOptions,
745 ) -> Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapClientStdError> {
746 self.run(ImapMessageStore::new(sequence_set, kind, flags, opts))
747 }
748
749 pub fn copy(
752 &mut self,
753 sequence_set: SequenceSet,
754 mailbox: Mailbox<'static>,
755 opts: ImapMessageCopyOptions,
756 ) -> Result<ImapCopyUid, ImapClientStdError> {
757 self.run(ImapMessageCopy::new(sequence_set, mailbox, opts))
758 }
759
760 pub fn r#move(
763 &mut self,
764 sequence_set: SequenceSet,
765 mailbox: Mailbox<'static>,
766 opts: ImapMessageMoveOptions,
767 ) -> Result<ImapCopyUid, ImapClientStdError> {
768 self.run(ImapMessageMove::new(sequence_set, mailbox, opts))
769 }
770
771 pub fn append(
776 &mut self,
777 mailbox: Mailbox<'static>,
778 message: &[u8],
779 opts: ImapMessageAppendOptions,
780 ) -> Result<ImapMessageAppendOutput, ImapClientStdError> {
781 self.run(ImapMessageAppend::new(mailbox, message.to_vec(), opts))
782 }
783
784 pub fn append_stream(
792 &mut self,
793 mailbox: Mailbox<'static>,
794 mut source: impl Read,
795 len: usize,
796 opts: ImapMessageAppendOptions,
797 ) -> Result<ImapMessageAppendOutput, ImapClientStdError> {
798 let mut coroutine = ImapMessageAppendStream::new(mailbox, len as u32, opts);
799 let mut buf = [0u8; READ_BUFFER_SIZE];
800 let mut arg: Option<&[u8]> = None;
801
802 loop {
803 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
804 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
805 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
806 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsRead) => {
807 let n = self.stream.read(&mut buf)?;
808 arg = Some(&buf[..n]);
809 }
810 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsWrite(bytes)) => {
811 self.stream.write_all(&bytes)?;
812 arg = None;
813 }
814 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsStream) => {
815 let len = len as u64;
816 let mut sink = source.by_ref().take(len);
817 let n = io::copy(&mut sink, &mut self.stream)?;
818 arg = (n != len).then_some(&[]);
821 }
822 }
823 }
824 }
825
826 pub fn sort(
833 &mut self,
834 sort_criteria: Vec1<SortCriterion>,
835 search_criteria: Vec1<SearchKey<'static>>,
836 opts: ImapMessageSortOptions,
837 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
838 self.run(ImapMessageSort::new(sort_criteria, search_criteria, opts))
839 }
840
841 pub fn thread(
843 &mut self,
844 algorithm: ThreadingAlgorithm<'static>,
845 search_criteria: Vec1<SearchKey<'static>>,
846 opts: ImapMessageThreadOptions,
847 ) -> Result<Vec<Thread>, ImapClientStdError> {
848 self.run(ImapMessageThread::new(algorithm, search_criteria, opts))
849 }
850}
851
852impl fmt::Debug for ImapClientStd {
853 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
854 f.debug_struct("ImapClientStd")
855 .field("fragmentizer", &self.fragmentizer)
856 .finish_non_exhaustive()
857 }
858}
859
860pub struct ImapMailboxWatchStream {
862 rx: Receiver<Result<ImapMailboxWatchEvent, ImapClientStdError>>,
863 handle: Option<JoinHandle<()>>,
864 shutdown: Arc<AtomicBool>,
865}
866
867impl ImapMailboxWatchStream {
868 pub fn try_recv(
870 &self,
871 ) -> Result<Result<ImapMailboxWatchEvent, ImapClientStdError>, TryRecvError> {
872 self.rx.try_recv()
873 }
874
875 pub fn recv_timeout(
877 &self,
878 timeout: Duration,
879 ) -> Result<Result<ImapMailboxWatchEvent, ImapClientStdError>, RecvTimeoutError> {
880 self.rx.recv_timeout(timeout)
881 }
882
883 pub fn close(mut self) -> Result<(), ImapClientStdError> {
885 self.shutdown.store(true, Ordering::SeqCst);
886 if let Some(handle) = self.handle.take() {
887 handle
888 .join()
889 .map_err(|_| io::Error::other("IMAP watch worker panicked"))?;
890 }
891 Ok(())
892 }
893}
894
895impl Iterator for ImapMailboxWatchStream {
896 type Item = Result<ImapMailboxWatchEvent, ImapClientStdError>;
897
898 fn next(&mut self) -> Option<Self::Item> {
899 self.rx.recv().ok()
900 }
901}
902
903impl Drop for ImapMailboxWatchStream {
904 fn drop(&mut self) {
905 self.shutdown.store(true, Ordering::SeqCst);
906
907 if let Some(handle) = self.handle.take() {
908 handle.join().ok();
909 }
910 }
911}
912
913#[cfg(any(
914 feature = "rustls-aws",
915 feature = "rustls-ring",
916 feature = "native-tls"
917))]
918impl ImapClientStd {
919 pub fn connect(
926 url: &Url,
927 tls: &Tls,
928 starttls: bool,
929 sasl: Option<impl Into<Sasl>>,
930 auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
931 ) -> Result<(Self, Vec<Capability<'static>>), ImapClientStdError> {
932 let Some(host) = url.host_str() else {
933 return Err(ImapClientStdError::UrlMissingHost(url.to_string()));
934 };
935
936 let (stream, is_tls) = match url.scheme() {
937 scheme if scheme.eq_ignore_ascii_case("imap") => (
938 StreamStd::connect_tcp(host, url.port().unwrap_or(143))?,
939 false,
940 ),
941 scheme if scheme.eq_ignore_ascii_case("imaps") => (
942 StreamStd::connect_tls(host, url.port().unwrap_or(993), tls)?,
943 true,
944 ),
945 scheme => {
946 let url = url.to_string();
947 let scheme = scheme.to_string();
948 return Err(ImapClientStdError::UrlUnsupportedScheme(url, scheme));
949 }
950 };
951
952 if starttls && is_tls {
953 return Err(ImapClientStdError::StartTlsOverTls);
954 }
955
956 let stream = if starttls {
959 let mut stream = stream;
960 let mut fragmentizer = Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE);
961 run_starttls(&mut stream, &mut fragmentizer)?;
962 stream.upgrade_tls(tls)?
963 } else {
964 stream
965 };
966
967 stream.set_read_timeout(Some(Duration::from_secs(5)))?;
970
971 let mut client = Self::new(stream);
972 client.auto_id = auto_id;
973
974 let mut capability = if starttls {
975 client.capability()?
976 } else {
977 client.greeting()?
978 };
979
980 if let Some(sasl) = sasl.map(Into::into) {
981 let ir = capability.contains(&Capability::SaslIr);
982
983 capability = match sasl {
984 Sasl::Anonymous(SaslAnonymous { message }) => {
985 let opts = ImapAuthAnonymousOptions {
986 initial_request: ir,
987 ensure_capabilities: true,
988 auto_id: client.auto_id.take(),
989 };
990
991 client.auth_anonymous(message, opts)?
992 }
993 Sasl::Login(SaslLogin { username, password }) => {
994 let opts = ImapLoginOptions {
995 ensure_capabilities: true,
996 auto_id: client.auto_id.take(),
997 };
998
999 client.login(username, password.expose_secret(), opts)?
1000 }
1001 Sasl::Plain(SaslPlain {
1002 authzid,
1003 authcid,
1004 passwd,
1005 }) => {
1006 let opts = ImapAuthPlainOptions {
1007 initial_request: ir,
1008 ensure_capabilities: true,
1009 auto_id: client.auto_id.take(),
1010 };
1011
1012 client.auth_plain(authzid, authcid, passwd.expose_secret(), opts)?
1013 }
1014 Sasl::Oauthbearer(SaslOauthbearer {
1015 username,
1016 host,
1017 port,
1018 token,
1019 }) => {
1020 let opts = ImapAuthOauthbearerOptions {
1021 initial_request: ir,
1022 ensure_capabilities: true,
1023 auto_id: client.auto_id.take(),
1024 };
1025
1026 client.auth_oauthbearer(username, host, port, token.expose_secret(), opts)?
1027 }
1028 Sasl::Xoauth2(SaslXoauth2 { username, token }) => {
1029 let opts = ImapAuthXoauth2Options {
1030 initial_request: ir,
1031 ensure_capabilities: true,
1032 auto_id: client.auto_id.take(),
1033 };
1034
1035 client.auth_xoauth2(username, token.expose_secret(), opts)?
1036 }
1037 #[cfg(feature = "scram")]
1038 Sasl::ScramSha256(SaslScramSha256 { username, password }) => {
1039 let opts = ImapAuthScramSha256Options {
1040 initial_request: ir,
1041 ensure_capabilities: true,
1042 auto_id: client.auto_id.take(),
1043 };
1044
1045 client.auth_scram_sha256(username, password.expose_secret(), opts)?
1046 }
1047 #[cfg(not(feature = "scram"))]
1048 Sasl::ScramSha256(_) => {
1049 return Err(ImapClientStdError::ScramSha256NotEnabled);
1050 }
1051 };
1052 }
1053
1054 Ok((client, capability))
1055 }
1056}
1057
1058#[cfg(any(
1061 feature = "rustls-aws",
1062 feature = "rustls-ring",
1063 feature = "native-tls"
1064))]
1065fn run_starttls(
1066 stream: &mut StreamStd,
1067 fragmentizer: &mut Fragmentizer,
1068) -> Result<(), ImapClientStdError> {
1069 let mut coroutine = ImapStartTls::new();
1070 let mut buf = [0u8; READ_BUFFER_SIZE];
1071 let mut arg: Option<&[u8]> = None;
1072
1073 loop {
1074 match coroutine.resume(fragmentizer, arg.take()) {
1075 ImapCoroutineState::Complete(Ok(_)) => return Ok(()),
1076 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
1077 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
1078 let n = stream.read(&mut buf)?;
1079 arg = Some(&buf[..n]);
1080 }
1081 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
1082 stream.write_all(&bytes)?;
1083 }
1084 }
1085 }
1086}
1087
1088pub trait ImapStream: Read + Write + Send + Any {
1094 fn as_any_mut(&mut self) -> &mut dyn Any;
1096}
1097
1098impl<T: Read + Write + Send + Any> ImapStream for T {
1099 fn as_any_mut(&mut self) -> &mut dyn Any {
1100 self
1101 }
1102}