imap_codec/codec/
encode.rs

1//! # Encoding of messages.
2//!
3//! To facilitates handling of literals, [Encoder::encode] returns an instance of [`Encoded`].
4//! The idea is that the encoder not only "dumps" the final serialization of a message but can be iterated over.
5//!
6//! # Example
7//!
8//! ```rust
9//! use imap_codec::{
10//!     encode::{Encoder, Fragment},
11//!     imap_types::{
12//!         command::{Command, CommandBody},
13//!         core::LiteralMode,
14//!     },
15//!     CommandCodec,
16//! };
17//!
18//! let command = Command::new("A1", CommandBody::login("Alice", "Pa²²W0rD").unwrap()).unwrap();
19//!
20//! for fragment in CommandCodec::default().encode(&command) {
21//!     match fragment {
22//!         Fragment::Line { data } => {
23//!             // A line that is ready to be send.
24//!             println!("C: {}", String::from_utf8(data).unwrap());
25//!         }
26//!         Fragment::Literal { data, mode } => match mode {
27//!             LiteralMode::Sync => {
28//!                 // Wait for a continuation request.
29//!                 println!("S: + ...")
30//!             }
31//!             LiteralMode::NonSync => {
32//!                 // We don't need to wait for a continuation request
33//!                 // as the server will also not send it.
34//!             }
35//!         },
36//!     }
37//! }
38//! ```
39//!
40//! Output of example:
41//!
42//! ```imap
43//! C: A1 LOGIN alice {10}
44//! S: + ...
45//! C: Pa²²W0rD
46//! ```
47
48use std::{borrow::Borrow, io::Write, num::NonZeroU32};
49
50use base64::{engine::general_purpose::STANDARD as base64, Engine};
51use chrono::{DateTime as ChronoDateTime, FixedOffset};
52use imap_types::{
53    auth::{AuthMechanism, AuthenticateData},
54    body::{
55        BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
56        MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
57    },
58    command::{Command, CommandBody},
59    core::{
60        AString, Atom, AtomExt, Charset, IString, Literal, LiteralMode, NString, Quoted,
61        QuotedChar, Tag, Text,
62    },
63    datetime::{DateTime, NaiveDate},
64    envelope::{Address, Envelope},
65    extensions::idle::IdleDone,
66    fetch::{
67        Macro, MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName, Part, Section,
68    },
69    flag::{Flag, FlagFetch, FlagNameAttribute, FlagPerm, StoreResponse, StoreType},
70    mailbox::{ListCharString, ListMailbox, Mailbox, MailboxOther},
71    response::{
72        Capability, Code, CodeOther, CommandContinuationRequest, Data, Greeting, GreetingKind,
73        Response, Status,
74    },
75    search::SearchKey,
76    sequence::{SeqOrUid, Sequence, SequenceSet},
77    status::{StatusDataItem, StatusDataItemName},
78    utils::escape_quoted,
79};
80use utils::{join_serializable, List1AttributeValueOrNil, List1OrNil};
81
82use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
83
84/// Encoder.
85///
86/// Implemented for types that know how to encode a specific IMAP message. See [implementors](trait.Encoder.html#implementors).
87pub trait Encoder {
88    type Message<'a>;
89
90    /// Encode this message.
91    ///
92    /// This will return an [`Encoded`] message.
93    fn encode(&self, message: &Self::Message<'_>) -> Encoded;
94}
95
96/// An encoded message.
97///
98/// This struct facilitates the implementation of IMAP client- and server implementations by
99/// yielding the encoding of a message through [`Fragment`]s. This is required, because the usage of
100/// literals (and some other types) may change the IMAP message flow. Thus, in many cases, it is an
101/// error to just "dump" a message and send it over the network.
102///
103/// # Example
104///
105/// ```rust
106/// use imap_codec::{
107///     encode::{Encoder, Fragment},
108///     imap_types::command::{Command, CommandBody},
109///     CommandCodec,
110/// };
111///
112/// let cmd = Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap();
113///
114/// for fragment in CommandCodec::default().encode(&cmd) {
115///     match fragment {
116///         Fragment::Line { data } => {}
117///         Fragment::Literal { data, mode } => {}
118///     }
119/// }
120/// ```
121#[derive(Clone, Debug)]
122pub struct Encoded {
123    items: Vec<Fragment>,
124}
125
126impl Encoded {
127    /// Dump the (remaining) encoded data without being guided by [`Fragment`]s.
128    pub fn dump(self) -> Vec<u8> {
129        let mut out = Vec::new();
130
131        for fragment in self.items {
132            match fragment {
133                Fragment::Line { mut data } => out.append(&mut data),
134                Fragment::Literal { mut data, .. } => out.append(&mut data),
135            }
136        }
137
138        out
139    }
140}
141
142impl Iterator for Encoded {
143    type Item = Fragment;
144
145    fn next(&mut self) -> Option<Self::Item> {
146        if !self.items.is_empty() {
147            Some(self.items.remove(0))
148        } else {
149            None
150        }
151    }
152}
153
154/// The intended action of a client or server.
155#[derive(Clone, Debug, Eq, PartialEq)]
156pub enum Fragment {
157    /// A line that is ready to be send.
158    Line { data: Vec<u8> },
159
160    /// A literal that may require an action before it should be send.
161    Literal { data: Vec<u8>, mode: LiteralMode },
162}
163
164//--------------------------------------------------------------------------------------------------
165
166#[derive(Clone, Debug, Default, Eq, PartialEq)]
167pub(crate) struct EncodeContext {
168    accumulator: Vec<u8>,
169    items: Vec<Fragment>,
170}
171
172impl EncodeContext {
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    pub fn push_line(&mut self) {
178        self.items.push(Fragment::Line {
179            data: std::mem::take(&mut self.accumulator),
180        })
181    }
182
183    pub fn push_literal(&mut self, mode: LiteralMode) {
184        self.items.push(Fragment::Literal {
185            data: std::mem::take(&mut self.accumulator),
186            mode,
187        })
188    }
189
190    pub fn into_items(self) -> Vec<Fragment> {
191        let Self {
192            accumulator,
193            mut items,
194        } = self;
195
196        if !accumulator.is_empty() {
197            items.push(Fragment::Line { data: accumulator });
198        }
199
200        items
201    }
202
203    #[cfg(test)]
204    pub(crate) fn dump(self) -> Vec<u8> {
205        let mut out = Vec::new();
206
207        for item in self.into_items() {
208            match item {
209                Fragment::Line { data } | Fragment::Literal { data, .. } => {
210                    out.extend_from_slice(&data)
211                }
212            }
213        }
214
215        out
216    }
217}
218
219impl Write for EncodeContext {
220    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
221        self.accumulator.extend_from_slice(buf);
222        Ok(buf.len())
223    }
224
225    fn flush(&mut self) -> std::io::Result<()> {
226        Ok(())
227    }
228}
229
230macro_rules! impl_encoder_for_codec {
231    ($codec:ty, $message:ty) => {
232        impl Encoder for $codec {
233            type Message<'a> = $message;
234
235            fn encode(&self, message: &Self::Message<'_>) -> Encoded {
236                let mut encode_context = EncodeContext::new();
237                EncodeIntoContext::encode_ctx(message.borrow(), &mut encode_context).unwrap();
238
239                Encoded {
240                    items: encode_context.into_items(),
241                }
242            }
243        }
244    };
245}
246
247impl_encoder_for_codec!(GreetingCodec, Greeting<'a>);
248impl_encoder_for_codec!(CommandCodec, Command<'a>);
249impl_encoder_for_codec!(AuthenticateDataCodec, AuthenticateData);
250impl_encoder_for_codec!(ResponseCodec, Response<'a>);
251impl_encoder_for_codec!(IdleDoneCodec, IdleDone);
252
253// -------------------------------------------------------------------------------------------------
254
255pub(crate) trait EncodeIntoContext {
256    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
257}
258
259// ----- Primitive ---------------------------------------------------------------------------------
260
261impl EncodeIntoContext for u32 {
262    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
263        ctx.write_all(self.to_string().as_bytes())
264    }
265}
266
267impl EncodeIntoContext for u64 {
268    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
269        ctx.write_all(self.to_string().as_bytes())
270    }
271}
272
273// ----- Command -----------------------------------------------------------------------------------
274
275impl<'a> EncodeIntoContext for Command<'a> {
276    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
277        self.tag.encode_ctx(ctx)?;
278        ctx.write_all(b" ")?;
279        self.body.encode_ctx(ctx)?;
280        ctx.write_all(b"\r\n")
281    }
282}
283
284impl<'a> EncodeIntoContext for Tag<'a> {
285    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
286        ctx.write_all(self.inner().as_bytes())
287    }
288}
289
290impl<'a> EncodeIntoContext for CommandBody<'a> {
291    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
292        match self {
293            CommandBody::Capability => ctx.write_all(b"CAPABILITY"),
294            CommandBody::Noop => ctx.write_all(b"NOOP"),
295            CommandBody::Logout => ctx.write_all(b"LOGOUT"),
296            #[cfg(feature = "starttls")]
297            CommandBody::StartTLS => ctx.write_all(b"STARTTLS"),
298            CommandBody::Authenticate {
299                mechanism,
300                initial_response,
301            } => {
302                ctx.write_all(b"AUTHENTICATE")?;
303                ctx.write_all(b" ")?;
304                mechanism.encode_ctx(ctx)?;
305
306                if let Some(ir) = initial_response {
307                    ctx.write_all(b" ")?;
308
309                    // RFC 4959 (https://datatracker.ietf.org/doc/html/rfc4959#section-3)
310                    // "To send a zero-length initial response, the client MUST send a single pad character ("=").
311                    // This indicates that the response is present, but is a zero-length string."
312                    if ir.declassify().is_empty() {
313                        ctx.write_all(b"=")?;
314                    } else {
315                        ctx.write_all(base64.encode(ir.declassify()).as_bytes())?;
316                    };
317                };
318
319                Ok(())
320            }
321            CommandBody::Login { username, password } => {
322                ctx.write_all(b"LOGIN")?;
323                ctx.write_all(b" ")?;
324                username.encode_ctx(ctx)?;
325                ctx.write_all(b" ")?;
326                password.declassify().encode_ctx(ctx)
327            }
328            CommandBody::Select { mailbox } => {
329                ctx.write_all(b"SELECT")?;
330                ctx.write_all(b" ")?;
331                mailbox.encode_ctx(ctx)
332            }
333            CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
334            CommandBody::Examine { mailbox } => {
335                ctx.write_all(b"EXAMINE")?;
336                ctx.write_all(b" ")?;
337                mailbox.encode_ctx(ctx)
338            }
339            CommandBody::Create { mailbox } => {
340                ctx.write_all(b"CREATE")?;
341                ctx.write_all(b" ")?;
342                mailbox.encode_ctx(ctx)
343            }
344            CommandBody::Delete { mailbox } => {
345                ctx.write_all(b"DELETE")?;
346                ctx.write_all(b" ")?;
347                mailbox.encode_ctx(ctx)
348            }
349            CommandBody::Rename {
350                from: mailbox,
351                to: new_mailbox,
352            } => {
353                ctx.write_all(b"RENAME")?;
354                ctx.write_all(b" ")?;
355                mailbox.encode_ctx(ctx)?;
356                ctx.write_all(b" ")?;
357                new_mailbox.encode_ctx(ctx)
358            }
359            CommandBody::Subscribe { mailbox } => {
360                ctx.write_all(b"SUBSCRIBE")?;
361                ctx.write_all(b" ")?;
362                mailbox.encode_ctx(ctx)
363            }
364            CommandBody::Unsubscribe { mailbox } => {
365                ctx.write_all(b"UNSUBSCRIBE")?;
366                ctx.write_all(b" ")?;
367                mailbox.encode_ctx(ctx)
368            }
369            CommandBody::List {
370                reference,
371                mailbox_wildcard,
372            } => {
373                ctx.write_all(b"LIST")?;
374                ctx.write_all(b" ")?;
375                reference.encode_ctx(ctx)?;
376                ctx.write_all(b" ")?;
377                mailbox_wildcard.encode_ctx(ctx)
378            }
379            CommandBody::Lsub {
380                reference,
381                mailbox_wildcard,
382            } => {
383                ctx.write_all(b"LSUB")?;
384                ctx.write_all(b" ")?;
385                reference.encode_ctx(ctx)?;
386                ctx.write_all(b" ")?;
387                mailbox_wildcard.encode_ctx(ctx)
388            }
389            CommandBody::Status {
390                mailbox,
391                item_names,
392            } => {
393                ctx.write_all(b"STATUS")?;
394                ctx.write_all(b" ")?;
395                mailbox.encode_ctx(ctx)?;
396                ctx.write_all(b" ")?;
397                ctx.write_all(b"(")?;
398                join_serializable(item_names, b" ", ctx)?;
399                ctx.write_all(b")")
400            }
401            CommandBody::Append {
402                mailbox,
403                flags,
404                date,
405                message,
406            } => {
407                ctx.write_all(b"APPEND")?;
408                ctx.write_all(b" ")?;
409                mailbox.encode_ctx(ctx)?;
410
411                if !flags.is_empty() {
412                    ctx.write_all(b" ")?;
413                    ctx.write_all(b"(")?;
414                    join_serializable(flags, b" ", ctx)?;
415                    ctx.write_all(b")")?;
416                }
417
418                if let Some(date) = date {
419                    ctx.write_all(b" ")?;
420                    date.encode_ctx(ctx)?;
421                }
422
423                ctx.write_all(b" ")?;
424                message.encode_ctx(ctx)
425            }
426            CommandBody::Check => ctx.write_all(b"CHECK"),
427            CommandBody::Close => ctx.write_all(b"CLOSE"),
428            CommandBody::Expunge => ctx.write_all(b"EXPUNGE"),
429            CommandBody::Search {
430                charset,
431                criteria,
432                uid,
433            } => {
434                if *uid {
435                    ctx.write_all(b"UID SEARCH")?;
436                } else {
437                    ctx.write_all(b"SEARCH")?;
438                }
439                if let Some(charset) = charset {
440                    ctx.write_all(b" CHARSET ")?;
441                    charset.encode_ctx(ctx)?;
442                }
443                ctx.write_all(b" ")?;
444                criteria.encode_ctx(ctx)
445            }
446            CommandBody::Fetch {
447                sequence_set,
448                macro_or_item_names,
449                uid,
450            } => {
451                if *uid {
452                    ctx.write_all(b"UID FETCH ")?;
453                } else {
454                    ctx.write_all(b"FETCH ")?;
455                }
456
457                sequence_set.encode_ctx(ctx)?;
458                ctx.write_all(b" ")?;
459                macro_or_item_names.encode_ctx(ctx)
460            }
461            CommandBody::Store {
462                sequence_set,
463                kind,
464                response,
465                flags,
466                uid,
467            } => {
468                if *uid {
469                    ctx.write_all(b"UID STORE ")?;
470                } else {
471                    ctx.write_all(b"STORE ")?;
472                }
473
474                sequence_set.encode_ctx(ctx)?;
475                ctx.write_all(b" ")?;
476
477                match kind {
478                    StoreType::Add => ctx.write_all(b"+")?,
479                    StoreType::Remove => ctx.write_all(b"-")?,
480                    StoreType::Replace => {}
481                }
482
483                ctx.write_all(b"FLAGS")?;
484
485                match response {
486                    StoreResponse::Answer => {}
487                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
488                }
489
490                ctx.write_all(b" (")?;
491                join_serializable(flags, b" ", ctx)?;
492                ctx.write_all(b")")
493            }
494            CommandBody::Copy {
495                sequence_set,
496                mailbox,
497                uid,
498            } => {
499                if *uid {
500                    ctx.write_all(b"UID COPY ")?;
501                } else {
502                    ctx.write_all(b"COPY ")?;
503                }
504                sequence_set.encode_ctx(ctx)?;
505                ctx.write_all(b" ")?;
506                mailbox.encode_ctx(ctx)
507            }
508            CommandBody::Idle => ctx.write_all(b"IDLE"),
509            CommandBody::Enable { capabilities } => {
510                ctx.write_all(b"ENABLE ")?;
511                join_serializable(capabilities.as_ref(), b" ", ctx)
512            }
513            CommandBody::Compress { algorithm } => {
514                ctx.write_all(b"COMPRESS ")?;
515                algorithm.encode_ctx(ctx)
516            }
517            CommandBody::GetQuota { root } => {
518                ctx.write_all(b"GETQUOTA ")?;
519                root.encode_ctx(ctx)
520            }
521            CommandBody::GetQuotaRoot { mailbox } => {
522                ctx.write_all(b"GETQUOTAROOT ")?;
523                mailbox.encode_ctx(ctx)
524            }
525            CommandBody::SetQuota { root, quotas } => {
526                ctx.write_all(b"SETQUOTA ")?;
527                root.encode_ctx(ctx)?;
528                ctx.write_all(b" (")?;
529                join_serializable(quotas.as_ref(), b" ", ctx)?;
530                ctx.write_all(b")")
531            }
532            CommandBody::Move {
533                sequence_set,
534                mailbox,
535                uid,
536            } => {
537                if *uid {
538                    ctx.write_all(b"UID MOVE ")?;
539                } else {
540                    ctx.write_all(b"MOVE ")?;
541                }
542                sequence_set.encode_ctx(ctx)?;
543                ctx.write_all(b" ")?;
544                mailbox.encode_ctx(ctx)
545            }
546        }
547    }
548}
549
550impl<'a> EncodeIntoContext for AuthMechanism<'a> {
551    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
552        write!(ctx, "{}", self)
553    }
554}
555
556impl EncodeIntoContext for AuthenticateData {
557    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
558        let encoded = base64.encode(self.0.declassify());
559        ctx.write_all(encoded.as_bytes())?;
560        ctx.write_all(b"\r\n")
561    }
562}
563
564impl<'a> EncodeIntoContext for AString<'a> {
565    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
566        match self {
567            AString::Atom(atom) => atom.encode_ctx(ctx),
568            AString::String(imap_str) => imap_str.encode_ctx(ctx),
569        }
570    }
571}
572
573impl<'a> EncodeIntoContext for Atom<'a> {
574    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
575        ctx.write_all(self.inner().as_bytes())
576    }
577}
578
579impl<'a> EncodeIntoContext for AtomExt<'a> {
580    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
581        ctx.write_all(self.inner().as_bytes())
582    }
583}
584
585impl<'a> EncodeIntoContext for IString<'a> {
586    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
587        match self {
588            Self::Literal(val) => val.encode_ctx(ctx),
589            Self::Quoted(val) => val.encode_ctx(ctx),
590        }
591    }
592}
593
594impl<'a> EncodeIntoContext for Literal<'a> {
595    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
596        match self.mode() {
597            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
598            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
599        }
600
601        ctx.push_line();
602        ctx.write_all(self.as_ref())?;
603        ctx.push_literal(self.mode());
604
605        Ok(())
606    }
607}
608
609impl<'a> EncodeIntoContext for Quoted<'a> {
610    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
611        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
612    }
613}
614
615impl<'a> EncodeIntoContext for Mailbox<'a> {
616    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
617        match self {
618            Mailbox::Inbox => ctx.write_all(b"INBOX"),
619            Mailbox::Other(other) => other.encode_ctx(ctx),
620        }
621    }
622}
623
624impl<'a> EncodeIntoContext for MailboxOther<'a> {
625    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
626        self.inner().encode_ctx(ctx)
627    }
628}
629
630impl<'a> EncodeIntoContext for ListMailbox<'a> {
631    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
632        match self {
633            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
634            ListMailbox::String(istr) => istr.encode_ctx(ctx),
635        }
636    }
637}
638
639impl<'a> EncodeIntoContext for ListCharString<'a> {
640    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
641        ctx.write_all(self.as_ref())
642    }
643}
644
645impl EncodeIntoContext for StatusDataItemName {
646    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
647        match self {
648            Self::Messages => ctx.write_all(b"MESSAGES"),
649            Self::Recent => ctx.write_all(b"RECENT"),
650            Self::UidNext => ctx.write_all(b"UIDNEXT"),
651            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
652            Self::Unseen => ctx.write_all(b"UNSEEN"),
653            Self::Deleted => ctx.write_all(b"DELETED"),
654            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
655            #[cfg(feature = "ext_condstore_qresync")]
656            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
657        }
658    }
659}
660
661impl<'a> EncodeIntoContext for Flag<'a> {
662    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
663        write!(ctx, "{}", self)
664    }
665}
666
667impl<'a> EncodeIntoContext for FlagFetch<'a> {
668    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
669        match self {
670            Self::Flag(flag) => flag.encode_ctx(ctx),
671            Self::Recent => ctx.write_all(b"\\Recent"),
672        }
673    }
674}
675
676impl<'a> EncodeIntoContext for FlagPerm<'a> {
677    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
678        match self {
679            Self::Flag(flag) => flag.encode_ctx(ctx),
680            Self::Asterisk => ctx.write_all(b"\\*"),
681        }
682    }
683}
684
685impl EncodeIntoContext for DateTime {
686    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
687        self.as_ref().encode_ctx(ctx)
688    }
689}
690
691impl<'a> EncodeIntoContext for Charset<'a> {
692    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
693        match self {
694            Charset::Atom(atom) => atom.encode_ctx(ctx),
695            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
696        }
697    }
698}
699
700impl<'a> EncodeIntoContext for SearchKey<'a> {
701    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
702        match self {
703            SearchKey::All => ctx.write_all(b"ALL"),
704            SearchKey::Answered => ctx.write_all(b"ANSWERED"),
705            SearchKey::Bcc(astring) => {
706                ctx.write_all(b"BCC ")?;
707                astring.encode_ctx(ctx)
708            }
709            SearchKey::Before(date) => {
710                ctx.write_all(b"BEFORE ")?;
711                date.encode_ctx(ctx)
712            }
713            SearchKey::Body(astring) => {
714                ctx.write_all(b"BODY ")?;
715                astring.encode_ctx(ctx)
716            }
717            SearchKey::Cc(astring) => {
718                ctx.write_all(b"CC ")?;
719                astring.encode_ctx(ctx)
720            }
721            SearchKey::Deleted => ctx.write_all(b"DELETED"),
722            SearchKey::Flagged => ctx.write_all(b"FLAGGED"),
723            SearchKey::From(astring) => {
724                ctx.write_all(b"FROM ")?;
725                astring.encode_ctx(ctx)
726            }
727            SearchKey::Keyword(flag_keyword) => {
728                ctx.write_all(b"KEYWORD ")?;
729                flag_keyword.encode_ctx(ctx)
730            }
731            SearchKey::New => ctx.write_all(b"NEW"),
732            SearchKey::Old => ctx.write_all(b"OLD"),
733            SearchKey::On(date) => {
734                ctx.write_all(b"ON ")?;
735                date.encode_ctx(ctx)
736            }
737            SearchKey::Recent => ctx.write_all(b"RECENT"),
738            SearchKey::Seen => ctx.write_all(b"SEEN"),
739            SearchKey::Since(date) => {
740                ctx.write_all(b"SINCE ")?;
741                date.encode_ctx(ctx)
742            }
743            SearchKey::Subject(astring) => {
744                ctx.write_all(b"SUBJECT ")?;
745                astring.encode_ctx(ctx)
746            }
747            SearchKey::Text(astring) => {
748                ctx.write_all(b"TEXT ")?;
749                astring.encode_ctx(ctx)
750            }
751            SearchKey::To(astring) => {
752                ctx.write_all(b"TO ")?;
753                astring.encode_ctx(ctx)
754            }
755            SearchKey::Unanswered => ctx.write_all(b"UNANSWERED"),
756            SearchKey::Undeleted => ctx.write_all(b"UNDELETED"),
757            SearchKey::Unflagged => ctx.write_all(b"UNFLAGGED"),
758            SearchKey::Unkeyword(flag_keyword) => {
759                ctx.write_all(b"UNKEYWORD ")?;
760                flag_keyword.encode_ctx(ctx)
761            }
762            SearchKey::Unseen => ctx.write_all(b"UNSEEN"),
763            SearchKey::Draft => ctx.write_all(b"DRAFT"),
764            SearchKey::Header(header_fld_name, astring) => {
765                ctx.write_all(b"HEADER ")?;
766                header_fld_name.encode_ctx(ctx)?;
767                ctx.write_all(b" ")?;
768                astring.encode_ctx(ctx)
769            }
770            SearchKey::Larger(number) => write!(ctx, "LARGER {number}"),
771            SearchKey::Not(search_key) => {
772                ctx.write_all(b"NOT ")?;
773                search_key.encode_ctx(ctx)
774            }
775            SearchKey::Or(search_key_a, search_key_b) => {
776                ctx.write_all(b"OR ")?;
777                search_key_a.encode_ctx(ctx)?;
778                ctx.write_all(b" ")?;
779                search_key_b.encode_ctx(ctx)
780            }
781            SearchKey::SentBefore(date) => {
782                ctx.write_all(b"SENTBEFORE ")?;
783                date.encode_ctx(ctx)
784            }
785            SearchKey::SentOn(date) => {
786                ctx.write_all(b"SENTON ")?;
787                date.encode_ctx(ctx)
788            }
789            SearchKey::SentSince(date) => {
790                ctx.write_all(b"SENTSINCE ")?;
791                date.encode_ctx(ctx)
792            }
793            SearchKey::Smaller(number) => write!(ctx, "SMALLER {number}"),
794            SearchKey::Uid(sequence_set) => {
795                ctx.write_all(b"UID ")?;
796                sequence_set.encode_ctx(ctx)
797            }
798            SearchKey::Undraft => ctx.write_all(b"UNDRAFT"),
799            SearchKey::SequenceSet(sequence_set) => sequence_set.encode_ctx(ctx),
800            SearchKey::And(search_keys) => {
801                ctx.write_all(b"(")?;
802                join_serializable(search_keys.as_ref(), b" ", ctx)?;
803                ctx.write_all(b")")
804            }
805        }
806    }
807}
808
809impl EncodeIntoContext for SequenceSet {
810    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
811        join_serializable(self.0.as_ref(), b",", ctx)
812    }
813}
814
815impl EncodeIntoContext for Sequence {
816    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
817        match self {
818            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
819            Sequence::Range(from, to) => {
820                from.encode_ctx(ctx)?;
821                ctx.write_all(b":")?;
822                to.encode_ctx(ctx)
823            }
824        }
825    }
826}
827
828impl EncodeIntoContext for SeqOrUid {
829    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
830        match self {
831            SeqOrUid::Value(number) => write!(ctx, "{number}"),
832            SeqOrUid::Asterisk => ctx.write_all(b"*"),
833        }
834    }
835}
836
837impl EncodeIntoContext for NaiveDate {
838    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
839        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
840    }
841}
842
843impl<'a> EncodeIntoContext for MacroOrMessageDataItemNames<'a> {
844    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
845        match self {
846            Self::Macro(m) => m.encode_ctx(ctx),
847            Self::MessageDataItemNames(item_names) => {
848                if item_names.len() == 1 {
849                    item_names[0].encode_ctx(ctx)
850                } else {
851                    ctx.write_all(b"(")?;
852                    join_serializable(item_names.as_slice(), b" ", ctx)?;
853                    ctx.write_all(b")")
854                }
855            }
856        }
857    }
858}
859
860impl EncodeIntoContext for Macro {
861    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
862        write!(ctx, "{}", self)
863    }
864}
865
866impl<'a> EncodeIntoContext for MessageDataItemName<'a> {
867    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
868        match self {
869            Self::Body => ctx.write_all(b"BODY"),
870            Self::BodyExt {
871                section,
872                partial,
873                peek,
874            } => {
875                if *peek {
876                    ctx.write_all(b"BODY.PEEK[")?;
877                } else {
878                    ctx.write_all(b"BODY[")?;
879                }
880                if let Some(section) = section {
881                    section.encode_ctx(ctx)?;
882                }
883                ctx.write_all(b"]")?;
884                if let Some((a, b)) = partial {
885                    write!(ctx, "<{a}.{b}>")?;
886                }
887
888                Ok(())
889            }
890            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
891            Self::Envelope => ctx.write_all(b"ENVELOPE"),
892            Self::Flags => ctx.write_all(b"FLAGS"),
893            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
894            Self::Rfc822 => ctx.write_all(b"RFC822"),
895            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
896            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
897            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
898            Self::Uid => ctx.write_all(b"UID"),
899        }
900    }
901}
902
903impl<'a> EncodeIntoContext for Section<'a> {
904    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
905        match self {
906            Section::Part(part) => part.encode_ctx(ctx),
907            Section::Header(maybe_part) => match maybe_part {
908                Some(part) => {
909                    part.encode_ctx(ctx)?;
910                    ctx.write_all(b".HEADER")
911                }
912                None => ctx.write_all(b"HEADER"),
913            },
914            Section::HeaderFields(maybe_part, header_list) => {
915                match maybe_part {
916                    Some(part) => {
917                        part.encode_ctx(ctx)?;
918                        ctx.write_all(b".HEADER.FIELDS (")?;
919                    }
920                    None => ctx.write_all(b"HEADER.FIELDS (")?,
921                };
922                join_serializable(header_list.as_ref(), b" ", ctx)?;
923                ctx.write_all(b")")
924            }
925            Section::HeaderFieldsNot(maybe_part, header_list) => {
926                match maybe_part {
927                    Some(part) => {
928                        part.encode_ctx(ctx)?;
929                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
930                    }
931                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
932                };
933                join_serializable(header_list.as_ref(), b" ", ctx)?;
934                ctx.write_all(b")")
935            }
936            Section::Text(maybe_part) => match maybe_part {
937                Some(part) => {
938                    part.encode_ctx(ctx)?;
939                    ctx.write_all(b".TEXT")
940                }
941                None => ctx.write_all(b"TEXT"),
942            },
943            Section::Mime(part) => {
944                part.encode_ctx(ctx)?;
945                ctx.write_all(b".MIME")
946            }
947        }
948    }
949}
950
951impl EncodeIntoContext for Part {
952    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
953        join_serializable(self.0.as_ref(), b".", ctx)
954    }
955}
956
957impl EncodeIntoContext for NonZeroU32 {
958    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
959        write!(ctx, "{self}")
960    }
961}
962
963impl<'a> EncodeIntoContext for Capability<'a> {
964    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
965        write!(ctx, "{}", self)
966    }
967}
968
969// ----- Responses ---------------------------------------------------------------------------------
970
971impl<'a> EncodeIntoContext for Response<'a> {
972    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
973        match self {
974            Response::Status(status) => status.encode_ctx(ctx),
975            Response::Data(data) => data.encode_ctx(ctx),
976            Response::CommandContinuationRequest(continue_request) => {
977                continue_request.encode_ctx(ctx)
978            }
979        }
980    }
981}
982
983impl<'a> EncodeIntoContext for Greeting<'a> {
984    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
985        ctx.write_all(b"* ")?;
986        self.kind.encode_ctx(ctx)?;
987        ctx.write_all(b" ")?;
988
989        if let Some(ref code) = self.code {
990            ctx.write_all(b"[")?;
991            code.encode_ctx(ctx)?;
992            ctx.write_all(b"] ")?;
993        }
994
995        self.text.encode_ctx(ctx)?;
996        ctx.write_all(b"\r\n")
997    }
998}
999
1000impl EncodeIntoContext for GreetingKind {
1001    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1002        match self {
1003            GreetingKind::Ok => ctx.write_all(b"OK"),
1004            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
1005            GreetingKind::Bye => ctx.write_all(b"BYE"),
1006        }
1007    }
1008}
1009
1010impl<'a> EncodeIntoContext for Status<'a> {
1011    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1012        fn format_status(
1013            tag: &Option<Tag>,
1014            status: &str,
1015            code: &Option<Code>,
1016            comment: &Text,
1017            ctx: &mut EncodeContext,
1018        ) -> std::io::Result<()> {
1019            match tag {
1020                Some(tag) => tag.encode_ctx(ctx)?,
1021                None => ctx.write_all(b"*")?,
1022            }
1023            ctx.write_all(b" ")?;
1024            ctx.write_all(status.as_bytes())?;
1025            ctx.write_all(b" ")?;
1026            if let Some(code) = code {
1027                ctx.write_all(b"[")?;
1028                code.encode_ctx(ctx)?;
1029                ctx.write_all(b"] ")?;
1030            }
1031            comment.encode_ctx(ctx)?;
1032            ctx.write_all(b"\r\n")
1033        }
1034
1035        match self {
1036            Status::Ok { tag, code, text } => format_status(tag, "OK", code, text, ctx),
1037            Status::No { tag, code, text } => format_status(tag, "NO", code, text, ctx),
1038            Status::Bad { tag, code, text } => format_status(tag, "BAD", code, text, ctx),
1039            Status::Bye { code, text } => format_status(&None, "BYE", code, text, ctx),
1040        }
1041    }
1042}
1043
1044impl<'a> EncodeIntoContext for Code<'a> {
1045    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1046        match self {
1047            Code::Alert => ctx.write_all(b"ALERT"),
1048            Code::BadCharset { allowed } => {
1049                if allowed.is_empty() {
1050                    ctx.write_all(b"BADCHARSET")
1051                } else {
1052                    ctx.write_all(b"BADCHARSET (")?;
1053                    join_serializable(allowed, b" ", ctx)?;
1054                    ctx.write_all(b")")
1055                }
1056            }
1057            Code::Capability(caps) => {
1058                ctx.write_all(b"CAPABILITY ")?;
1059                join_serializable(caps.as_ref(), b" ", ctx)
1060            }
1061            Code::Parse => ctx.write_all(b"PARSE"),
1062            Code::PermanentFlags(flags) => {
1063                ctx.write_all(b"PERMANENTFLAGS (")?;
1064                join_serializable(flags, b" ", ctx)?;
1065                ctx.write_all(b")")
1066            }
1067            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
1068            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
1069            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
1070            Code::UidNext(next) => {
1071                ctx.write_all(b"UIDNEXT ")?;
1072                next.encode_ctx(ctx)
1073            }
1074            Code::UidValidity(validity) => {
1075                ctx.write_all(b"UIDVALIDITY ")?;
1076                validity.encode_ctx(ctx)
1077            }
1078            Code::Unseen(seq) => {
1079                ctx.write_all(b"UNSEEN ")?;
1080                seq.encode_ctx(ctx)
1081            }
1082            // RFC 2221
1083            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1084            Code::Referral(url) => {
1085                ctx.write_all(b"REFERRAL ")?;
1086                ctx.write_all(url.as_bytes())
1087            }
1088            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
1089            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
1090            Code::TooBig => ctx.write_all(b"TOOBIG"),
1091            Code::Other(unknown) => unknown.encode_ctx(ctx),
1092        }
1093    }
1094}
1095
1096impl<'a> EncodeIntoContext for CodeOther<'a> {
1097    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1098        ctx.write_all(self.inner())
1099    }
1100}
1101
1102impl<'a> EncodeIntoContext for Text<'a> {
1103    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1104        ctx.write_all(self.inner().as_bytes())
1105    }
1106}
1107
1108impl<'a> EncodeIntoContext for Data<'a> {
1109    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1110        match self {
1111            Data::Capability(caps) => {
1112                ctx.write_all(b"* CAPABILITY ")?;
1113                join_serializable(caps.as_ref(), b" ", ctx)?;
1114            }
1115            Data::List {
1116                items,
1117                delimiter,
1118                mailbox,
1119            } => {
1120                ctx.write_all(b"* LIST (")?;
1121                join_serializable(items, b" ", ctx)?;
1122                ctx.write_all(b") ")?;
1123
1124                if let Some(delimiter) = delimiter {
1125                    ctx.write_all(b"\"")?;
1126                    delimiter.encode_ctx(ctx)?;
1127                    ctx.write_all(b"\"")?;
1128                } else {
1129                    ctx.write_all(b"NIL")?;
1130                }
1131                ctx.write_all(b" ")?;
1132                mailbox.encode_ctx(ctx)?;
1133            }
1134            Data::Lsub {
1135                items,
1136                delimiter,
1137                mailbox,
1138            } => {
1139                ctx.write_all(b"* LSUB (")?;
1140                join_serializable(items, b" ", ctx)?;
1141                ctx.write_all(b") ")?;
1142
1143                if let Some(delimiter) = delimiter {
1144                    ctx.write_all(b"\"")?;
1145                    delimiter.encode_ctx(ctx)?;
1146                    ctx.write_all(b"\"")?;
1147                } else {
1148                    ctx.write_all(b"NIL")?;
1149                }
1150                ctx.write_all(b" ")?;
1151                mailbox.encode_ctx(ctx)?;
1152            }
1153            Data::Status { mailbox, items } => {
1154                ctx.write_all(b"* STATUS ")?;
1155                mailbox.encode_ctx(ctx)?;
1156                ctx.write_all(b" (")?;
1157                join_serializable(items, b" ", ctx)?;
1158                ctx.write_all(b")")?;
1159            }
1160            Data::Search(seqs) => {
1161                if seqs.is_empty() {
1162                    ctx.write_all(b"* SEARCH")?;
1163                } else {
1164                    ctx.write_all(b"* SEARCH ")?;
1165                    join_serializable(seqs, b" ", ctx)?;
1166                }
1167            }
1168            Data::Flags(flags) => {
1169                ctx.write_all(b"* FLAGS (")?;
1170                join_serializable(flags, b" ", ctx)?;
1171                ctx.write_all(b")")?;
1172            }
1173            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
1174            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
1175            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
1176            Data::Fetch { seq, items } => {
1177                write!(ctx, "* {seq} FETCH (")?;
1178                join_serializable(items.as_ref(), b" ", ctx)?;
1179                ctx.write_all(b")")?;
1180            }
1181            Data::Enabled { capabilities } => {
1182                write!(ctx, "* ENABLED")?;
1183
1184                for cap in capabilities {
1185                    ctx.write_all(b" ")?;
1186                    cap.encode_ctx(ctx)?;
1187                }
1188            }
1189            Data::Quota { root, quotas } => {
1190                ctx.write_all(b"* QUOTA ")?;
1191                root.encode_ctx(ctx)?;
1192                ctx.write_all(b" (")?;
1193                join_serializable(quotas.as_ref(), b" ", ctx)?;
1194                ctx.write_all(b")")?;
1195            }
1196            Data::QuotaRoot { mailbox, roots } => {
1197                ctx.write_all(b"* QUOTAROOT ")?;
1198                mailbox.encode_ctx(ctx)?;
1199                for root in roots {
1200                    ctx.write_all(b" ")?;
1201                    root.encode_ctx(ctx)?;
1202                }
1203            }
1204        }
1205
1206        ctx.write_all(b"\r\n")
1207    }
1208}
1209
1210impl<'a> EncodeIntoContext for FlagNameAttribute<'a> {
1211    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1212        write!(ctx, "{}", self)
1213    }
1214}
1215
1216impl EncodeIntoContext for QuotedChar {
1217    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1218        match self.inner() {
1219            '\\' => ctx.write_all(b"\\\\"),
1220            '"' => ctx.write_all(b"\\\""),
1221            other => ctx.write_all(&[other as u8]),
1222        }
1223    }
1224}
1225
1226impl EncodeIntoContext for StatusDataItem {
1227    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1228        match self {
1229            Self::Messages(count) => {
1230                ctx.write_all(b"MESSAGES ")?;
1231                count.encode_ctx(ctx)
1232            }
1233            Self::Recent(count) => {
1234                ctx.write_all(b"RECENT ")?;
1235                count.encode_ctx(ctx)
1236            }
1237            Self::UidNext(next) => {
1238                ctx.write_all(b"UIDNEXT ")?;
1239                next.encode_ctx(ctx)
1240            }
1241            Self::UidValidity(identifier) => {
1242                ctx.write_all(b"UIDVALIDITY ")?;
1243                identifier.encode_ctx(ctx)
1244            }
1245            Self::Unseen(count) => {
1246                ctx.write_all(b"UNSEEN ")?;
1247                count.encode_ctx(ctx)
1248            }
1249            Self::Deleted(count) => {
1250                ctx.write_all(b"DELETED ")?;
1251                count.encode_ctx(ctx)
1252            }
1253            Self::DeletedStorage(count) => {
1254                ctx.write_all(b"DELETED-STORAGE ")?;
1255                count.encode_ctx(ctx)
1256            }
1257        }
1258    }
1259}
1260
1261impl<'a> EncodeIntoContext for MessageDataItem<'a> {
1262    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1263        match self {
1264            Self::BodyExt {
1265                section,
1266                origin,
1267                data,
1268            } => {
1269                ctx.write_all(b"BODY[")?;
1270                if let Some(section) = section {
1271                    section.encode_ctx(ctx)?;
1272                }
1273                ctx.write_all(b"]")?;
1274                if let Some(origin) = origin {
1275                    write!(ctx, "<{origin}>")?;
1276                }
1277                ctx.write_all(b" ")?;
1278                data.encode_ctx(ctx)
1279            }
1280            // FIXME: do not return body-ext-1part and body-ext-mpart here
1281            Self::Body(body) => {
1282                ctx.write_all(b"BODY ")?;
1283                body.encode_ctx(ctx)
1284            }
1285            Self::BodyStructure(body) => {
1286                ctx.write_all(b"BODYSTRUCTURE ")?;
1287                body.encode_ctx(ctx)
1288            }
1289            Self::Envelope(envelope) => {
1290                ctx.write_all(b"ENVELOPE ")?;
1291                envelope.encode_ctx(ctx)
1292            }
1293            Self::Flags(flags) => {
1294                ctx.write_all(b"FLAGS (")?;
1295                join_serializable(flags, b" ", ctx)?;
1296                ctx.write_all(b")")
1297            }
1298            Self::InternalDate(datetime) => {
1299                ctx.write_all(b"INTERNALDATE ")?;
1300                datetime.encode_ctx(ctx)
1301            }
1302            Self::Rfc822(nstring) => {
1303                ctx.write_all(b"RFC822 ")?;
1304                nstring.encode_ctx(ctx)
1305            }
1306            Self::Rfc822Header(nstring) => {
1307                ctx.write_all(b"RFC822.HEADER ")?;
1308                nstring.encode_ctx(ctx)
1309            }
1310            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
1311            Self::Rfc822Text(nstring) => {
1312                ctx.write_all(b"RFC822.TEXT ")?;
1313                nstring.encode_ctx(ctx)
1314            }
1315            Self::Uid(uid) => write!(ctx, "UID {uid}"),
1316        }
1317    }
1318}
1319
1320impl<'a> EncodeIntoContext for NString<'a> {
1321    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1322        match &self.0 {
1323            Some(imap_str) => imap_str.encode_ctx(ctx),
1324            None => ctx.write_all(b"NIL"),
1325        }
1326    }
1327}
1328
1329impl<'a> EncodeIntoContext for BodyStructure<'a> {
1330    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1331        ctx.write_all(b"(")?;
1332        match self {
1333            BodyStructure::Single {
1334                body,
1335                extension_data: extension,
1336            } => {
1337                body.encode_ctx(ctx)?;
1338                if let Some(extension) = extension {
1339                    ctx.write_all(b" ")?;
1340                    extension.encode_ctx(ctx)?;
1341                }
1342            }
1343            BodyStructure::Multi {
1344                bodies,
1345                subtype,
1346                extension_data,
1347            } => {
1348                for body in bodies.as_ref() {
1349                    body.encode_ctx(ctx)?;
1350                }
1351                ctx.write_all(b" ")?;
1352                subtype.encode_ctx(ctx)?;
1353
1354                if let Some(extension) = extension_data {
1355                    ctx.write_all(b" ")?;
1356                    extension.encode_ctx(ctx)?;
1357                }
1358            }
1359        }
1360        ctx.write_all(b")")
1361    }
1362}
1363
1364impl<'a> EncodeIntoContext for Body<'a> {
1365    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1366        match self.specific {
1367            SpecificFields::Basic {
1368                r#type: ref type_,
1369                ref subtype,
1370            } => {
1371                type_.encode_ctx(ctx)?;
1372                ctx.write_all(b" ")?;
1373                subtype.encode_ctx(ctx)?;
1374                ctx.write_all(b" ")?;
1375                self.basic.encode_ctx(ctx)
1376            }
1377            SpecificFields::Message {
1378                ref envelope,
1379                ref body_structure,
1380                number_of_lines,
1381            } => {
1382                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
1383                self.basic.encode_ctx(ctx)?;
1384                ctx.write_all(b" ")?;
1385                envelope.encode_ctx(ctx)?;
1386                ctx.write_all(b" ")?;
1387                body_structure.encode_ctx(ctx)?;
1388                ctx.write_all(b" ")?;
1389                write!(ctx, "{number_of_lines}")
1390            }
1391            SpecificFields::Text {
1392                ref subtype,
1393                number_of_lines,
1394            } => {
1395                ctx.write_all(b"\"TEXT\" ")?;
1396                subtype.encode_ctx(ctx)?;
1397                ctx.write_all(b" ")?;
1398                self.basic.encode_ctx(ctx)?;
1399                ctx.write_all(b" ")?;
1400                write!(ctx, "{number_of_lines}")
1401            }
1402        }
1403    }
1404}
1405
1406impl<'a> EncodeIntoContext for BasicFields<'a> {
1407    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1408        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
1409        ctx.write_all(b" ")?;
1410        self.id.encode_ctx(ctx)?;
1411        ctx.write_all(b" ")?;
1412        self.description.encode_ctx(ctx)?;
1413        ctx.write_all(b" ")?;
1414        self.content_transfer_encoding.encode_ctx(ctx)?;
1415        ctx.write_all(b" ")?;
1416        write!(ctx, "{}", self.size)
1417    }
1418}
1419
1420impl<'a> EncodeIntoContext for Envelope<'a> {
1421    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1422        ctx.write_all(b"(")?;
1423        self.date.encode_ctx(ctx)?;
1424        ctx.write_all(b" ")?;
1425        self.subject.encode_ctx(ctx)?;
1426        ctx.write_all(b" ")?;
1427        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
1428        ctx.write_all(b" ")?;
1429        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
1430        ctx.write_all(b" ")?;
1431        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
1432        ctx.write_all(b" ")?;
1433        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
1434        ctx.write_all(b" ")?;
1435        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
1436        ctx.write_all(b" ")?;
1437        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
1438        ctx.write_all(b" ")?;
1439        self.in_reply_to.encode_ctx(ctx)?;
1440        ctx.write_all(b" ")?;
1441        self.message_id.encode_ctx(ctx)?;
1442        ctx.write_all(b")")
1443    }
1444}
1445
1446impl<'a> EncodeIntoContext for Address<'a> {
1447    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1448        ctx.write_all(b"(")?;
1449        self.name.encode_ctx(ctx)?;
1450        ctx.write_all(b" ")?;
1451        self.adl.encode_ctx(ctx)?;
1452        ctx.write_all(b" ")?;
1453        self.mailbox.encode_ctx(ctx)?;
1454        ctx.write_all(b" ")?;
1455        self.host.encode_ctx(ctx)?;
1456        ctx.write_all(b")")?;
1457
1458        Ok(())
1459    }
1460}
1461
1462impl<'a> EncodeIntoContext for SinglePartExtensionData<'a> {
1463    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1464        self.md5.encode_ctx(ctx)?;
1465
1466        if let Some(disposition) = &self.tail {
1467            ctx.write_all(b" ")?;
1468            disposition.encode_ctx(ctx)?;
1469        }
1470
1471        Ok(())
1472    }
1473}
1474
1475impl<'a> EncodeIntoContext for MultiPartExtensionData<'a> {
1476    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1477        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
1478
1479        if let Some(disposition) = &self.tail {
1480            ctx.write_all(b" ")?;
1481            disposition.encode_ctx(ctx)?;
1482        }
1483
1484        Ok(())
1485    }
1486}
1487
1488impl<'a> EncodeIntoContext for Disposition<'a> {
1489    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1490        match &self.disposition {
1491            Some((s, param)) => {
1492                ctx.write_all(b"(")?;
1493                s.encode_ctx(ctx)?;
1494                ctx.write_all(b" ")?;
1495                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
1496                ctx.write_all(b")")?;
1497            }
1498            None => ctx.write_all(b"NIL")?,
1499        }
1500
1501        if let Some(language) = &self.tail {
1502            ctx.write_all(b" ")?;
1503            language.encode_ctx(ctx)?;
1504        }
1505
1506        Ok(())
1507    }
1508}
1509
1510impl<'a> EncodeIntoContext for Language<'a> {
1511    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1512        List1OrNil(&self.language, b" ").encode_ctx(ctx)?;
1513
1514        if let Some(location) = &self.tail {
1515            ctx.write_all(b" ")?;
1516            location.encode_ctx(ctx)?;
1517        }
1518
1519        Ok(())
1520    }
1521}
1522
1523impl<'a> EncodeIntoContext for Location<'a> {
1524    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1525        self.location.encode_ctx(ctx)?;
1526
1527        for body_extension in &self.extensions {
1528            ctx.write_all(b" ")?;
1529            body_extension.encode_ctx(ctx)?;
1530        }
1531
1532        Ok(())
1533    }
1534}
1535
1536impl<'a> EncodeIntoContext for BodyExtension<'a> {
1537    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1538        match self {
1539            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
1540            BodyExtension::Number(number) => number.encode_ctx(ctx),
1541            BodyExtension::List(list) => {
1542                ctx.write_all(b"(")?;
1543                join_serializable(list.as_ref(), b" ", ctx)?;
1544                ctx.write_all(b")")
1545            }
1546        }
1547    }
1548}
1549
1550impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
1551    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1552        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
1553    }
1554}
1555
1556impl<'a> EncodeIntoContext for CommandContinuationRequest<'a> {
1557    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1558        match self {
1559            Self::Basic(continue_basic) => match continue_basic.code() {
1560                Some(code) => {
1561                    ctx.write_all(b"+ [")?;
1562                    code.encode_ctx(ctx)?;
1563                    ctx.write_all(b"] ")?;
1564                    continue_basic.text().encode_ctx(ctx)?;
1565                    ctx.write_all(b"\r\n")
1566                }
1567                None => {
1568                    ctx.write_all(b"+ ")?;
1569                    continue_basic.text().encode_ctx(ctx)?;
1570                    ctx.write_all(b"\r\n")
1571                }
1572            },
1573            Self::Base64(data) => {
1574                ctx.write_all(b"+ ")?;
1575                ctx.write_all(base64.encode(data).as_bytes())?;
1576                ctx.write_all(b"\r\n")
1577            }
1578        }
1579    }
1580}
1581
1582mod utils {
1583    use std::io::Write;
1584
1585    use super::{EncodeContext, EncodeIntoContext};
1586
1587    pub struct List1OrNil<'a, T>(pub &'a Vec<T>, pub &'a [u8]);
1588
1589    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
1590
1591    pub(crate) fn join_serializable<I: EncodeIntoContext>(
1592        elements: &[I],
1593        sep: &[u8],
1594        ctx: &mut EncodeContext,
1595    ) -> std::io::Result<()> {
1596        if let Some((last, head)) = elements.split_last() {
1597            for item in head {
1598                item.encode_ctx(ctx)?;
1599                ctx.write_all(sep)?;
1600            }
1601
1602            last.encode_ctx(ctx)
1603        } else {
1604            Ok(())
1605        }
1606    }
1607
1608    impl<'a, T> EncodeIntoContext for List1OrNil<'a, T>
1609    where
1610        T: EncodeIntoContext,
1611    {
1612        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1613            if let Some((last, head)) = self.0.split_last() {
1614                ctx.write_all(b"(")?;
1615
1616                for item in head {
1617                    item.encode_ctx(ctx)?;
1618                    ctx.write_all(self.1)?;
1619                }
1620
1621                last.encode_ctx(ctx)?;
1622
1623                ctx.write_all(b")")
1624            } else {
1625                ctx.write_all(b"NIL")
1626            }
1627        }
1628    }
1629
1630    impl<'a, T> EncodeIntoContext for List1AttributeValueOrNil<'a, T>
1631    where
1632        T: EncodeIntoContext,
1633    {
1634        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1635            if let Some((last, head)) = self.0.split_last() {
1636                ctx.write_all(b"(")?;
1637
1638                for (attribute, value) in head {
1639                    attribute.encode_ctx(ctx)?;
1640                    ctx.write_all(b" ")?;
1641                    value.encode_ctx(ctx)?;
1642                    ctx.write_all(b" ")?;
1643                }
1644
1645                let (attribute, value) = last;
1646                attribute.encode_ctx(ctx)?;
1647                ctx.write_all(b" ")?;
1648                value.encode_ctx(ctx)?;
1649
1650                ctx.write_all(b")")
1651            } else {
1652                ctx.write_all(b"NIL")
1653            }
1654        }
1655    }
1656}
1657
1658#[cfg(test)]
1659mod tests {
1660    use std::num::NonZeroU32;
1661
1662    use imap_types::{
1663        auth::AuthMechanism,
1664        command::{Command, CommandBody},
1665        core::{AString, Literal, NString, NonEmptyVec},
1666        fetch::MessageDataItem,
1667        response::{Data, Response},
1668        utils::escape_byte_string,
1669    };
1670
1671    use super::*;
1672
1673    #[test]
1674    fn test_api_encoder_usage() {
1675        let cmd = Command::new(
1676            "A",
1677            CommandBody::login(
1678                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
1679                "password",
1680            )
1681            .unwrap(),
1682        )
1683        .unwrap();
1684
1685        // Dump.
1686        let got_encoded = CommandCodec::default().encode(&cmd).dump();
1687
1688        // Encoded.
1689        let encoded = CommandCodec::default().encode(&cmd);
1690
1691        let mut out = Vec::new();
1692
1693        for x in encoded {
1694            match x {
1695                Fragment::Line { data } => {
1696                    println!("C: {}", escape_byte_string(&data));
1697                    out.extend_from_slice(&data);
1698                }
1699                Fragment::Literal { data, mode } => {
1700                    match mode {
1701                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
1702                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
1703                    }
1704
1705                    println!("C: {}", escape_byte_string(&data));
1706                    out.extend_from_slice(&data);
1707                }
1708            }
1709        }
1710
1711        assert_eq!(got_encoded, out);
1712    }
1713
1714    #[test]
1715    fn test_encode_command() {
1716        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
1717            (
1718                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
1719                [Fragment::Line {
1720                    data: b"A LOGIN alice pass\r\n".to_vec(),
1721                }]
1722                .as_ref(),
1723            ),
1724            (
1725                Command::new(
1726                    "A",
1727                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
1728                )
1729                .unwrap(),
1730                [
1731                    Fragment::Line {
1732                        data: b"A LOGIN alice {2}\r\n".to_vec(),
1733                    },
1734                    Fragment::Literal {
1735                        data: b"\xCA\xFE".to_vec(),
1736                        mode: LiteralMode::Sync,
1737                    },
1738                    Fragment::Line {
1739                        data: b"\r\n".to_vec(),
1740                    },
1741                ]
1742                .as_ref(),
1743            ),
1744            (
1745                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
1746                [Fragment::Line {
1747                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
1748                }]
1749                .as_ref(),
1750            ),
1751            (
1752                Command::new(
1753                    "A",
1754                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
1755                )
1756                .unwrap(),
1757                [Fragment::Line {
1758                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
1759                }]
1760                .as_ref(),
1761            ),
1762            (
1763                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
1764                [Fragment::Line {
1765                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
1766                }]
1767                .as_ref(),
1768            ),
1769            (
1770                Command::new(
1771                    "A",
1772                    CommandBody::authenticate_with_ir(
1773                        AuthMechanism::Plain,
1774                        b"\x00alice\x00pass".as_ref(),
1775                    ),
1776                )
1777                .unwrap(),
1778                [Fragment::Line {
1779                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
1780                }]
1781                .as_ref(),
1782            ),
1783        ]);
1784    }
1785
1786    #[test]
1787    fn test_encode_response() {
1788        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
1789            (
1790                Response::Data(Data::Fetch {
1791                    seq: NonZeroU32::new(12345).unwrap(),
1792                    items: NonEmptyVec::from(MessageDataItem::BodyExt {
1793                        section: None,
1794                        origin: None,
1795                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
1796                    }),
1797                }),
1798                [
1799                    Fragment::Line {
1800                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
1801                    },
1802                    Fragment::Literal {
1803                        data: b"ABCDE".to_vec(),
1804                        mode: LiteralMode::Sync,
1805                    },
1806                    Fragment::Line {
1807                        data: b")\r\n".to_vec(),
1808                    },
1809                ]
1810                .as_ref(),
1811            ),
1812            (
1813                Response::Data(Data::Fetch {
1814                    seq: NonZeroU32::new(12345).unwrap(),
1815                    items: NonEmptyVec::from(MessageDataItem::BodyExt {
1816                        section: None,
1817                        origin: None,
1818                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
1819                    }),
1820                }),
1821                [
1822                    Fragment::Line {
1823                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
1824                    },
1825                    Fragment::Literal {
1826                        data: b"ABCDE".to_vec(),
1827                        mode: LiteralMode::NonSync,
1828                    },
1829                    Fragment::Line {
1830                        data: b")\r\n".to_vec(),
1831                    },
1832                ]
1833                .as_ref(),
1834            ),
1835        ])
1836    }
1837
1838    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
1839    where
1840        E: Encoder<Message<'a> = M> + Default,
1841        F: AsRef<[Fragment]>,
1842    {
1843        for (i, (obj, actions)) in tests.iter().enumerate() {
1844            println!("# Testing {i}");
1845
1846            let encoder = E::default().encode(obj);
1847            let actions = actions.as_ref();
1848
1849            assert_eq!(encoder.collect::<Vec<_>>(), actions);
1850        }
1851    }
1852}