1use 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
84pub trait Encoder {
88 type Message<'a>;
89
90 fn encode(&self, message: &Self::Message<'_>) -> Encoded;
94}
95
96#[derive(Clone, Debug)]
122pub struct Encoded {
123 items: Vec<Fragment>,
124}
125
126impl Encoded {
127 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#[derive(Clone, Debug, Eq, PartialEq)]
156pub enum Fragment {
157 Line { data: Vec<u8> },
159
160 Literal { data: Vec<u8>, mode: LiteralMode },
162}
163
164#[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
253pub(crate) trait EncodeIntoContext {
256 fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
257}
258
259impl 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
273impl<'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 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
969impl<'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 #[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 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 let got_encoded = CommandCodec::default().encode(&cmd).dump();
1687
1688 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}