io_imap/lib.rs
1#![no_std]
2#![deny(missing_docs)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5//! # io-imap
6//!
7//! I/O-free IMAP client coroutines built on
8//! [imap-codec](https://docs.rs/imap-codec): every command exchange is
9//! a resumable state machine emitting read and write requests instead
10//! of performing I/O itself, so the caller owns the socket and pumps
11//! the coroutine (see the `client` feature for a ready-made
12//! std-blocking pump). imap-codec is re-exported as [`codec`], with
13//! imap-types as [`types`], so consumers encode and decode with the
14//! exact same codec version.
15//!
16//! The crate ships the three standard Pimalaya layers: the I/O-free
17//! coroutines (no_std core, always present), a light std client
18//! (`client` feature) wrapping a caller-provided stream, and a full
19//! std client (`rustls-ring` default, `rustls-aws`, `native-tls`) that
20//! also opens TCP, negotiates TLS and authenticates.
21//!
22//! ## Layout: one module per RFC
23//!
24//! Like io-http and io-oauth, the source tree mirrors the specs:
25//! [`rfc3501`] (IMAP4rev1 commands), [`rfc2177`] (IDLE), [`rfc2971`]
26//! (ID), [`rfc3691`] (UNSELECT), [`rfc4315`] (UIDPLUS), [`rfc5161`]
27//! (ENABLE), [`rfc5256`] (SORT and THREAD), [`rfc6851`] (MOVE),
28//! [`rfc7628`] (OAUTHBEARER) and `rfc7677` (SCRAM-SHA-256, behind the
29//! `scram` feature); the [`sasl`] module holds the RFC-agnostic
30//! mechanisms (ANONYMOUS, LOGIN, PLAIN, XOAUTH2). The CONDSTORE and
31//! QRESYNC extensions (RFC 7162) have no module of their own: they
32//! surface as parameters and response fields of the [`rfc3501`]
33//! select, examine and fetch coroutines, and power [`watch`]. Code
34//! spanning several RFC modules lives at the crate root: [`send`],
35//! [`watch`], [`client`] and [`coroutine`].
36//!
37//! Public types follow the Imap-Target-Verb naming scheme
38//! (`ImapMailboxCreate`, `ImapMessageFetchStream`) with Options, Error,
39//! Yield and Event companions; single-step coroutines hold the send
40//! directly, multi-step ones keep a private State enum.
41//!
42//! ## The coroutine contract
43//!
44//! Every coroutine implements [`coroutine::ImapCoroutine`]. Unlike the
45//! sibling io-* crates, resume takes two arguments besides self: a
46//! borrowed `Fragmentizer` (the connection-wide parser buffer, shared
47//! across every coroutine run on that connection so partial reads
48//! survive between commands) and the optional input bytes. It returns
49//! [`coroutine::ImapCoroutineState`]: either a yield or the terminal
50//! result. The [`imap_try!`] macro is the coroutine equivalent of `?`.
51//!
52//! The standard yield is [`coroutine::ImapYield`]: WantsRead (the
53//! caller reads more bytes and feeds them back, `Some(&[])` on EOF) or
54//! WantsWrite (the caller writes the given bytes). Coroutines that
55//! need richer signals declare their own yield enum: the IDLE and
56//! mailbox-watch coroutines add an Event variant, and the streaming
57//! APPEND and FETCH coroutines add WantsStream / BodyChunk variants so
58//! message bodies move straight between socket and caller storage
59//! without landing in memory whole.
60//!
61//! ## The send primitive
62//!
63//! Every command-shaped coroutine delegates to one shared primitive:
64//! [`send::ImapSend`]. It serialises the command through imap-codec
65//! (handling synchronising literals by pausing until the server
66//! continuation), then collects the response: data and untagged
67//! status lines accumulate, a tagged, bye or continuation-request line
68//! terminates, and undecodable untagged lines are skipped instead of
69//! failing the whole command. Its terminal value is
70//! [`send::ImapSendOutput`]; failures surface as
71//! [`send::ImapSendError`]. The receive-only constructor
72//! [`send::ImapSend::receive`] parses the response of a request whose
73//! bytes were written out of band (used by the streamed APPEND).
74//!
75//! ## Authentication
76//!
77//! Each SASL mechanism is its own coroutine supporting both the non-IR
78//! and SASL-IR (RFC 4959) flows. Every auth and login coroutine offers
79//! an optional auto_id chaining an RFC 2971 ID round-trip right after
80//! authentication, required by providers such as mail.qq.com and
81//! Fastmail. Secrets ride in imap-types `Secret` wrappers so they never
82//! land in logs.
83//!
84//! ## Watching a mailbox
85//!
86//! [`watch`] provides `ImapMailboxWatch`, a composite coroutine
87//! chaining ENABLE QRESYNC, SELECT (CONDSTORE), a FETCH baseline seed,
88//! then an IDLE wake-loop with SELECT (QRESYNC) delta pulls, emitting
89//! UID-keyed added/changed/removed events. The connection is dedicated;
90//! a shared `AtomicBool` winds it down cleanly.
91//!
92//! ## The std client
93//!
94//! [`client::ImapClientStd`] (`client` feature) wraps any blocking
95//! `Read + Write` stream plus a per-connection `Fragmentizer`, and
96//! exposes one method per coroutine. The connect constructor (TLS
97//! features) parses an imap:// or imaps:// URL, opens the connection
98//! through pimalaya-stream, performs the optional STARTTLS upgrade,
99//! reads the greeting and runs the chosen SASL mechanism.
100//!
101//! ## Conventions
102//!
103//! The conventions every Pimalaya repository shares (the sans-I/O
104//! coroutine approach, no_std, module and error rules) are described
105//! in the [Pimalaya ARCHITECTURE](https://github.com/pimalaya/.github/blob/master/ARCHITECTURE.md)
106//! and [GUIDELINES](https://github.com/pimalaya/.github/blob/master/GUIDELINES.md);
107//! the Imap-Target-Verb naming above is the org-wide canon, and the
108//! [`codec`] / [`types`] root re-exports are its blessed exception for
109//! foreign crates the API is built on.
110//! Coroutines log through the log crate at two levels: debug carries a
111//! short human-readable phrase at state changes, and a trace directly
112//! below dumps the data when there is any. Complete runnable programs
113//! live in the examples folder, one per layer.
114
115extern crate alloc;
116#[cfg(feature = "client")]
117extern crate std;
118
119#[cfg(feature = "client")]
120pub mod client;
121pub mod coroutine;
122pub mod rfc2177;
123pub mod rfc2971;
124pub mod rfc3501;
125pub mod rfc3691;
126pub mod rfc4315;
127pub mod rfc5161;
128pub mod rfc5256;
129pub mod rfc6851;
130pub mod rfc7628;
131#[cfg(feature = "scram")]
132pub mod rfc7677;
133pub mod sasl;
134pub mod send;
135pub mod watch;
136
137/// The imap-codec crate this version of io-imap builds on, re-exported
138/// so consumers encode and decode with the exact same codec version.
139pub use imap_codec as codec;
140/// The imap-types crate matching [`codec`], re-exported for the same
141/// version-lock reason.
142///
143/// Coroutine inputs and outputs are made of these types.
144pub use imap_codec::imap_types as types;
145
146/// Tests whether a capability list advertises a given capability, written as a
147/// [`matches!`]-style variant pattern without the `Capability::` prefix.
148///
149/// Matches by variant, so payload-carrying capabilities are checked with a
150/// wildcard: `has_imap_capability!(caps, Sort(_))` is true for both bare `SORT`
151/// and `SORT=DISPLAY`.
152///
153/// ```
154/// use io_imap::has_imap_capability;
155/// use io_imap::types::response::Capability;
156///
157/// let caps = [Capability::Move, Capability::Sort(None)];
158/// assert!(has_imap_capability!(caps, Sort(_)));
159/// assert!(has_imap_capability!(caps, Move));
160/// assert!(!has_imap_capability!(caps, Idle));
161/// ```
162#[macro_export]
163macro_rules! has_imap_capability {
164 ($caps:expr, $($variant:tt)+) => {
165 $caps
166 .iter()
167 .any(|capability| matches!(capability, $crate::types::response::Capability::$($variant)+))
168 };
169}