Skip to main content

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//!     CommandCodec,
11//!     encode::{Encoder, Fragment},
12//!     imap_types::{
13//!         command::{Command, CommandBody},
14//!         core::LiteralMode,
15//!     },
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
48#[cfg(feature = "ext_condstore_qresync")]
49use std::num::NonZeroU64;
50use std::{borrow::Borrow, collections::VecDeque, io::Write, num::NonZeroU32};
51
52use base64::{Engine, engine::general_purpose::STANDARD as base64};
53use chrono::{DateTime as ChronoDateTime, FixedOffset};
54#[cfg(feature = "ext_condstore_qresync")]
55use imap_types::command::{FetchModifier, SelectParameter, StoreModifier};
56use imap_types::{
57    auth::{AuthMechanism, AuthenticateData},
58    body::{
59        BasicFields, Body, BodyExtension, BodyStructure, Disposition, Language, Location,
60        MultiPartExtensionData, SinglePartExtensionData, SpecificFields,
61    },
62    command::{Command, CommandBody},
63    core::{
64        AString, Atom, AtomExt, Charset, IString, Literal, LiteralMode, NString, NString8, Quoted,
65        QuotedChar, Tag, Text,
66    },
67    datetime::{DateTime, NaiveDate},
68    envelope::{Address, Envelope},
69    extensions::idle::IdleDone,
70    fetch::{
71        Macro, MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName, Part, Section,
72    },
73    flag::{Flag, FlagFetch, FlagNameAttribute, FlagPerm, StoreResponse, StoreType},
74    mailbox::{ListCharString, ListMailbox, Mailbox, MailboxOther},
75    response::{
76        Bye, Capability, Code, CodeOther, CommandContinuationRequest, Data, Greeting, GreetingKind,
77        Response, Status, StatusBody, StatusKind, Tagged,
78    },
79    search::SearchKey,
80    sequence::{SeqOrUid, Sequence, SequenceSet},
81    status::{StatusDataItem, StatusDataItemName},
82    utils::escape_quoted,
83};
84use utils::{List1AttributeValueOrNil, List1OrNil, join_serializable};
85
86#[cfg(feature = "ext_namespace")]
87use crate::extensions::namespace::encode_namespaces;
88use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
89
90/// Encoder.
91///
92/// Implemented for types that know how to encode a specific IMAP message. See [implementors](trait.Encoder.html#implementors).
93pub trait Encoder {
94    type Message<'a>;
95
96    /// Encode this message.
97    ///
98    /// This will return an [`Encoded`] message.
99    fn encode(&self, message: &Self::Message<'_>) -> Encoded;
100}
101
102/// An encoded message.
103///
104/// This struct facilitates the implementation of IMAP client- and server implementations by
105/// yielding the encoding of a message through [`Fragment`]s. This is required, because the usage of
106/// literals (and some other types) may change the IMAP message flow. Thus, in many cases, it is an
107/// error to just "dump" a message and send it over the network.
108///
109/// # Example
110///
111/// ```rust
112/// use imap_codec::{
113///     CommandCodec,
114///     encode::{Encoder, Fragment},
115///     imap_types::command::{Command, CommandBody},
116/// };
117///
118/// let cmd = Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap();
119///
120/// for fragment in CommandCodec::default().encode(&cmd) {
121///     match fragment {
122///         Fragment::Line { data } => {}
123///         Fragment::Literal { data, mode } => {}
124///     }
125/// }
126/// ```
127#[derive(Clone, Debug)]
128pub struct Encoded {
129    items: VecDeque<Fragment>,
130}
131
132impl Encoded {
133    /// Dump the (remaining) encoded data without being guided by [`Fragment`]s.
134    pub fn dump(self) -> Vec<u8> {
135        let mut out = Vec::new();
136
137        for fragment in self.items {
138            match fragment {
139                Fragment::Line { mut data } => out.append(&mut data),
140                Fragment::Literal { mut data, .. } => out.append(&mut data),
141            }
142        }
143
144        out
145    }
146}
147
148impl Iterator for Encoded {
149    type Item = Fragment;
150
151    fn next(&mut self) -> Option<Self::Item> {
152        self.items.pop_front()
153    }
154}
155
156/// The intended action of a client or server.
157#[derive(Clone, Debug, Eq, PartialEq)]
158pub enum Fragment {
159    /// A line that is ready to be send.
160    Line { data: Vec<u8> },
161
162    /// A literal that may require an action before it should be send.
163    Literal { data: Vec<u8>, mode: LiteralMode },
164}
165
166//--------------------------------------------------------------------------------------------------
167
168#[derive(Clone, Debug, Default, Eq, PartialEq)]
169pub(crate) struct EncodeContext {
170    accumulator: Vec<u8>,
171    items: VecDeque<Fragment>,
172}
173
174impl EncodeContext {
175    pub fn new() -> Self {
176        Self::default()
177    }
178
179    pub fn push_line(&mut self) {
180        self.items.push_back(Fragment::Line {
181            data: std::mem::take(&mut self.accumulator),
182        })
183    }
184
185    pub fn push_literal(&mut self, mode: LiteralMode) {
186        self.items.push_back(Fragment::Literal {
187            data: std::mem::take(&mut self.accumulator),
188            mode,
189        })
190    }
191
192    pub fn into_items(self) -> VecDeque<Fragment> {
193        let Self {
194            accumulator,
195            mut items,
196        } = self;
197
198        if !accumulator.is_empty() {
199            items.push_back(Fragment::Line { data: accumulator });
200        }
201
202        items
203    }
204
205    #[cfg(test)]
206    pub(crate) fn dump(self) -> Vec<u8> {
207        let mut out = Vec::new();
208
209        for item in self.into_items() {
210            match item {
211                Fragment::Line { data } | Fragment::Literal { data, .. } => {
212                    out.extend_from_slice(&data)
213                }
214            }
215        }
216
217        out
218    }
219}
220
221impl Write for EncodeContext {
222    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
223        self.accumulator.extend_from_slice(buf);
224        Ok(buf.len())
225    }
226
227    fn flush(&mut self) -> std::io::Result<()> {
228        Ok(())
229    }
230}
231
232macro_rules! impl_encoder_for_codec {
233    ($codec:ty, $message:ty) => {
234        impl Encoder for $codec {
235            type Message<'a> = $message;
236
237            fn encode(&self, message: &Self::Message<'_>) -> Encoded {
238                let mut encode_context = EncodeContext::new();
239                EncodeIntoContext::encode_ctx(message.borrow(), &mut encode_context).unwrap();
240
241                Encoded {
242                    items: encode_context.into_items(),
243                }
244            }
245        }
246    };
247}
248
249impl_encoder_for_codec!(GreetingCodec, Greeting<'a>);
250impl_encoder_for_codec!(CommandCodec, Command<'a>);
251impl_encoder_for_codec!(AuthenticateDataCodec, AuthenticateData<'a>);
252impl_encoder_for_codec!(ResponseCodec, Response<'a>);
253impl_encoder_for_codec!(IdleDoneCodec, IdleDone);
254
255// -------------------------------------------------------------------------------------------------
256
257pub(crate) trait EncodeIntoContext {
258    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
259}
260
261// ----- Primitive ---------------------------------------------------------------------------------
262
263impl EncodeIntoContext for u32 {
264    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
265        ctx.write_all(self.to_string().as_bytes())
266    }
267}
268
269impl EncodeIntoContext for u64 {
270    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
271        ctx.write_all(self.to_string().as_bytes())
272    }
273}
274
275// ----- Command -----------------------------------------------------------------------------------
276
277impl EncodeIntoContext for Command<'_> {
278    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
279        self.tag.encode_ctx(ctx)?;
280        ctx.write_all(b" ")?;
281        self.body.encode_ctx(ctx)?;
282        ctx.write_all(b"\r\n")
283    }
284}
285
286impl EncodeIntoContext for Tag<'_> {
287    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
288        ctx.write_all(self.inner().as_bytes())
289    }
290}
291
292impl EncodeIntoContext for CommandBody<'_> {
293    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
294        match self {
295            CommandBody::Capability => ctx.write_all(b"CAPABILITY"),
296            CommandBody::Noop => ctx.write_all(b"NOOP"),
297            CommandBody::Logout => ctx.write_all(b"LOGOUT"),
298            #[cfg(feature = "starttls")]
299            CommandBody::StartTLS => ctx.write_all(b"STARTTLS"),
300            CommandBody::Authenticate {
301                mechanism,
302                initial_response,
303            } => {
304                ctx.write_all(b"AUTHENTICATE ")?;
305                mechanism.encode_ctx(ctx)?;
306
307                if let Some(ir) = initial_response {
308                    ctx.write_all(b" ")?;
309
310                    // RFC 4959 (https://datatracker.ietf.org/doc/html/rfc4959#section-3)
311                    // "To send a zero-length initial response, the client MUST send a single pad character ("=").
312                    // This indicates that the response is present, but is a zero-length string."
313                    if ir.declassify().is_empty() {
314                        ctx.write_all(b"=")?;
315                    } else {
316                        ctx.write_all(base64.encode(ir.declassify()).as_bytes())?;
317                    };
318                };
319
320                Ok(())
321            }
322            CommandBody::Login { username, password } => {
323                ctx.write_all(b"LOGIN ")?;
324                username.encode_ctx(ctx)?;
325                ctx.write_all(b" ")?;
326                password.declassify().encode_ctx(ctx)
327            }
328            CommandBody::Select {
329                mailbox,
330                #[cfg(feature = "ext_condstore_qresync")]
331                parameters,
332            } => {
333                ctx.write_all(b"SELECT ")?;
334                mailbox.encode_ctx(ctx)?;
335
336                #[cfg(feature = "ext_condstore_qresync")]
337                if !parameters.is_empty() {
338                    ctx.write_all(b" (")?;
339                    join_serializable(parameters, b" ", ctx)?;
340                    ctx.write_all(b")")?;
341                }
342
343                Ok(())
344            }
345            CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
346            CommandBody::Examine {
347                mailbox,
348                #[cfg(feature = "ext_condstore_qresync")]
349                parameters,
350            } => {
351                ctx.write_all(b"EXAMINE ")?;
352                mailbox.encode_ctx(ctx)?;
353
354                #[cfg(feature = "ext_condstore_qresync")]
355                if !parameters.is_empty() {
356                    ctx.write_all(b" (")?;
357                    join_serializable(parameters, b" ", ctx)?;
358                    ctx.write_all(b")")?;
359                }
360
361                Ok(())
362            }
363            CommandBody::Create { mailbox } => {
364                ctx.write_all(b"CREATE ")?;
365                mailbox.encode_ctx(ctx)
366            }
367            CommandBody::Delete { mailbox } => {
368                ctx.write_all(b"DELETE ")?;
369                mailbox.encode_ctx(ctx)
370            }
371            CommandBody::Rename {
372                from: mailbox,
373                to: new_mailbox,
374            } => {
375                ctx.write_all(b"RENAME ")?;
376                mailbox.encode_ctx(ctx)?;
377                ctx.write_all(b" ")?;
378                new_mailbox.encode_ctx(ctx)
379            }
380            CommandBody::Subscribe { mailbox } => {
381                ctx.write_all(b"SUBSCRIBE ")?;
382                mailbox.encode_ctx(ctx)
383            }
384            CommandBody::Unsubscribe { mailbox } => {
385                ctx.write_all(b"UNSUBSCRIBE ")?;
386                mailbox.encode_ctx(ctx)
387            }
388            CommandBody::List {
389                reference,
390                mailbox_wildcard,
391            } => {
392                ctx.write_all(b"LIST ")?;
393                reference.encode_ctx(ctx)?;
394                ctx.write_all(b" ")?;
395                mailbox_wildcard.encode_ctx(ctx)
396            }
397            CommandBody::Lsub {
398                reference,
399                mailbox_wildcard,
400            } => {
401                ctx.write_all(b"LSUB ")?;
402                reference.encode_ctx(ctx)?;
403                ctx.write_all(b" ")?;
404                mailbox_wildcard.encode_ctx(ctx)
405            }
406            CommandBody::Status {
407                mailbox,
408                item_names,
409            } => {
410                ctx.write_all(b"STATUS ")?;
411                mailbox.encode_ctx(ctx)?;
412                ctx.write_all(b" (")?;
413                join_serializable(item_names, b" ", ctx)?;
414                ctx.write_all(b")")
415            }
416            CommandBody::Append {
417                mailbox,
418                flags,
419                date,
420                message,
421            } => {
422                ctx.write_all(b"APPEND ")?;
423                mailbox.encode_ctx(ctx)?;
424
425                if !flags.is_empty() {
426                    ctx.write_all(b" (")?;
427                    join_serializable(flags, b" ", ctx)?;
428                    ctx.write_all(b")")?;
429                }
430
431                if let Some(date) = date {
432                    ctx.write_all(b" ")?;
433                    date.encode_ctx(ctx)?;
434                }
435
436                ctx.write_all(b" ")?;
437                message.encode_ctx(ctx)
438            }
439            CommandBody::Check => ctx.write_all(b"CHECK"),
440            CommandBody::Close => ctx.write_all(b"CLOSE"),
441            CommandBody::Expunge => ctx.write_all(b"EXPUNGE"),
442            CommandBody::ExpungeUid { sequence_set } => {
443                ctx.write_all(b"UID EXPUNGE ")?;
444                sequence_set.encode_ctx(ctx)
445            }
446            CommandBody::Search {
447                charset,
448                criteria,
449                uid,
450            } => {
451                if *uid {
452                    ctx.write_all(b"UID SEARCH")?;
453                } else {
454                    ctx.write_all(b"SEARCH")?;
455                }
456                if let Some(charset) = charset {
457                    ctx.write_all(b" CHARSET ")?;
458                    charset.encode_ctx(ctx)?;
459                }
460                ctx.write_all(b" ")?;
461                join_serializable(criteria.as_ref(), b" ", ctx)
462            }
463            CommandBody::Sort {
464                sort_criteria,
465                charset,
466                search_criteria,
467                uid,
468            } => {
469                if *uid {
470                    ctx.write_all(b"UID SORT (")?;
471                } else {
472                    ctx.write_all(b"SORT (")?;
473                }
474                join_serializable(sort_criteria.as_ref(), b" ", ctx)?;
475                ctx.write_all(b") ")?;
476                charset.encode_ctx(ctx)?;
477                ctx.write_all(b" ")?;
478                join_serializable(search_criteria.as_ref(), b" ", ctx)
479            }
480            CommandBody::Thread {
481                algorithm,
482                charset,
483                search_criteria,
484                uid,
485            } => {
486                if *uid {
487                    ctx.write_all(b"UID THREAD ")?;
488                } else {
489                    ctx.write_all(b"THREAD ")?;
490                }
491                algorithm.encode_ctx(ctx)?;
492                ctx.write_all(b" ")?;
493                charset.encode_ctx(ctx)?;
494                ctx.write_all(b" ")?;
495                join_serializable(search_criteria.as_ref(), b" ", ctx)
496            }
497            CommandBody::Fetch {
498                sequence_set,
499                macro_or_item_names,
500                uid,
501                #[cfg(feature = "ext_condstore_qresync")]
502                modifiers,
503            } => {
504                if *uid {
505                    ctx.write_all(b"UID FETCH ")?;
506                } else {
507                    ctx.write_all(b"FETCH ")?;
508                }
509
510                sequence_set.encode_ctx(ctx)?;
511                ctx.write_all(b" ")?;
512                macro_or_item_names.encode_ctx(ctx)?;
513
514                #[cfg(feature = "ext_condstore_qresync")]
515                if !modifiers.is_empty() {
516                    ctx.write_all(b" (")?;
517                    join_serializable(modifiers, b" ", ctx)?;
518                    ctx.write_all(b")")?;
519                }
520
521                Ok(())
522            }
523            CommandBody::Store {
524                sequence_set,
525                kind,
526                response,
527                flags,
528                uid,
529                #[cfg(feature = "ext_condstore_qresync")]
530                modifiers,
531            } => {
532                if *uid {
533                    ctx.write_all(b"UID STORE ")?;
534                } else {
535                    ctx.write_all(b"STORE ")?;
536                }
537
538                sequence_set.encode_ctx(ctx)?;
539                ctx.write_all(b" ")?;
540
541                #[cfg(feature = "ext_condstore_qresync")]
542                if !modifiers.is_empty() {
543                    ctx.write_all(b"(")?;
544                    join_serializable(modifiers, b" ", ctx)?;
545                    ctx.write_all(b") ")?;
546                }
547
548                match kind {
549                    StoreType::Add => ctx.write_all(b"+")?,
550                    StoreType::Remove => ctx.write_all(b"-")?,
551                    StoreType::Replace => {}
552                }
553
554                ctx.write_all(b"FLAGS")?;
555
556                match response {
557                    StoreResponse::Answer => {}
558                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
559                }
560
561                ctx.write_all(b" (")?;
562                join_serializable(flags, b" ", ctx)?;
563                ctx.write_all(b")")
564            }
565            CommandBody::Copy {
566                sequence_set,
567                mailbox,
568                uid,
569            } => {
570                if *uid {
571                    ctx.write_all(b"UID COPY ")?;
572                } else {
573                    ctx.write_all(b"COPY ")?;
574                }
575                sequence_set.encode_ctx(ctx)?;
576                ctx.write_all(b" ")?;
577                mailbox.encode_ctx(ctx)
578            }
579            CommandBody::Idle => ctx.write_all(b"IDLE"),
580            CommandBody::Enable { capabilities } => {
581                ctx.write_all(b"ENABLE ")?;
582                join_serializable(capabilities.as_ref(), b" ", ctx)
583            }
584            CommandBody::Compress { algorithm } => {
585                ctx.write_all(b"COMPRESS ")?;
586                algorithm.encode_ctx(ctx)
587            }
588            CommandBody::GetQuota { root } => {
589                ctx.write_all(b"GETQUOTA ")?;
590                root.encode_ctx(ctx)
591            }
592            CommandBody::GetQuotaRoot { mailbox } => {
593                ctx.write_all(b"GETQUOTAROOT ")?;
594                mailbox.encode_ctx(ctx)
595            }
596            CommandBody::SetQuota { root, quotas } => {
597                ctx.write_all(b"SETQUOTA ")?;
598                root.encode_ctx(ctx)?;
599                ctx.write_all(b" (")?;
600                join_serializable(quotas.as_ref(), b" ", ctx)?;
601                ctx.write_all(b")")
602            }
603            CommandBody::Move {
604                sequence_set,
605                mailbox,
606                uid,
607            } => {
608                if *uid {
609                    ctx.write_all(b"UID MOVE ")?;
610                } else {
611                    ctx.write_all(b"MOVE ")?;
612                }
613                sequence_set.encode_ctx(ctx)?;
614                ctx.write_all(b" ")?;
615                mailbox.encode_ctx(ctx)
616            }
617            #[cfg(feature = "ext_id")]
618            CommandBody::Id { parameters } => {
619                ctx.write_all(b"ID ")?;
620
621                match parameters {
622                    Some(parameters) => {
623                        if let Some((first, tail)) = parameters.split_first() {
624                            ctx.write_all(b"(")?;
625
626                            first.0.encode_ctx(ctx)?;
627                            ctx.write_all(b" ")?;
628                            first.1.encode_ctx(ctx)?;
629
630                            for parameter in tail {
631                                ctx.write_all(b" ")?;
632                                parameter.0.encode_ctx(ctx)?;
633                                ctx.write_all(b" ")?;
634                                parameter.1.encode_ctx(ctx)?;
635                            }
636
637                            ctx.write_all(b")")
638                        } else {
639                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
640                            {
641                                ctx.write_all(b"()")
642                            }
643                            #[cfg(feature = "quirk_id_empty_to_nil")]
644                            {
645                                ctx.write_all(b"NIL")
646                            }
647                        }
648                    }
649                    None => ctx.write_all(b"NIL"),
650                }
651            }
652            #[cfg(feature = "ext_metadata")]
653            CommandBody::SetMetadata {
654                mailbox,
655                entry_values,
656            } => {
657                ctx.write_all(b"SETMETADATA ")?;
658                mailbox.encode_ctx(ctx)?;
659                ctx.write_all(b" (")?;
660                join_serializable(entry_values.as_ref(), b" ", ctx)?;
661                ctx.write_all(b")")
662            }
663            #[cfg(feature = "ext_metadata")]
664            CommandBody::GetMetadata {
665                options,
666                mailbox,
667                entries,
668            } => {
669                ctx.write_all(b"GETMETADATA")?;
670
671                if !options.is_empty() {
672                    ctx.write_all(b" (")?;
673                    join_serializable(options, b" ", ctx)?;
674                    ctx.write_all(b")")?;
675                }
676
677                ctx.write_all(b" ")?;
678                mailbox.encode_ctx(ctx)?;
679
680                ctx.write_all(b" ")?;
681
682                if entries.as_ref().len() == 1 {
683                    entries.as_ref()[0].encode_ctx(ctx)
684                } else {
685                    ctx.write_all(b"(")?;
686                    join_serializable(entries.as_ref(), b" ", ctx)?;
687                    ctx.write_all(b")")
688                }
689            }
690            #[cfg(feature = "ext_namespace")]
691            &CommandBody::Namespace => ctx.write_all(b"NAMESPACE"),
692        }
693    }
694}
695
696#[cfg(feature = "ext_condstore_qresync")]
697impl EncodeIntoContext for FetchModifier {
698    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
699        match self {
700            FetchModifier::ChangedSince(since) => write!(ctx, "CHANGEDSINCE {since}"),
701            FetchModifier::Vanished => write!(ctx, "VANISHED"),
702        }
703    }
704}
705
706#[cfg(feature = "ext_condstore_qresync")]
707impl EncodeIntoContext for StoreModifier {
708    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
709        match self {
710            StoreModifier::UnchangedSince(since) => write!(ctx, "UNCHANGEDSINCE {since}"),
711        }
712    }
713}
714
715#[cfg(feature = "ext_condstore_qresync")]
716impl EncodeIntoContext for SelectParameter {
717    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
718        match self {
719            SelectParameter::CondStore => write!(ctx, "CONDSTORE"),
720            SelectParameter::QResync {
721                uid_validity,
722                mod_sequence_value,
723                known_uids,
724                seq_match_data,
725            } => {
726                write!(ctx, "QRESYNC (")?;
727                uid_validity.encode_ctx(ctx)?;
728                write!(ctx, " ")?;
729                mod_sequence_value.encode_ctx(ctx)?;
730
731                if let Some(known_uids) = known_uids {
732                    write!(ctx, " ")?;
733                    known_uids.encode_ctx(ctx)?;
734                }
735
736                if let Some((known_sequence_set, known_uid_set)) = seq_match_data {
737                    write!(ctx, " (")?;
738                    known_sequence_set.encode_ctx(ctx)?;
739                    write!(ctx, " ")?;
740                    known_uid_set.encode_ctx(ctx)?;
741                    write!(ctx, ")")?;
742                }
743
744                write!(ctx, ")")
745            }
746        }
747    }
748}
749
750impl EncodeIntoContext for AuthMechanism<'_> {
751    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
752        write!(ctx, "{self}")
753    }
754}
755
756impl EncodeIntoContext for AuthenticateData<'_> {
757    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
758        match self {
759            Self::Continue(data) => {
760                let encoded = base64.encode(data.declassify());
761                ctx.write_all(encoded.as_bytes())?;
762                ctx.write_all(b"\r\n")
763            }
764            Self::Cancel => ctx.write_all(b"*\r\n"),
765        }
766    }
767}
768
769impl EncodeIntoContext for AString<'_> {
770    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
771        match self {
772            AString::Atom(atom) => atom.encode_ctx(ctx),
773            AString::String(imap_str) => imap_str.encode_ctx(ctx),
774        }
775    }
776}
777
778impl EncodeIntoContext for Atom<'_> {
779    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
780        ctx.write_all(self.inner().as_bytes())
781    }
782}
783
784impl EncodeIntoContext for AtomExt<'_> {
785    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
786        ctx.write_all(self.inner().as_bytes())
787    }
788}
789
790impl EncodeIntoContext for IString<'_> {
791    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
792        match self {
793            Self::Literal(val) => val.encode_ctx(ctx),
794            Self::Quoted(val) => val.encode_ctx(ctx),
795            #[cfg(feature = "ext_utf8")]
796            Self::QuotedUtf8(val) => val.encode_ctx(ctx),
797        }
798    }
799}
800
801impl EncodeIntoContext for Literal<'_> {
802    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
803        match self.mode() {
804            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
805            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
806        }
807
808        ctx.push_line();
809        ctx.write_all(self.as_ref())?;
810        ctx.push_literal(self.mode());
811
812        Ok(())
813    }
814}
815
816impl EncodeIntoContext for Quoted<'_> {
817    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
818        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
819    }
820}
821
822impl EncodeIntoContext for Mailbox<'_> {
823    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
824        match self {
825            Mailbox::Inbox => ctx.write_all(b"INBOX"),
826            Mailbox::Other(other) => other.encode_ctx(ctx),
827        }
828    }
829}
830
831impl EncodeIntoContext for MailboxOther<'_> {
832    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
833        self.inner().encode_ctx(ctx)
834    }
835}
836
837impl EncodeIntoContext for ListMailbox<'_> {
838    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
839        match self {
840            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
841            ListMailbox::String(istr) => istr.encode_ctx(ctx),
842        }
843    }
844}
845
846impl EncodeIntoContext for ListCharString<'_> {
847    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
848        ctx.write_all(self.as_ref())
849    }
850}
851
852impl EncodeIntoContext for StatusDataItemName {
853    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
854        match self {
855            Self::Messages => ctx.write_all(b"MESSAGES"),
856            Self::Recent => ctx.write_all(b"RECENT"),
857            Self::UidNext => ctx.write_all(b"UIDNEXT"),
858            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
859            Self::Unseen => ctx.write_all(b"UNSEEN"),
860            Self::Deleted => ctx.write_all(b"DELETED"),
861            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
862            #[cfg(feature = "ext_condstore_qresync")]
863            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
864        }
865    }
866}
867
868impl EncodeIntoContext for Flag<'_> {
869    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
870        write!(ctx, "{self}")
871    }
872}
873
874impl EncodeIntoContext for FlagFetch<'_> {
875    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
876        match self {
877            Self::Flag(flag) => flag.encode_ctx(ctx),
878            Self::Recent => ctx.write_all(b"\\Recent"),
879        }
880    }
881}
882
883impl EncodeIntoContext for FlagPerm<'_> {
884    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
885        match self {
886            Self::Flag(flag) => flag.encode_ctx(ctx),
887            Self::Asterisk => ctx.write_all(b"\\*"),
888        }
889    }
890}
891
892impl EncodeIntoContext for DateTime {
893    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
894        self.as_ref().encode_ctx(ctx)
895    }
896}
897
898impl EncodeIntoContext for Charset<'_> {
899    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
900        match self {
901            Charset::Atom(atom) => atom.encode_ctx(ctx),
902            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
903        }
904    }
905}
906
907impl EncodeIntoContext for SearchKey<'_> {
908    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
909        match self {
910            SearchKey::All => ctx.write_all(b"ALL"),
911            SearchKey::Answered => ctx.write_all(b"ANSWERED"),
912            SearchKey::Bcc(astring) => {
913                ctx.write_all(b"BCC ")?;
914                astring.encode_ctx(ctx)
915            }
916            SearchKey::Before(date) => {
917                ctx.write_all(b"BEFORE ")?;
918                date.encode_ctx(ctx)
919            }
920            SearchKey::Body(astring) => {
921                ctx.write_all(b"BODY ")?;
922                astring.encode_ctx(ctx)
923            }
924            SearchKey::Cc(astring) => {
925                ctx.write_all(b"CC ")?;
926                astring.encode_ctx(ctx)
927            }
928            SearchKey::Deleted => ctx.write_all(b"DELETED"),
929            SearchKey::Flagged => ctx.write_all(b"FLAGGED"),
930            SearchKey::From(astring) => {
931                ctx.write_all(b"FROM ")?;
932                astring.encode_ctx(ctx)
933            }
934            SearchKey::Keyword(flag_keyword) => {
935                ctx.write_all(b"KEYWORD ")?;
936                flag_keyword.encode_ctx(ctx)
937            }
938            SearchKey::New => ctx.write_all(b"NEW"),
939            SearchKey::Old => ctx.write_all(b"OLD"),
940            SearchKey::On(date) => {
941                ctx.write_all(b"ON ")?;
942                date.encode_ctx(ctx)
943            }
944            SearchKey::Recent => ctx.write_all(b"RECENT"),
945            SearchKey::Seen => ctx.write_all(b"SEEN"),
946            SearchKey::Since(date) => {
947                ctx.write_all(b"SINCE ")?;
948                date.encode_ctx(ctx)
949            }
950            SearchKey::Subject(astring) => {
951                ctx.write_all(b"SUBJECT ")?;
952                astring.encode_ctx(ctx)
953            }
954            SearchKey::Text(astring) => {
955                ctx.write_all(b"TEXT ")?;
956                astring.encode_ctx(ctx)
957            }
958            SearchKey::To(astring) => {
959                ctx.write_all(b"TO ")?;
960                astring.encode_ctx(ctx)
961            }
962            SearchKey::Unanswered => ctx.write_all(b"UNANSWERED"),
963            SearchKey::Undeleted => ctx.write_all(b"UNDELETED"),
964            SearchKey::Unflagged => ctx.write_all(b"UNFLAGGED"),
965            SearchKey::Unkeyword(flag_keyword) => {
966                ctx.write_all(b"UNKEYWORD ")?;
967                flag_keyword.encode_ctx(ctx)
968            }
969            SearchKey::Unseen => ctx.write_all(b"UNSEEN"),
970            SearchKey::Draft => ctx.write_all(b"DRAFT"),
971            SearchKey::Header(header_fld_name, astring) => {
972                ctx.write_all(b"HEADER ")?;
973                header_fld_name.encode_ctx(ctx)?;
974                ctx.write_all(b" ")?;
975                astring.encode_ctx(ctx)
976            }
977            SearchKey::Larger(number) => write!(ctx, "LARGER {number}"),
978            SearchKey::Not(search_key) => {
979                ctx.write_all(b"NOT ")?;
980                search_key.encode_ctx(ctx)
981            }
982            SearchKey::Or(search_key_a, search_key_b) => {
983                ctx.write_all(b"OR ")?;
984                search_key_a.encode_ctx(ctx)?;
985                ctx.write_all(b" ")?;
986                search_key_b.encode_ctx(ctx)
987            }
988            SearchKey::SentBefore(date) => {
989                ctx.write_all(b"SENTBEFORE ")?;
990                date.encode_ctx(ctx)
991            }
992            SearchKey::SentOn(date) => {
993                ctx.write_all(b"SENTON ")?;
994                date.encode_ctx(ctx)
995            }
996            SearchKey::SentSince(date) => {
997                ctx.write_all(b"SENTSINCE ")?;
998                date.encode_ctx(ctx)
999            }
1000            SearchKey::Smaller(number) => write!(ctx, "SMALLER {number}"),
1001            SearchKey::Uid(sequence_set) => {
1002                ctx.write_all(b"UID ")?;
1003                sequence_set.encode_ctx(ctx)
1004            }
1005            SearchKey::Undraft => ctx.write_all(b"UNDRAFT"),
1006            #[cfg(feature = "ext_condstore_qresync")]
1007            SearchKey::ModSequence { entry, modseq } => {
1008                ctx.write_all(b"MODSEQ")?;
1009                if let Some((attribute_flag, entry_type_req)) = entry {
1010                    write!(ctx, " \"/flags/{attribute_flag}\"")?;
1011                    write!(ctx, " {entry_type_req}")?;
1012                }
1013                ctx.write_all(b" ")?;
1014                modseq.encode_ctx(ctx)
1015            }
1016            SearchKey::SequenceSet(sequence_set) => sequence_set.encode_ctx(ctx),
1017            SearchKey::And(search_keys) => {
1018                ctx.write_all(b"(")?;
1019                join_serializable(search_keys.as_ref(), b" ", ctx)?;
1020                ctx.write_all(b")")
1021            }
1022        }
1023    }
1024}
1025
1026impl EncodeIntoContext for SequenceSet {
1027    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1028        #[cfg(feature = "quirk_always_normalize_sequence_sets")]
1029        let mut set = self.clone();
1030        #[cfg(feature = "quirk_always_normalize_sequence_sets")]
1031        set.normalize();
1032
1033        #[cfg(not(feature = "quirk_always_normalize_sequence_sets"))]
1034        let set = self;
1035
1036        join_serializable(set.0.as_ref(), b",", ctx)
1037    }
1038}
1039
1040impl EncodeIntoContext for Sequence {
1041    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1042        #[cfg(feature = "quirk_always_normalize_sequence_sets")]
1043        let mut seq = self.clone();
1044        #[cfg(feature = "quirk_always_normalize_sequence_sets")]
1045        seq.normalize();
1046
1047        #[cfg(not(feature = "quirk_always_normalize_sequence_sets"))]
1048        let seq = self;
1049
1050        match seq {
1051            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
1052            Sequence::Range(from, to) => {
1053                from.encode_ctx(ctx)?;
1054                ctx.write_all(b":")?;
1055                to.encode_ctx(ctx)
1056            }
1057        }
1058    }
1059}
1060
1061impl EncodeIntoContext for SeqOrUid {
1062    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1063        match self {
1064            SeqOrUid::Value(number) => write!(ctx, "{number}"),
1065            SeqOrUid::Asterisk => ctx.write_all(b"*"),
1066        }
1067    }
1068}
1069
1070impl EncodeIntoContext for NaiveDate {
1071    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1072        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
1073    }
1074}
1075
1076impl EncodeIntoContext for MacroOrMessageDataItemNames<'_> {
1077    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1078        match self {
1079            Self::Macro(m) => m.encode_ctx(ctx),
1080            Self::MessageDataItemNames(item_names) => {
1081                if item_names.len() == 1 {
1082                    item_names[0].encode_ctx(ctx)
1083                } else {
1084                    ctx.write_all(b"(")?;
1085                    join_serializable(item_names.as_slice(), b" ", ctx)?;
1086                    ctx.write_all(b")")
1087                }
1088            }
1089        }
1090    }
1091}
1092
1093impl EncodeIntoContext for Macro {
1094    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1095        write!(ctx, "{self}")
1096    }
1097}
1098
1099impl EncodeIntoContext for MessageDataItemName<'_> {
1100    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1101        match self {
1102            Self::Body => ctx.write_all(b"BODY"),
1103            Self::BodyExt {
1104                section,
1105                partial,
1106                peek,
1107            } => {
1108                if *peek {
1109                    ctx.write_all(b"BODY.PEEK[")?;
1110                } else {
1111                    ctx.write_all(b"BODY[")?;
1112                }
1113                if let Some(section) = section {
1114                    section.encode_ctx(ctx)?;
1115                }
1116                ctx.write_all(b"]")?;
1117                if let Some((a, b)) = partial {
1118                    write!(ctx, "<{a}.{b}>")?;
1119                }
1120
1121                Ok(())
1122            }
1123            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
1124            Self::Envelope => ctx.write_all(b"ENVELOPE"),
1125            Self::Flags => ctx.write_all(b"FLAGS"),
1126            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
1127            Self::Rfc822 => ctx.write_all(b"RFC822"),
1128            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
1129            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
1130            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
1131            Self::Uid => ctx.write_all(b"UID"),
1132            MessageDataItemName::Binary {
1133                section,
1134                partial,
1135                peek,
1136            } => {
1137                ctx.write_all(b"BINARY")?;
1138                if *peek {
1139                    ctx.write_all(b".PEEK")?;
1140                }
1141
1142                ctx.write_all(b"[")?;
1143                join_serializable(section, b".", ctx)?;
1144                ctx.write_all(b"]")?;
1145
1146                if let Some((a, b)) = partial {
1147                    ctx.write_all(b"<")?;
1148                    a.encode_ctx(ctx)?;
1149                    ctx.write_all(b".")?;
1150                    b.encode_ctx(ctx)?;
1151                    ctx.write_all(b">")?;
1152                }
1153
1154                Ok(())
1155            }
1156            MessageDataItemName::BinarySize { section } => {
1157                ctx.write_all(b"BINARY.SIZE")?;
1158
1159                ctx.write_all(b"[")?;
1160                join_serializable(section, b".", ctx)?;
1161                ctx.write_all(b"]")
1162            }
1163            #[cfg(feature = "ext_condstore_qresync")]
1164            MessageDataItemName::ModSeq => ctx.write_all(b"MODSEQ"),
1165        }
1166    }
1167}
1168
1169impl EncodeIntoContext for Section<'_> {
1170    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1171        match self {
1172            Section::Part(part) => part.encode_ctx(ctx),
1173            Section::Header(maybe_part) => match maybe_part {
1174                Some(part) => {
1175                    part.encode_ctx(ctx)?;
1176                    ctx.write_all(b".HEADER")
1177                }
1178                None => ctx.write_all(b"HEADER"),
1179            },
1180            Section::HeaderFields(maybe_part, header_list) => {
1181                match maybe_part {
1182                    Some(part) => {
1183                        part.encode_ctx(ctx)?;
1184                        ctx.write_all(b".HEADER.FIELDS (")?;
1185                    }
1186                    None => ctx.write_all(b"HEADER.FIELDS (")?,
1187                };
1188                join_serializable(header_list.as_ref(), b" ", ctx)?;
1189                ctx.write_all(b")")
1190            }
1191            Section::HeaderFieldsNot(maybe_part, header_list) => {
1192                match maybe_part {
1193                    Some(part) => {
1194                        part.encode_ctx(ctx)?;
1195                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
1196                    }
1197                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
1198                };
1199                join_serializable(header_list.as_ref(), b" ", ctx)?;
1200                ctx.write_all(b")")
1201            }
1202            Section::Text(maybe_part) => match maybe_part {
1203                Some(part) => {
1204                    part.encode_ctx(ctx)?;
1205                    ctx.write_all(b".TEXT")
1206                }
1207                None => ctx.write_all(b"TEXT"),
1208            },
1209            Section::Mime(part) => {
1210                part.encode_ctx(ctx)?;
1211                ctx.write_all(b".MIME")
1212            }
1213        }
1214    }
1215}
1216
1217impl EncodeIntoContext for Part {
1218    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1219        join_serializable(self.0.as_ref(), b".", ctx)
1220    }
1221}
1222
1223impl EncodeIntoContext for NonZeroU32 {
1224    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1225        write!(ctx, "{self}")
1226    }
1227}
1228
1229#[cfg(feature = "ext_condstore_qresync")]
1230impl EncodeIntoContext for NonZeroU64 {
1231    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1232        write!(ctx, "{self}")
1233    }
1234}
1235
1236impl EncodeIntoContext for Capability<'_> {
1237    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1238        write!(ctx, "{self}")
1239    }
1240}
1241
1242// ----- Responses ---------------------------------------------------------------------------------
1243
1244impl EncodeIntoContext for Response<'_> {
1245    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1246        match self {
1247            Response::Status(status) => status.encode_ctx(ctx),
1248            Response::Data(data) => data.encode_ctx(ctx),
1249            Response::CommandContinuationRequest(continue_request) => {
1250                continue_request.encode_ctx(ctx)
1251            }
1252        }
1253    }
1254}
1255
1256impl EncodeIntoContext for Greeting<'_> {
1257    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1258        ctx.write_all(b"* ")?;
1259        self.kind.encode_ctx(ctx)?;
1260        ctx.write_all(b" ")?;
1261
1262        if let Some(ref code) = self.code {
1263            ctx.write_all(b"[")?;
1264            code.encode_ctx(ctx)?;
1265            ctx.write_all(b"] ")?;
1266        }
1267
1268        self.text.encode_ctx(ctx)?;
1269        ctx.write_all(b"\r\n")
1270    }
1271}
1272
1273impl EncodeIntoContext for GreetingKind {
1274    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1275        match self {
1276            GreetingKind::Ok => ctx.write_all(b"OK"),
1277            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
1278            GreetingKind::Bye => ctx.write_all(b"BYE"),
1279        }
1280    }
1281}
1282
1283impl EncodeIntoContext for Status<'_> {
1284    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1285        fn format_status(
1286            tag: Option<&Tag>,
1287            status: &str,
1288            code: &Option<Code>,
1289            comment: &Text,
1290            ctx: &mut EncodeContext,
1291        ) -> std::io::Result<()> {
1292            match tag {
1293                Some(tag) => tag.encode_ctx(ctx)?,
1294                None => ctx.write_all(b"*")?,
1295            }
1296            ctx.write_all(b" ")?;
1297            ctx.write_all(status.as_bytes())?;
1298            ctx.write_all(b" ")?;
1299            if let Some(code) = code {
1300                ctx.write_all(b"[")?;
1301                code.encode_ctx(ctx)?;
1302                ctx.write_all(b"] ")?;
1303            }
1304            comment.encode_ctx(ctx)?;
1305            ctx.write_all(b"\r\n")
1306        }
1307
1308        match self {
1309            Self::Untagged(StatusBody { kind, code, text }) => match kind {
1310                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
1311                StatusKind::No => format_status(None, "NO", code, text, ctx),
1312                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
1313            },
1314            Self::Tagged(Tagged {
1315                tag,
1316                body: StatusBody { kind, code, text },
1317            }) => match kind {
1318                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
1319                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
1320                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
1321            },
1322            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
1323        }
1324    }
1325}
1326
1327impl EncodeIntoContext for Code<'_> {
1328    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1329        match self {
1330            Code::Alert => ctx.write_all(b"ALERT"),
1331            Code::BadCharset { allowed } => {
1332                if allowed.is_empty() {
1333                    ctx.write_all(b"BADCHARSET")
1334                } else {
1335                    ctx.write_all(b"BADCHARSET (")?;
1336                    join_serializable(allowed, b" ", ctx)?;
1337                    ctx.write_all(b")")
1338                }
1339            }
1340            Code::Capability(caps) => {
1341                ctx.write_all(b"CAPABILITY ")?;
1342                join_serializable(caps.as_ref(), b" ", ctx)
1343            }
1344            Code::Parse => ctx.write_all(b"PARSE"),
1345            Code::PermanentFlags(flags) => {
1346                ctx.write_all(b"PERMANENTFLAGS (")?;
1347                join_serializable(flags, b" ", ctx)?;
1348                ctx.write_all(b")")
1349            }
1350            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
1351            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
1352            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
1353            Code::UidNext(next) => {
1354                ctx.write_all(b"UIDNEXT ")?;
1355                next.encode_ctx(ctx)
1356            }
1357            Code::UidValidity(validity) => {
1358                ctx.write_all(b"UIDVALIDITY ")?;
1359                validity.encode_ctx(ctx)
1360            }
1361            Code::Unseen(seq) => {
1362                ctx.write_all(b"UNSEEN ")?;
1363                seq.encode_ctx(ctx)
1364            }
1365            // RFC 2221
1366            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1367            Code::Referral(url) => {
1368                ctx.write_all(b"REFERRAL ")?;
1369                ctx.write_all(url.as_bytes())
1370            }
1371            // RFC 4551
1372            #[cfg(feature = "ext_condstore_qresync")]
1373            Code::HighestModSeq(modseq) => {
1374                ctx.write_all(b"HIGHESTMODSEQ ")?;
1375                modseq.encode_ctx(ctx)
1376            }
1377            #[cfg(feature = "ext_condstore_qresync")]
1378            Code::NoModSeq => ctx.write_all(b"NOMODSEQ"),
1379            #[cfg(feature = "ext_condstore_qresync")]
1380            Code::Modified(sequence_set) => {
1381                ctx.write_all(b"MODIFIED ")?;
1382                sequence_set.encode_ctx(ctx)
1383            }
1384            #[cfg(feature = "ext_condstore_qresync")]
1385            Code::Closed => ctx.write_all(b"CLOSED"),
1386            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
1387            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
1388            Code::TooBig => ctx.write_all(b"TOOBIG"),
1389            #[cfg(feature = "ext_metadata")]
1390            Code::Metadata(code) => {
1391                ctx.write_all(b"METADATA ")?;
1392                code.encode_ctx(ctx)
1393            }
1394            Code::UnknownCte => ctx.write_all(b"UNKNOWN-CTE"),
1395            Code::AppendUid { uid_validity, uid } => {
1396                ctx.write_all(b"APPENDUID ")?;
1397                uid_validity.encode_ctx(ctx)?;
1398                ctx.write_all(b" ")?;
1399                uid.encode_ctx(ctx)
1400            }
1401            Code::CopyUid {
1402                uid_validity,
1403                source,
1404                destination,
1405            } => {
1406                ctx.write_all(b"COPYUID ")?;
1407                uid_validity.encode_ctx(ctx)?;
1408                ctx.write_all(b" ")?;
1409                source.encode_ctx(ctx)?;
1410                ctx.write_all(b" ")?;
1411                destination.encode_ctx(ctx)
1412            }
1413            Code::UidNotSticky => ctx.write_all(b"UIDNOTSTICKY"),
1414            Code::Other(unknown) => unknown.encode_ctx(ctx),
1415        }
1416    }
1417}
1418
1419impl EncodeIntoContext for CodeOther<'_> {
1420    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1421        ctx.write_all(self.inner())
1422    }
1423}
1424
1425impl EncodeIntoContext for Text<'_> {
1426    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1427        ctx.write_all(self.inner().as_bytes())
1428    }
1429}
1430
1431impl EncodeIntoContext for Data<'_> {
1432    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1433        match self {
1434            Data::Capability(caps) => {
1435                ctx.write_all(b"* CAPABILITY ")?;
1436                join_serializable(caps.as_ref(), b" ", ctx)?;
1437            }
1438            Data::List {
1439                items,
1440                delimiter,
1441                mailbox,
1442            } => {
1443                ctx.write_all(b"* LIST (")?;
1444                join_serializable(items, b" ", ctx)?;
1445                ctx.write_all(b") ")?;
1446
1447                if let Some(delimiter) = delimiter {
1448                    ctx.write_all(b"\"")?;
1449                    delimiter.encode_ctx(ctx)?;
1450                    ctx.write_all(b"\"")?;
1451                } else {
1452                    ctx.write_all(b"NIL")?;
1453                }
1454                ctx.write_all(b" ")?;
1455                mailbox.encode_ctx(ctx)?;
1456            }
1457            Data::Lsub {
1458                items,
1459                delimiter,
1460                mailbox,
1461            } => {
1462                ctx.write_all(b"* LSUB (")?;
1463                join_serializable(items, b" ", ctx)?;
1464                ctx.write_all(b") ")?;
1465
1466                if let Some(delimiter) = delimiter {
1467                    ctx.write_all(b"\"")?;
1468                    delimiter.encode_ctx(ctx)?;
1469                    ctx.write_all(b"\"")?;
1470                } else {
1471                    ctx.write_all(b"NIL")?;
1472                }
1473                ctx.write_all(b" ")?;
1474                mailbox.encode_ctx(ctx)?;
1475            }
1476            Data::Status { mailbox, items } => {
1477                ctx.write_all(b"* STATUS ")?;
1478                mailbox.encode_ctx(ctx)?;
1479                ctx.write_all(b" (")?;
1480                join_serializable(items, b" ", ctx)?;
1481                ctx.write_all(b")")?;
1482            }
1483            // TODO: Exclude pattern via cfg?
1484            #[cfg(not(feature = "ext_condstore_qresync"))]
1485            Data::Search(seqs) => {
1486                if seqs.is_empty() {
1487                    ctx.write_all(b"* SEARCH")?;
1488                } else {
1489                    ctx.write_all(b"* SEARCH ")?;
1490                    join_serializable(seqs, b" ", ctx)?;
1491                }
1492            }
1493            // TODO: Exclude pattern via cfg?
1494            #[cfg(feature = "ext_condstore_qresync")]
1495            Data::Search(seqs, modseq) => {
1496                if seqs.is_empty() {
1497                    ctx.write_all(b"* SEARCH")?;
1498                } else {
1499                    ctx.write_all(b"* SEARCH ")?;
1500                    join_serializable(seqs, b" ", ctx)?;
1501                }
1502
1503                if let Some(modseq) = modseq {
1504                    ctx.write_all(b" (MODSEQ ")?;
1505                    modseq.encode_ctx(ctx)?;
1506                    ctx.write_all(b")")?;
1507                }
1508            }
1509            // TODO: Exclude pattern via cfg?
1510            #[cfg(not(feature = "ext_condstore_qresync"))]
1511            Data::Sort(seqs) => {
1512                if seqs.is_empty() {
1513                    ctx.write_all(b"* SORT")?;
1514                } else {
1515                    ctx.write_all(b"* SORT ")?;
1516                    join_serializable(seqs, b" ", ctx)?;
1517                }
1518            }
1519            // TODO: Exclude pattern via cfg?
1520            #[cfg(feature = "ext_condstore_qresync")]
1521            Data::Sort(seqs, modseq) => {
1522                if seqs.is_empty() {
1523                    ctx.write_all(b"* SORT")?;
1524                } else {
1525                    ctx.write_all(b"* SORT ")?;
1526                    join_serializable(seqs, b" ", ctx)?;
1527                }
1528
1529                if let Some(modseq) = modseq {
1530                    ctx.write_all(b" (MODSEQ ")?;
1531                    modseq.encode_ctx(ctx)?;
1532                    ctx.write_all(b")")?;
1533                }
1534            }
1535            Data::Thread(threads) => {
1536                if threads.is_empty() {
1537                    ctx.write_all(b"* THREAD")?;
1538                } else {
1539                    ctx.write_all(b"* THREAD ")?;
1540                    for thread in threads {
1541                        thread.encode_ctx(ctx)?;
1542                    }
1543                }
1544            }
1545            Data::Flags(flags) => {
1546                ctx.write_all(b"* FLAGS (")?;
1547                join_serializable(flags, b" ", ctx)?;
1548                ctx.write_all(b")")?;
1549            }
1550            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
1551            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
1552            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
1553            Data::Fetch { seq, items } => {
1554                write!(ctx, "* {seq} FETCH (")?;
1555                join_serializable(items.as_ref(), b" ", ctx)?;
1556                ctx.write_all(b")")?;
1557            }
1558            Data::Enabled { capabilities } => {
1559                write!(ctx, "* ENABLED")?;
1560
1561                for cap in capabilities {
1562                    ctx.write_all(b" ")?;
1563                    cap.encode_ctx(ctx)?;
1564                }
1565            }
1566            Data::Quota { root, quotas } => {
1567                ctx.write_all(b"* QUOTA ")?;
1568                root.encode_ctx(ctx)?;
1569                ctx.write_all(b" (")?;
1570                join_serializable(quotas.as_ref(), b" ", ctx)?;
1571                ctx.write_all(b")")?;
1572            }
1573            Data::QuotaRoot { mailbox, roots } => {
1574                ctx.write_all(b"* QUOTAROOT ")?;
1575                mailbox.encode_ctx(ctx)?;
1576                for root in roots {
1577                    ctx.write_all(b" ")?;
1578                    root.encode_ctx(ctx)?;
1579                }
1580            }
1581            #[cfg(feature = "ext_id")]
1582            Data::Id { parameters } => {
1583                ctx.write_all(b"* ID ")?;
1584
1585                match parameters {
1586                    Some(parameters) => {
1587                        if let Some((first, tail)) = parameters.split_first() {
1588                            ctx.write_all(b"(")?;
1589
1590                            first.0.encode_ctx(ctx)?;
1591                            ctx.write_all(b" ")?;
1592                            first.1.encode_ctx(ctx)?;
1593
1594                            for parameter in tail {
1595                                ctx.write_all(b" ")?;
1596                                parameter.0.encode_ctx(ctx)?;
1597                                ctx.write_all(b" ")?;
1598                                parameter.1.encode_ctx(ctx)?;
1599                            }
1600
1601                            ctx.write_all(b")")?;
1602                        } else {
1603                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
1604                            {
1605                                ctx.write_all(b"()")?;
1606                            }
1607                            #[cfg(feature = "quirk_id_empty_to_nil")]
1608                            {
1609                                ctx.write_all(b"NIL")?;
1610                            }
1611                        }
1612                    }
1613                    None => {
1614                        ctx.write_all(b"NIL")?;
1615                    }
1616                }
1617            }
1618            #[cfg(feature = "ext_metadata")]
1619            Data::Metadata { mailbox, items } => {
1620                ctx.write_all(b"* METADATA ")?;
1621                mailbox.encode_ctx(ctx)?;
1622                ctx.write_all(b" ")?;
1623                items.encode_ctx(ctx)?;
1624            }
1625            #[cfg(feature = "ext_condstore_qresync")]
1626            Data::Vanished {
1627                earlier,
1628                known_uids,
1629            } => {
1630                ctx.write_all(b"* VANISHED")?;
1631                if *earlier {
1632                    ctx.write_all(b" (EARLIER)")?;
1633                }
1634                ctx.write_all(b" ")?;
1635                known_uids.encode_ctx(ctx)?;
1636            }
1637            #[cfg(feature = "ext_namespace")]
1638            Data::Namespace {
1639                personal,
1640                other,
1641                shared,
1642            } => {
1643                ctx.write_all(b"* NAMESPACE ")?;
1644                encode_namespaces(ctx, personal)?;
1645                ctx.write_all(b" ")?;
1646                encode_namespaces(ctx, other)?;
1647                ctx.write_all(b" ")?;
1648                encode_namespaces(ctx, shared)?;
1649            }
1650        }
1651
1652        ctx.write_all(b"\r\n")
1653    }
1654}
1655
1656impl EncodeIntoContext for FlagNameAttribute<'_> {
1657    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1658        write!(ctx, "{self}")
1659    }
1660}
1661
1662impl EncodeIntoContext for QuotedChar {
1663    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1664        match self.inner() {
1665            '\\' => ctx.write_all(b"\\\\"),
1666            '"' => ctx.write_all(b"\\\""),
1667            other => ctx.write_all(&[other as u8]),
1668        }
1669    }
1670}
1671
1672impl EncodeIntoContext for StatusDataItem {
1673    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1674        match self {
1675            Self::Messages(count) => {
1676                ctx.write_all(b"MESSAGES ")?;
1677                count.encode_ctx(ctx)
1678            }
1679            Self::Recent(count) => {
1680                ctx.write_all(b"RECENT ")?;
1681                count.encode_ctx(ctx)
1682            }
1683            Self::UidNext(next) => {
1684                ctx.write_all(b"UIDNEXT ")?;
1685                next.encode_ctx(ctx)
1686            }
1687            Self::UidValidity(identifier) => {
1688                ctx.write_all(b"UIDVALIDITY ")?;
1689                identifier.encode_ctx(ctx)
1690            }
1691            Self::Unseen(count) => {
1692                ctx.write_all(b"UNSEEN ")?;
1693                count.encode_ctx(ctx)
1694            }
1695            Self::Deleted(count) => {
1696                ctx.write_all(b"DELETED ")?;
1697                count.encode_ctx(ctx)
1698            }
1699            Self::DeletedStorage(count) => {
1700                ctx.write_all(b"DELETED-STORAGE ")?;
1701                count.encode_ctx(ctx)
1702            }
1703            #[cfg(feature = "ext_condstore_qresync")]
1704            Self::HighestModSeq(value) => {
1705                ctx.write_all(b"HIGHESTMODSEQ ")?;
1706                value.encode_ctx(ctx)
1707            }
1708        }
1709    }
1710}
1711
1712impl EncodeIntoContext for MessageDataItem<'_> {
1713    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1714        match self {
1715            Self::BodyExt {
1716                section,
1717                origin,
1718                data,
1719            } => {
1720                ctx.write_all(b"BODY[")?;
1721                if let Some(section) = section {
1722                    section.encode_ctx(ctx)?;
1723                }
1724                ctx.write_all(b"]")?;
1725                if let Some(origin) = origin {
1726                    write!(ctx, "<{origin}>")?;
1727                }
1728                ctx.write_all(b" ")?;
1729                data.encode_ctx(ctx)
1730            }
1731            // FIXME: do not return body-ext-1part and body-ext-mpart here
1732            Self::Body(body) => {
1733                ctx.write_all(b"BODY ")?;
1734                body.encode_ctx(ctx)
1735            }
1736            Self::BodyStructure(body) => {
1737                ctx.write_all(b"BODYSTRUCTURE ")?;
1738                body.encode_ctx(ctx)
1739            }
1740            Self::Envelope(envelope) => {
1741                ctx.write_all(b"ENVELOPE ")?;
1742                envelope.encode_ctx(ctx)
1743            }
1744            Self::Flags(flags) => {
1745                ctx.write_all(b"FLAGS (")?;
1746                join_serializable(flags, b" ", ctx)?;
1747                ctx.write_all(b")")
1748            }
1749            Self::InternalDate(datetime) => {
1750                ctx.write_all(b"INTERNALDATE ")?;
1751                datetime.encode_ctx(ctx)
1752            }
1753            Self::Rfc822(nstring) => {
1754                ctx.write_all(b"RFC822 ")?;
1755                nstring.encode_ctx(ctx)
1756            }
1757            Self::Rfc822Header(nstring) => {
1758                ctx.write_all(b"RFC822.HEADER ")?;
1759                nstring.encode_ctx(ctx)
1760            }
1761            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
1762            Self::Rfc822Text(nstring) => {
1763                ctx.write_all(b"RFC822.TEXT ")?;
1764                nstring.encode_ctx(ctx)
1765            }
1766            Self::Uid(uid) => write!(ctx, "UID {uid}"),
1767            Self::Binary { section, value } => {
1768                ctx.write_all(b"BINARY[")?;
1769                join_serializable(section, b".", ctx)?;
1770                ctx.write_all(b"] ")?;
1771                value.encode_ctx(ctx)
1772            }
1773            Self::BinarySize { section, size } => {
1774                ctx.write_all(b"BINARY.SIZE[")?;
1775                join_serializable(section, b".", ctx)?;
1776                ctx.write_all(b"] ")?;
1777                size.encode_ctx(ctx)
1778            }
1779            #[cfg(feature = "ext_condstore_qresync")]
1780            Self::ModSeq(value) => write!(ctx, "MODSEQ {value}"),
1781        }
1782    }
1783}
1784
1785impl EncodeIntoContext for NString<'_> {
1786    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1787        match &self.0 {
1788            Some(imap_str) => imap_str.encode_ctx(ctx),
1789            None => ctx.write_all(b"NIL"),
1790        }
1791    }
1792}
1793
1794impl EncodeIntoContext for NString8<'_> {
1795    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1796        match self {
1797            NString8::NString(nstring) => nstring.encode_ctx(ctx),
1798            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
1799        }
1800    }
1801}
1802
1803impl EncodeIntoContext for BodyStructure<'_> {
1804    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1805        ctx.write_all(b"(")?;
1806        match self {
1807            BodyStructure::Single {
1808                body,
1809                extension_data: extension,
1810            } => {
1811                body.encode_ctx(ctx)?;
1812                if let Some(extension) = extension {
1813                    ctx.write_all(b" ")?;
1814                    extension.encode_ctx(ctx)?;
1815                }
1816            }
1817            BodyStructure::Multi {
1818                bodies,
1819                subtype,
1820                extension_data,
1821            } => {
1822                for body in bodies.as_ref() {
1823                    body.encode_ctx(ctx)?;
1824                }
1825                ctx.write_all(b" ")?;
1826                subtype.encode_ctx(ctx)?;
1827
1828                if let Some(extension) = extension_data {
1829                    ctx.write_all(b" ")?;
1830                    extension.encode_ctx(ctx)?;
1831                }
1832            }
1833        }
1834        ctx.write_all(b")")
1835    }
1836}
1837
1838impl EncodeIntoContext for Body<'_> {
1839    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1840        match self.specific {
1841            SpecificFields::Basic {
1842                r#type: ref type_,
1843                ref subtype,
1844            } => {
1845                type_.encode_ctx(ctx)?;
1846                ctx.write_all(b" ")?;
1847                subtype.encode_ctx(ctx)?;
1848                ctx.write_all(b" ")?;
1849                self.basic.encode_ctx(ctx)
1850            }
1851            SpecificFields::Message {
1852                ref envelope,
1853                ref body_structure,
1854                number_of_lines,
1855            } => {
1856                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
1857                self.basic.encode_ctx(ctx)?;
1858                ctx.write_all(b" ")?;
1859                envelope.encode_ctx(ctx)?;
1860                ctx.write_all(b" ")?;
1861                body_structure.encode_ctx(ctx)?;
1862                ctx.write_all(b" ")?;
1863                write!(ctx, "{number_of_lines}")
1864            }
1865            SpecificFields::Text {
1866                ref subtype,
1867                number_of_lines,
1868            } => {
1869                ctx.write_all(b"\"TEXT\" ")?;
1870                subtype.encode_ctx(ctx)?;
1871                ctx.write_all(b" ")?;
1872                self.basic.encode_ctx(ctx)?;
1873                ctx.write_all(b" ")?;
1874                write!(ctx, "{number_of_lines}")
1875            }
1876        }
1877    }
1878}
1879
1880impl EncodeIntoContext for BasicFields<'_> {
1881    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1882        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
1883        ctx.write_all(b" ")?;
1884        self.id.encode_ctx(ctx)?;
1885        ctx.write_all(b" ")?;
1886        self.description.encode_ctx(ctx)?;
1887        ctx.write_all(b" ")?;
1888        self.content_transfer_encoding.encode_ctx(ctx)?;
1889        ctx.write_all(b" ")?;
1890        write!(ctx, "{}", self.size)
1891    }
1892}
1893
1894impl EncodeIntoContext for Envelope<'_> {
1895    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1896        ctx.write_all(b"(")?;
1897        self.date.encode_ctx(ctx)?;
1898        ctx.write_all(b" ")?;
1899        self.subject.encode_ctx(ctx)?;
1900        ctx.write_all(b" ")?;
1901        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
1902        ctx.write_all(b" ")?;
1903        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
1904        ctx.write_all(b" ")?;
1905        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
1906        ctx.write_all(b" ")?;
1907        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
1908        ctx.write_all(b" ")?;
1909        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
1910        ctx.write_all(b" ")?;
1911        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
1912        ctx.write_all(b" ")?;
1913        self.in_reply_to.encode_ctx(ctx)?;
1914        ctx.write_all(b" ")?;
1915        self.message_id.encode_ctx(ctx)?;
1916        ctx.write_all(b")")
1917    }
1918}
1919
1920impl EncodeIntoContext for Address<'_> {
1921    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1922        ctx.write_all(b"(")?;
1923        self.name.encode_ctx(ctx)?;
1924        ctx.write_all(b" ")?;
1925        self.adl.encode_ctx(ctx)?;
1926        ctx.write_all(b" ")?;
1927        self.mailbox.encode_ctx(ctx)?;
1928        ctx.write_all(b" ")?;
1929        self.host.encode_ctx(ctx)?;
1930        ctx.write_all(b")")?;
1931
1932        Ok(())
1933    }
1934}
1935
1936impl EncodeIntoContext for SinglePartExtensionData<'_> {
1937    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1938        self.md5.encode_ctx(ctx)?;
1939
1940        if let Some(disposition) = &self.tail {
1941            ctx.write_all(b" ")?;
1942            disposition.encode_ctx(ctx)?;
1943        }
1944
1945        Ok(())
1946    }
1947}
1948
1949impl EncodeIntoContext for MultiPartExtensionData<'_> {
1950    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1951        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
1952
1953        if let Some(disposition) = &self.tail {
1954            ctx.write_all(b" ")?;
1955            disposition.encode_ctx(ctx)?;
1956        }
1957
1958        Ok(())
1959    }
1960}
1961
1962impl EncodeIntoContext for Disposition<'_> {
1963    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1964        match &self.disposition {
1965            Some((s, param)) => {
1966                ctx.write_all(b"(")?;
1967                s.encode_ctx(ctx)?;
1968                ctx.write_all(b" ")?;
1969                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
1970                ctx.write_all(b")")?;
1971            }
1972            None => ctx.write_all(b"NIL")?,
1973        }
1974
1975        if let Some(language) = &self.tail {
1976            ctx.write_all(b" ")?;
1977            language.encode_ctx(ctx)?;
1978        }
1979
1980        Ok(())
1981    }
1982}
1983
1984impl EncodeIntoContext for Language<'_> {
1985    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1986        List1OrNil(&self.language, b" ").encode_ctx(ctx)?;
1987
1988        if let Some(location) = &self.tail {
1989            ctx.write_all(b" ")?;
1990            location.encode_ctx(ctx)?;
1991        }
1992
1993        Ok(())
1994    }
1995}
1996
1997impl EncodeIntoContext for Location<'_> {
1998    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1999        self.location.encode_ctx(ctx)?;
2000
2001        for body_extension in &self.extensions {
2002            ctx.write_all(b" ")?;
2003            body_extension.encode_ctx(ctx)?;
2004        }
2005
2006        Ok(())
2007    }
2008}
2009
2010impl EncodeIntoContext for BodyExtension<'_> {
2011    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2012        match self {
2013            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
2014            BodyExtension::Number(number) => number.encode_ctx(ctx),
2015            BodyExtension::List(list) => {
2016                ctx.write_all(b"(")?;
2017                join_serializable(list.as_ref(), b" ", ctx)?;
2018                ctx.write_all(b")")
2019            }
2020        }
2021    }
2022}
2023
2024impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
2025    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2026        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
2027    }
2028}
2029
2030impl EncodeIntoContext for CommandContinuationRequest<'_> {
2031    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2032        match self {
2033            Self::Basic(continue_basic) => match continue_basic.code() {
2034                Some(code) => {
2035                    ctx.write_all(b"+ [")?;
2036                    code.encode_ctx(ctx)?;
2037                    ctx.write_all(b"] ")?;
2038                    continue_basic.text().encode_ctx(ctx)?;
2039                    ctx.write_all(b"\r\n")
2040                }
2041                None => {
2042                    ctx.write_all(b"+ ")?;
2043                    continue_basic.text().encode_ctx(ctx)?;
2044                    ctx.write_all(b"\r\n")
2045                }
2046            },
2047            Self::Base64(data) => {
2048                ctx.write_all(b"+ ")?;
2049                ctx.write_all(base64.encode(data).as_bytes())?;
2050                ctx.write_all(b"\r\n")
2051            }
2052        }
2053    }
2054}
2055
2056pub(crate) mod utils {
2057    use std::io::Write;
2058
2059    use super::{EncodeContext, EncodeIntoContext};
2060
2061    pub struct List1OrNil<'a, T>(pub &'a Vec<T>, pub &'a [u8]);
2062
2063    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
2064
2065    pub(crate) fn join_serializable<I: EncodeIntoContext>(
2066        elements: &[I],
2067        sep: &[u8],
2068        ctx: &mut EncodeContext,
2069    ) -> std::io::Result<()> {
2070        if let Some((last, head)) = elements.split_last() {
2071            for item in head {
2072                item.encode_ctx(ctx)?;
2073                ctx.write_all(sep)?;
2074            }
2075
2076            last.encode_ctx(ctx)
2077        } else {
2078            Ok(())
2079        }
2080    }
2081
2082    impl<T> EncodeIntoContext for List1OrNil<'_, T>
2083    where
2084        T: EncodeIntoContext,
2085    {
2086        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2087            if let Some((last, head)) = self.0.split_last() {
2088                ctx.write_all(b"(")?;
2089
2090                for item in head {
2091                    item.encode_ctx(ctx)?;
2092                    ctx.write_all(self.1)?;
2093                }
2094
2095                last.encode_ctx(ctx)?;
2096
2097                ctx.write_all(b")")
2098            } else {
2099                ctx.write_all(b"NIL")
2100            }
2101        }
2102    }
2103
2104    impl<T> EncodeIntoContext for List1AttributeValueOrNil<'_, T>
2105    where
2106        T: EncodeIntoContext,
2107    {
2108        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2109            if let Some((last, head)) = self.0.split_last() {
2110                ctx.write_all(b"(")?;
2111
2112                for (attribute, value) in head {
2113                    attribute.encode_ctx(ctx)?;
2114                    ctx.write_all(b" ")?;
2115                    value.encode_ctx(ctx)?;
2116                    ctx.write_all(b" ")?;
2117                }
2118
2119                let (attribute, value) = last;
2120                attribute.encode_ctx(ctx)?;
2121                ctx.write_all(b" ")?;
2122                value.encode_ctx(ctx)?;
2123
2124                ctx.write_all(b")")
2125            } else {
2126                ctx.write_all(b"NIL")
2127            }
2128        }
2129    }
2130}
2131
2132#[cfg(test)]
2133mod tests {
2134    use std::num::NonZeroU32;
2135
2136    use imap_types::{
2137        auth::AuthMechanism,
2138        command::{Command, CommandBody},
2139        core::{AString, Literal, NString, Vec1},
2140        fetch::MessageDataItem,
2141        response::{Data, Response},
2142        utils::escape_byte_string,
2143    };
2144
2145    use super::*;
2146
2147    #[test]
2148    fn test_api_encoder_usage() {
2149        let cmd = Command::new(
2150            "A",
2151            CommandBody::login(
2152                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2153                "password",
2154            )
2155            .unwrap(),
2156        )
2157        .unwrap();
2158
2159        // Dump.
2160        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2161
2162        // Encoded.
2163        let encoded = CommandCodec::default().encode(&cmd);
2164
2165        let mut out = Vec::new();
2166
2167        for x in encoded {
2168            match x {
2169                Fragment::Line { data } => {
2170                    println!("C: {}", escape_byte_string(&data));
2171                    out.extend_from_slice(&data);
2172                }
2173                Fragment::Literal { data, mode } => {
2174                    match mode {
2175                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
2176                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2177                    }
2178
2179                    println!("C: {}", escape_byte_string(&data));
2180                    out.extend_from_slice(&data);
2181                }
2182            }
2183        }
2184
2185        assert_eq!(got_encoded, out);
2186    }
2187
2188    #[test]
2189    fn test_encode_command() {
2190        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2191            (
2192                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2193                [Fragment::Line {
2194                    data: b"A LOGIN alice pass\r\n".to_vec(),
2195                }]
2196                .as_ref(),
2197            ),
2198            (
2199                Command::new(
2200                    "A",
2201                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2202                )
2203                .unwrap(),
2204                [
2205                    Fragment::Line {
2206                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2207                    },
2208                    Fragment::Literal {
2209                        data: b"\xCA\xFE".to_vec(),
2210                        mode: LiteralMode::Sync,
2211                    },
2212                    Fragment::Line {
2213                        data: b"\r\n".to_vec(),
2214                    },
2215                ]
2216                .as_ref(),
2217            ),
2218            (
2219                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2220                [Fragment::Line {
2221                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2222                }]
2223                .as_ref(),
2224            ),
2225            (
2226                Command::new(
2227                    "A",
2228                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2229                )
2230                .unwrap(),
2231                [Fragment::Line {
2232                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2233                }]
2234                .as_ref(),
2235            ),
2236            (
2237                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2238                [Fragment::Line {
2239                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2240                }]
2241                .as_ref(),
2242            ),
2243            (
2244                Command::new(
2245                    "A",
2246                    CommandBody::authenticate_with_ir(
2247                        AuthMechanism::Plain,
2248                        b"\x00alice\x00pass".as_ref(),
2249                    ),
2250                )
2251                .unwrap(),
2252                [Fragment::Line {
2253                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2254                }]
2255                .as_ref(),
2256            ),
2257        ]);
2258    }
2259
2260    #[test]
2261    fn test_encode_response() {
2262        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2263            (
2264                Response::Data(Data::Fetch {
2265                    seq: NonZeroU32::new(12345).unwrap(),
2266                    items: Vec1::from(MessageDataItem::BodyExt {
2267                        section: None,
2268                        origin: None,
2269                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2270                    }),
2271                }),
2272                [
2273                    Fragment::Line {
2274                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2275                    },
2276                    Fragment::Literal {
2277                        data: b"ABCDE".to_vec(),
2278                        mode: LiteralMode::Sync,
2279                    },
2280                    Fragment::Line {
2281                        data: b")\r\n".to_vec(),
2282                    },
2283                ]
2284                .as_ref(),
2285            ),
2286            (
2287                Response::Data(Data::Fetch {
2288                    seq: NonZeroU32::new(12345).unwrap(),
2289                    items: Vec1::from(MessageDataItem::BodyExt {
2290                        section: None,
2291                        origin: None,
2292                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2293                    }),
2294                }),
2295                [
2296                    Fragment::Line {
2297                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2298                    },
2299                    Fragment::Literal {
2300                        data: b"ABCDE".to_vec(),
2301                        mode: LiteralMode::NonSync,
2302                    },
2303                    Fragment::Line {
2304                        data: b")\r\n".to_vec(),
2305                    },
2306                ]
2307                .as_ref(),
2308            ),
2309        ])
2310    }
2311
2312    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
2313    where
2314        E: Encoder<Message<'a> = M> + Default,
2315        F: AsRef<[Fragment]>,
2316    {
2317        for (i, (obj, actions)) in tests.iter().enumerate() {
2318            println!("# Testing {i}");
2319
2320            let encoder = E::default().encode(obj);
2321            let actions = actions.as_ref();
2322
2323            assert_eq!(encoder.collect::<Vec<_>>(), actions);
2324        }
2325    }
2326}