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 fn default_port(scheme: &str) -> u16 {
291 if scheme.eq_ignore_ascii_case("imaps") {
292 993
293 } else {
294 143
295 }
296}
297
298pub struct ImapClientStd {
301 pub stream: Box<dyn ImapStream>,
303 pub fragmentizer: Fragmentizer,
306 pub auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
312 pub pre_authenticated: bool,
317}
318
319impl ImapClientStd {
320 pub fn new<S: Read + Write + Send + 'static>(stream: S) -> Self {
323 Self {
324 stream: Box::new(stream),
325 fragmentizer: Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE),
326 auto_id: None,
327 pre_authenticated: false,
328 }
329 }
330
331 pub fn set_stream<S: Read + Write + Send + 'static>(&mut self, stream: S) {
333 self.stream = Box::new(stream);
334 }
335
336 pub fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, ImapClientStdError>
342 where
343 C: ImapCoroutine<Yield = ImapYield, Return = Result<T, E>>,
344 ImapClientStdError: From<E>,
345 {
346 let mut buf = [0u8; READ_BUFFER_SIZE];
347 let mut arg: Option<&[u8]> = None;
348
349 loop {
350 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
351 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
352 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
353 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
354 let n = self.stream.read(&mut buf)?;
355 if n == 0 {
358 let kind = io::ErrorKind::UnexpectedEof;
359 let err = io::Error::new(kind, "IMAP server closed the connection");
360 return Err(err.into());
361 }
362 arg = Some(&buf[..n]);
363 }
364 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
365 self.stream.write_all(&bytes)?;
366 arg = None;
367 }
368 }
369 }
370 }
371
372 pub fn greeting(&mut self) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
375 Ok(self
376 .run(ImapGreetingGet::new(ImapGreetingGetOptions {
377 ensure_capabilities: true,
378 }))?
379 .capability)
380 }
381
382 pub fn login(
384 &mut self,
385 user: impl AsRef<str>,
386 password: impl AsRef<str>,
387 opts: ImapLoginOptions,
388 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
389 self.run(ImapLogin::new(user, password, opts)?)
390 }
391
392 pub fn starttls(&mut self) -> Result<Vec<u8>, ImapClientStdError> {
399 self.run(ImapStartTls::new())
400 }
401
402 pub fn auth_anonymous(
404 &mut self,
405 message: Option<impl AsRef<str>>,
406 opts: ImapAuthAnonymousOptions,
407 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
408 self.run(ImapAuthAnonymous::new(message, opts))
409 }
410
411 pub fn auth_login(
414 &mut self,
415 user: impl AsRef<str>,
416 password: impl AsRef<str>,
417 opts: ImapAuthLoginOptions,
418 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
419 self.run(ImapAuthLogin::new(user, password, opts))
420 }
421
422 pub fn auth_plain(
424 &mut self,
425 authzid: Option<impl AsRef<str>>,
426 authcid: impl AsRef<str>,
427 password: impl AsRef<str>,
428 opts: ImapAuthPlainOptions,
429 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
430 self.run(ImapAuthPlain::new(authzid, authcid, password, opts))
431 }
432
433 pub fn auth_oauthbearer(
436 &mut self,
437 user: impl AsRef<str>,
438 host: impl AsRef<str>,
439 port: u16,
440 token: impl AsRef<str>,
441 opts: ImapAuthOauthbearerOptions,
442 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
443 self.run(ImapAuthOauthbearer::new(user, host, port, token, opts))
444 }
445
446 pub fn auth_xoauth2(
449 &mut self,
450 user: impl AsRef<str>,
451 token: impl AsRef<str>,
452 opts: ImapAuthXoauth2Options,
453 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
454 self.run(ImapAuthXoauth2::new(user, token, opts))
455 }
456
457 #[cfg(feature = "scram")]
459 pub fn auth_scram_sha256(
460 &mut self,
461 user: impl AsRef<str>,
462 password: impl AsRef<str>,
463 opts: ImapAuthScramSha256Options,
464 ) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
465 self.run(ImapAuthScramSha256::new(user, password, opts))
466 }
467
468 pub fn logout(&mut self) -> Result<(), ImapClientStdError> {
470 self.run(ImapLogout::new())
471 }
472
473 pub fn capability(&mut self) -> Result<Vec<Capability<'static>>, ImapClientStdError> {
475 self.run(ImapCapabilityGet::new())
476 }
477
478 pub fn noop(&mut self) -> Result<(), ImapClientStdError> {
480 self.run(ImapNoop::new())
481 }
482
483 pub fn raw(&mut self, command: impl AsRef<str>) -> Result<String, ImapClientStdError> {
489 self.run(ImapRaw::new(command))
490 }
491
492 pub fn id(
494 &mut self,
495 opts: ImapServerIdOptions,
496 ) -> Result<Option<Vec<(IString<'static>, NString<'static>)>>, ImapClientStdError> {
497 self.run(ImapServerId::new(opts))
498 }
499
500 pub fn enable(
502 &mut self,
503 capabilities: Vec1<CapabilityEnable<'static>>,
504 ) -> Result<Option<Vec<CapabilityEnable<'static>>>, ImapClientStdError> {
505 self.run(ImapExtensionEnable::new(capabilities))
506 }
507
508 pub fn list(
510 &mut self,
511 reference: Mailbox<'static>,
512 pattern: ListMailbox<'static>,
513 ) -> Result<ImapMailboxListing, ImapClientStdError> {
514 self.run(ImapMailboxList::new(reference, pattern))
515 }
516
517 pub fn lsub(
520 &mut self,
521 reference: Mailbox<'static>,
522 pattern: ListMailbox<'static>,
523 ) -> Result<ImapMailboxListing, ImapClientStdError> {
524 self.run(ImapMailboxLsub::new(reference, pattern))
525 }
526
527 pub fn status(
529 &mut self,
530 mailbox: Mailbox<'static>,
531 item_names: impl Into<Cow<'static, [StatusDataItemName]>>,
532 ) -> Result<Vec<StatusDataItem>, ImapClientStdError> {
533 self.run(ImapMailboxStatus::new(mailbox, item_names))
534 }
535
536 pub fn create(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
538 self.run(ImapMailboxCreate::new(mailbox))
539 }
540
541 pub fn delete(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
543 self.run(ImapMailboxDelete::new(mailbox))
544 }
545
546 pub fn rename(
548 &mut self,
549 from: Mailbox<'static>,
550 to: Mailbox<'static>,
551 ) -> Result<(), ImapClientStdError> {
552 self.run(ImapMailboxRename::new(from, to))
553 }
554
555 pub fn subscribe(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
557 self.run(ImapMailboxSubscribe::new(mailbox))
558 }
559
560 pub fn unsubscribe(&mut self, mailbox: Mailbox<'static>) -> Result<(), ImapClientStdError> {
562 self.run(ImapMailboxUnsubscribe::new(mailbox))
563 }
564
565 pub fn select(
567 &mut self,
568 mailbox: Mailbox<'static>,
569 opts: ImapMailboxSelectOptions,
570 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
571 self.run(ImapMailboxSelect::new(mailbox, opts))
572 }
573
574 pub fn examine(
576 &mut self,
577 mailbox: Mailbox<'static>,
578 opts: ImapMailboxExamineOptions,
579 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
580 self.run(ImapMailboxExamine::new(mailbox, opts))
581 }
582
583 pub fn select_qresync(
588 &mut self,
589 mailbox: Mailbox<'static>,
590 uid_validity: NonZeroU32,
591 highest_mod_seq: u64,
592 capability: &[Capability<'static>],
593 ) -> Result<ImapMailboxSelectData, ImapClientStdError> {
594 if !capability.contains(&Capability::QResync) {
595 return Err(ImapClientStdError::QresyncNotSupported);
596 }
597
598 let Some(highest_mod_seq) = NonZeroU64::new(highest_mod_seq) else {
599 return Err(ImapClientStdError::InvalidModSeq);
600 };
601
602 let parameters = vec![SelectParameter::QResync {
603 uid_validity,
604 mod_sequence_value: highest_mod_seq,
605 known_uids: None,
606 seq_match_data: None,
607 }];
608
609 self.select(mailbox, ImapMailboxSelectOptions { parameters })
610 }
611
612 pub fn close(&mut self) -> Result<(), ImapClientStdError> {
614 self.run(ImapMailboxClose::new())
615 }
616
617 pub fn unselect(&mut self) -> Result<(), ImapClientStdError> {
619 self.run(ImapMailboxUnselect::new())
620 }
621
622 pub fn check(&mut self) -> Result<(), ImapClientStdError> {
624 self.run(ImapMailboxCheck::new())
625 }
626
627 pub fn expunge(&mut self) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
629 self.run(ImapMailboxExpunge::new())
630 }
631
632 pub fn watch_mailbox(
637 self,
638 mailbox: Mailbox<'static>,
639 capability: &[Capability<'static>],
640 ) -> Result<ImapMailboxWatchStream, ImapClientStdError> {
641 let shutdown = Arc::new(AtomicBool::new(false));
642 let mut watcher = ImapMailboxWatch::new(capability, mailbox, shutdown.clone())?;
643 let mut fragmentizer = self.fragmentizer;
644 let mut stream = self.stream;
645
646 let (tx, rx) = mpsc::sync_channel::<Result<ImapMailboxWatchEvent, ImapClientStdError>>(256);
647 let shutdown_handle = shutdown.clone();
648 let handle = thread::spawn(move || {
649 let mut buf = [0u8; READ_BUFFER_SIZE];
650 let mut arg: Option<Vec<u8>> = None;
651
652 loop {
653 match watcher.resume(&mut fragmentizer, arg.as_deref()) {
654 ImapCoroutineState::Yielded(ImapMailboxWatchYield::Event(e)) => {
655 arg = None;
656 if tx.send(Ok(e)).is_err() {
657 return;
658 }
659 }
660 ImapCoroutineState::Complete(Ok(())) => return,
661 ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsRead) => {
662 match stream.read(&mut buf) {
663 Ok(0) => {
664 let eof = io::ErrorKind::UnexpectedEof;
665 let err = "IMAP server closed the connection during watch";
666 tx.send(Err(io::Error::new(eof, err).into())).ok();
667 return;
668 }
669 Ok(n) => arg = Some(buf[..n].to_vec()),
670 Err(err) => {
671 tx.send(Err(err.into())).ok();
672 return;
673 }
674 }
675 }
676 ImapCoroutineState::Yielded(ImapMailboxWatchYield::WantsWrite(bytes)) => {
677 if let Err(err) = stream.write_all(&bytes) {
678 tx.send(Err(err.into())).ok();
679 return;
680 }
681 arg = None;
682 }
683 ImapCoroutineState::Complete(Err(err)) => {
684 tx.send(Err(err.into())).ok();
685 return;
686 }
687 }
688 }
689 });
690
691 Ok(ImapMailboxWatchStream {
692 rx,
693 handle: Some(handle),
694 shutdown: shutdown_handle,
695 })
696 }
697
698 pub fn fetch(
700 &mut self,
701 sequence_set: SequenceSet,
702 items: MacroOrMessageDataItemNames<'static>,
703 opts: ImapMessageFetchOptions,
704 ) -> Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapClientStdError> {
705 self.run(ImapMessageFetch::new(sequence_set, items, opts))
706 }
707
708 pub fn fetch_body_stream(
714 &mut self,
715 id: NonZeroU32,
716 uid: bool,
717 mut sink: impl Write,
718 ) -> Result<(), ImapClientStdError> {
719 let mut coroutine = ImapMessageFetchStream::new(id, uid);
720 let mut buf = [0u8; READ_BUFFER_SIZE];
721 let mut arg: Option<&[u8]> = None;
722
723 loop {
724 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
725 ImapCoroutineState::Complete(Ok(())) => return Ok(()),
726 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
727 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsRead) => {
728 let n = self.stream.read(&mut buf)?;
729 arg = Some(&buf[..n]);
730 }
731 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsWrite(bytes)) => {
732 self.stream.write_all(&bytes)?;
733 arg = None;
734 }
735 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::BodyChunk(bytes)) => {
736 sink.write_all(&bytes)?;
737 arg = None;
738 }
739 ImapCoroutineState::Yielded(ImapMessageFetchStreamYield::WantsStream { len }) => {
740 let len = len as u64;
741 let mut stream = (&mut self.stream).take(len);
742 let n = io::copy(&mut stream, &mut sink)?;
743 arg = (n != len).then_some(&[]);
746 }
747 }
748 }
749 }
750
751 pub fn search(
753 &mut self,
754 criteria: Vec1<SearchKey<'static>>,
755 opts: ImapMessageSearchOptions,
756 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
757 self.run(ImapMessageSearch::new(criteria, opts))
758 }
759
760 pub fn store(
762 &mut self,
763 sequence_set: SequenceSet,
764 kind: StoreType,
765 flags: Vec<Flag<'static>>,
766 opts: ImapMessageStoreOptions,
767 ) -> Result<BTreeMap<NonZeroU32, Vec1<MessageDataItem<'static>>>, ImapClientStdError> {
768 self.run(ImapMessageStore::new(sequence_set, kind, flags, opts))
769 }
770
771 pub fn copy(
774 &mut self,
775 sequence_set: SequenceSet,
776 mailbox: Mailbox<'static>,
777 opts: ImapMessageCopyOptions,
778 ) -> Result<ImapCopyUid, ImapClientStdError> {
779 self.run(ImapMessageCopy::new(sequence_set, mailbox, opts))
780 }
781
782 pub fn r#move(
785 &mut self,
786 sequence_set: SequenceSet,
787 mailbox: Mailbox<'static>,
788 opts: ImapMessageMoveOptions,
789 ) -> Result<ImapCopyUid, ImapClientStdError> {
790 self.run(ImapMessageMove::new(sequence_set, mailbox, opts))
791 }
792
793 pub fn append(
798 &mut self,
799 mailbox: Mailbox<'static>,
800 message: &[u8],
801 opts: ImapMessageAppendOptions,
802 ) -> Result<ImapMessageAppendOutput, ImapClientStdError> {
803 self.run(ImapMessageAppend::new(mailbox, message.to_vec(), opts))
804 }
805
806 pub fn append_stream(
814 &mut self,
815 mailbox: Mailbox<'static>,
816 mut source: impl Read,
817 len: usize,
818 opts: ImapMessageAppendOptions,
819 ) -> Result<ImapMessageAppendOutput, ImapClientStdError> {
820 let mut coroutine = ImapMessageAppendStream::new(mailbox, len as u32, opts);
821 let mut buf = [0u8; READ_BUFFER_SIZE];
822 let mut arg: Option<&[u8]> = None;
823
824 loop {
825 match coroutine.resume(&mut self.fragmentizer, arg.take()) {
826 ImapCoroutineState::Complete(Ok(out)) => return Ok(out),
827 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
828 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsRead) => {
829 let n = self.stream.read(&mut buf)?;
830 arg = Some(&buf[..n]);
831 }
832 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsWrite(bytes)) => {
833 self.stream.write_all(&bytes)?;
834 arg = None;
835 }
836 ImapCoroutineState::Yielded(ImapMessageAppendStreamYield::WantsStream) => {
837 let len = len as u64;
838 let mut sink = source.by_ref().take(len);
839 let n = io::copy(&mut sink, &mut self.stream)?;
840 arg = (n != len).then_some(&[]);
843 }
844 }
845 }
846 }
847
848 pub fn sort(
855 &mut self,
856 sort_criteria: Vec1<SortCriterion>,
857 search_criteria: Vec1<SearchKey<'static>>,
858 opts: ImapMessageSortOptions,
859 ) -> Result<Vec<NonZeroU32>, ImapClientStdError> {
860 self.run(ImapMessageSort::new(sort_criteria, search_criteria, opts))
861 }
862
863 pub fn thread(
865 &mut self,
866 algorithm: ThreadingAlgorithm<'static>,
867 search_criteria: Vec1<SearchKey<'static>>,
868 opts: ImapMessageThreadOptions,
869 ) -> Result<Vec<Thread>, ImapClientStdError> {
870 self.run(ImapMessageThread::new(algorithm, search_criteria, opts))
871 }
872}
873
874impl fmt::Debug for ImapClientStd {
875 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
876 f.debug_struct("ImapClientStd")
877 .field("fragmentizer", &self.fragmentizer)
878 .finish_non_exhaustive()
879 }
880}
881
882pub struct ImapMailboxWatchStream {
884 rx: Receiver<Result<ImapMailboxWatchEvent, ImapClientStdError>>,
885 handle: Option<JoinHandle<()>>,
886 shutdown: Arc<AtomicBool>,
887}
888
889impl ImapMailboxWatchStream {
890 pub fn try_recv(
892 &self,
893 ) -> Result<Result<ImapMailboxWatchEvent, ImapClientStdError>, TryRecvError> {
894 self.rx.try_recv()
895 }
896
897 pub fn recv_timeout(
899 &self,
900 timeout: Duration,
901 ) -> Result<Result<ImapMailboxWatchEvent, ImapClientStdError>, RecvTimeoutError> {
902 self.rx.recv_timeout(timeout)
903 }
904
905 pub fn close(mut self) -> Result<(), ImapClientStdError> {
907 self.shutdown.store(true, Ordering::SeqCst);
908 if let Some(handle) = self.handle.take() {
909 handle
910 .join()
911 .map_err(|_| io::Error::other("IMAP watch worker panicked"))?;
912 }
913 Ok(())
914 }
915}
916
917impl Iterator for ImapMailboxWatchStream {
918 type Item = Result<ImapMailboxWatchEvent, ImapClientStdError>;
919
920 fn next(&mut self) -> Option<Self::Item> {
921 self.rx.recv().ok()
922 }
923}
924
925impl Drop for ImapMailboxWatchStream {
926 fn drop(&mut self) {
927 self.shutdown.store(true, Ordering::SeqCst);
928
929 if let Some(handle) = self.handle.take() {
930 handle.join().ok();
931 }
932 }
933}
934
935#[cfg(any(
936 feature = "rustls-aws",
937 feature = "rustls-ring",
938 feature = "native-tls"
939))]
940impl ImapClientStd {
941 pub fn connect(
948 url: &Url,
949 tls: &Tls,
950 starttls: bool,
951 sasl: Option<impl Into<Sasl>>,
952 auto_id: Option<Vec<(IString<'static>, NString<'static>)>>,
953 ) -> Result<(Self, Vec<Capability<'static>>), ImapClientStdError> {
954 let (stream, is_tls) = match url.scheme() {
955 scheme if scheme.eq_ignore_ascii_case("imap") => {
956 let host = tcp_host(url)?;
957 (
958 StreamStd::connect_tcp(host, url.port().unwrap_or(default_port(scheme)))?,
959 false,
960 )
961 }
962 scheme if scheme.eq_ignore_ascii_case("imaps") => {
963 let host = tcp_host(url)?;
964 (
965 StreamStd::connect_tls(host, url.port().unwrap_or(default_port(scheme)), tls)?,
966 true,
967 )
968 }
969 scheme if scheme.eq_ignore_ascii_case("unix") => {
974 (StreamStd::connect_unix(url.path())?, false)
975 }
976 scheme => {
977 let url = url.to_string();
978 let scheme = scheme.to_string();
979 return Err(ImapClientStdError::UrlUnsupportedScheme(url, scheme));
980 }
981 };
982
983 if starttls && is_tls {
984 return Err(ImapClientStdError::StartTlsOverTls);
985 }
986
987 let stream = if starttls {
990 let mut stream = stream;
991 let mut fragmentizer = Fragmentizer::new(FRAGMENTIZER_MAX_MESSAGE_SIZE);
992 run_starttls(&mut stream, &mut fragmentizer)?;
993 stream.upgrade_tls(tls)?
994 } else {
995 stream
996 };
997
998 stream.set_read_timeout(Some(Duration::from_secs(5)))?;
1001
1002 let mut client = Self::new(stream);
1003 client.auto_id = auto_id;
1004
1005 let (mut capability, pre_authenticated) = if starttls {
1006 (client.capability()?, false)
1007 } else {
1008 let greeting = client.run(ImapGreetingGet::new(ImapGreetingGetOptions {
1009 ensure_capabilities: true,
1010 }))?;
1011 (greeting.capability, greeting.pre_authenticated)
1012 };
1013 client.pre_authenticated = pre_authenticated;
1014
1015 if let Some(sasl) = sasl.map(Into::into).filter(|_| !pre_authenticated) {
1019 let ir = capability.contains(&Capability::SaslIr);
1020
1021 capability = match sasl {
1022 Sasl::Anonymous(SaslAnonymous { message }) => {
1023 let opts = ImapAuthAnonymousOptions {
1024 initial_request: ir,
1025 ensure_capabilities: true,
1026 auto_id: client.auto_id.take(),
1027 };
1028
1029 client.auth_anonymous(message, opts)?
1030 }
1031 Sasl::Login(SaslLogin { username, password }) => {
1032 let opts = ImapLoginOptions {
1033 ensure_capabilities: true,
1034 auto_id: client.auto_id.take(),
1035 };
1036
1037 client.login(username, password.expose_secret(), opts)?
1038 }
1039 Sasl::Plain(SaslPlain {
1040 authzid,
1041 authcid,
1042 passwd,
1043 }) => {
1044 let opts = ImapAuthPlainOptions {
1045 initial_request: ir,
1046 ensure_capabilities: true,
1047 auto_id: client.auto_id.take(),
1048 };
1049
1050 client.auth_plain(authzid, authcid, passwd.expose_secret(), opts)?
1051 }
1052 Sasl::Oauthbearer(SaslOauthbearer {
1053 username,
1054 host,
1055 port,
1056 token,
1057 }) => {
1058 let opts = ImapAuthOauthbearerOptions {
1059 initial_request: ir,
1060 ensure_capabilities: true,
1061 auto_id: client.auto_id.take(),
1062 };
1063
1064 client.auth_oauthbearer(username, host, port, token.expose_secret(), opts)?
1065 }
1066 Sasl::Xoauth2(SaslXoauth2 { username, token }) => {
1067 let opts = ImapAuthXoauth2Options {
1068 initial_request: ir,
1069 ensure_capabilities: true,
1070 auto_id: client.auto_id.take(),
1071 };
1072
1073 client.auth_xoauth2(username, token.expose_secret(), opts)?
1074 }
1075 #[cfg(feature = "scram")]
1076 Sasl::ScramSha256(SaslScramSha256 { username, password }) => {
1077 let opts = ImapAuthScramSha256Options {
1078 initial_request: ir,
1079 ensure_capabilities: true,
1080 auto_id: client.auto_id.take(),
1081 };
1082
1083 client.auth_scram_sha256(username, password.expose_secret(), opts)?
1084 }
1085 #[cfg(not(feature = "scram"))]
1086 Sasl::ScramSha256(_) => {
1087 return Err(ImapClientStdError::ScramSha256NotEnabled);
1088 }
1089 };
1090 }
1091
1092 Ok((client, capability))
1093 }
1094}
1095
1096fn tcp_host(url: &Url) -> Result<&str, ImapClientStdError> {
1099 url.host_str()
1100 .ok_or_else(|| ImapClientStdError::UrlMissingHost(url.to_string()))
1101}
1102
1103#[cfg(any(
1106 feature = "rustls-aws",
1107 feature = "rustls-ring",
1108 feature = "native-tls"
1109))]
1110fn run_starttls(
1111 stream: &mut StreamStd,
1112 fragmentizer: &mut Fragmentizer,
1113) -> Result<(), ImapClientStdError> {
1114 let mut coroutine = ImapStartTls::new();
1115 let mut buf = [0u8; READ_BUFFER_SIZE];
1116 let mut arg: Option<&[u8]> = None;
1117
1118 loop {
1119 match coroutine.resume(fragmentizer, arg.take()) {
1120 ImapCoroutineState::Complete(Ok(_)) => return Ok(()),
1121 ImapCoroutineState::Complete(Err(err)) => return Err(err.into()),
1122 ImapCoroutineState::Yielded(ImapYield::WantsRead) => {
1123 let n = stream.read(&mut buf)?;
1124 arg = Some(&buf[..n]);
1125 }
1126 ImapCoroutineState::Yielded(ImapYield::WantsWrite(bytes)) => {
1127 stream.write_all(&bytes)?;
1128 }
1129 }
1130 }
1131}
1132
1133pub trait ImapStream: Read + Write + Send + Any {
1139 fn as_any_mut(&mut self) -> &mut dyn Any;
1141}
1142
1143impl<T: Read + Write + Send + Any> ImapStream for T {
1144 fn as_any_mut(&mut self) -> &mut dyn Any {
1145 self
1146 }
1147}