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