1#[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
90pub trait Encoder {
94 type Message<'a>;
95
96 fn encode(&self, message: &Self::Message<'_>) -> Encoded;
100}
101
102#[derive(Clone, Debug)]
128pub struct Encoded {
129 items: VecDeque<Fragment>,
130}
131
132impl Encoded {
133 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#[derive(Clone, Debug, Eq, PartialEq)]
158pub enum Fragment {
159 Line { data: Vec<u8> },
161
162 Literal { data: Vec<u8>, mode: LiteralMode },
164}
165
166#[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
255pub(crate) trait EncodeIntoContext {
258 fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
259}
260
261impl 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
275impl 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 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
1242impl 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 #[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 #[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 #[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 #[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 #[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 #[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 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 let got_encoded = CommandCodec::default().encode(&cmd).dump();
2161
2162 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}