• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

duesee / imap-codec / 9964908275

16 Jul 2024 10:20PM UTC coverage: 92.967% (-0.01%) from 92.981%
9964908275

push

github

duesee
Update imap-codec/Cargo.toml

Co-authored-by: Jakob Schikowski <jakob.schikowski@gmx.de>

11156 of 12000 relevant lines covered (92.97%)

1090.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.49
/imap-codec/src/codec/encode.rs
1
//! # Encoding of messages.
2
//!
3
//! To facilitates handling of literals, [Encoder::encode] returns an instance of [`Encoded`].
4
//! The idea is that the encoder not only "dumps" the final serialization of a message but can be iterated over.
5
//!
6
//! # Example
7
//!
8
//! ```rust
9
//! use imap_codec::{
10
//!     encode::{Encoder, Fragment},
11
//!     imap_types::{
12
//!         command::{Command, CommandBody},
13
//!         core::LiteralMode,
14
//!     },
15
//!     CommandCodec,
16
//! };
17
//!
18
//! let command = Command::new("A1", CommandBody::login("Alice", "Pa²²W0rD").unwrap()).unwrap();
19
//!
20
//! for fragment in CommandCodec::default().encode(&command) {
21
//!     match fragment {
22
//!         Fragment::Line { data } => {
23
//!             // A line that is ready to be send.
24
//!             println!("C: {}", String::from_utf8(data).unwrap());
25
//!         }
26
//!         Fragment::Literal { data, mode } => match mode {
27
//!             LiteralMode::Sync => {
28
//!                 // Wait for a continuation request.
29
//!                 println!("S: + ...")
30
//!             }
31
//!             LiteralMode::NonSync => {
32
//!                 // We don't need to wait for a continuation request
33
//!                 // as the server will also not send it.
34
//!             }
35
//!         },
36
//!     }
37
//! }
38
//! ```
39
//!
40
//! Output of example:
41
//!
42
//! ```imap
43
//! C: A1 LOGIN alice {10}
44
//! S: + ...
45
//! C: Pa²²W0rD
46
//! ```
47

48
use std::{borrow::Borrow, collections::VecDeque, io::Write, num::NonZeroU32};
49

50
use base64::{engine::general_purpose::STANDARD as base64, Engine};
51
use chrono::{DateTime as ChronoDateTime, FixedOffset};
52
use 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, NString8, 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
        Bye, Capability, Code, CodeOther, CommandContinuationRequest, Data, Greeting, GreetingKind,
73
        Response, Status, StatusBody, StatusKind, Tagged,
74
    },
75
    search::SearchKey,
76
    sequence::{SeqOrUid, Sequence, SequenceSet},
77
    status::{StatusDataItem, StatusDataItemName},
78
    utils::escape_quoted,
79
};
80
#[cfg(feature = "serde")]
81
use serde::{Deserialize, Serialize};
82
use utils::{join_serializable, List1AttributeValueOrNil, List1OrNil};
83

84
use crate::{AuthenticateDataCodec, CommandCodec, GreetingCodec, IdleDoneCodec, ResponseCodec};
85

86
/// Encoder.
87
///
88
/// Implemented for types that know how to encode a specific IMAP message. See [implementors](trait.Encoder.html#implementors).
89
pub trait Encoder {
90
    type Message<'a>;
91

92
    /// Encode this message.
93
    ///
94
    /// This will return an [`Encoded`] message.
95
    fn encode(&self, message: &Self::Message<'_>) -> Encoded;
96
}
97

98
/// An encoded message.
99
///
100
/// This struct facilitates the implementation of IMAP client- and server implementations by
101
/// yielding the encoding of a message through [`Fragment`]s. This is required, because the usage of
102
/// literals (and some other types) may change the IMAP message flow. Thus, in many cases, it is an
103
/// error to just "dump" a message and send it over the network.
104
///
105
/// # Example
106
///
107
/// ```rust
108
/// use imap_codec::{
109
///     encode::{Encoder, Fragment},
110
///     imap_types::command::{Command, CommandBody},
111
///     CommandCodec,
112
/// };
113
///
114
/// let cmd = Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap();
115
///
116
/// for fragment in CommandCodec::default().encode(&cmd) {
117
///     match fragment {
118
///         Fragment::Line { data } => {}
119
///         Fragment::Literal { data, mode } => {}
120
///     }
121
/// }
122
/// ```
123
#[derive(Clone, Debug)]
124
pub struct Encoded {
125
    items: VecDeque<Fragment>,
126
}
127

128
impl Encoded {
129
    /// Dump the (remaining) encoded data without being guided by [`Fragment`]s.
130
    pub fn dump(self) -> Vec<u8> {
3,546✔
131
        let mut out = Vec::new();
3,546✔
132

133
        for fragment in self.items {
7,124✔
134
            match fragment {
3,578✔
135
                Fragment::Line { mut data } => out.append(&mut data),
3,562✔
136
                Fragment::Literal { mut data, .. } => out.append(&mut data),
16✔
137
            }
138
        }
139

140
        out
3,546✔
141
    }
3,546✔
142
}
143

144
impl Iterator for Encoded {
145
    type Item = Fragment;
146

147
    fn next(&mut self) -> Option<Self::Item> {
52✔
148
        self.items.pop_front()
52✔
149
    }
52✔
150
}
151

152
/// The intended action of a client or server.
153
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
26✔
154
#[derive(Clone, Debug, Eq, PartialEq)]
155
pub enum Fragment {
156
    /// A line that is ready to be send.
157
    Line { data: Vec<u8> },
158

159
    /// A literal that may require an action before it should be send.
160
    Literal { data: Vec<u8>, mode: LiteralMode },
161
}
162

163
//--------------------------------------------------------------------------------------------------
164

165
#[derive(Clone, Debug, Default, Eq, PartialEq)]
166
pub(crate) struct EncodeContext {
167
    accumulator: Vec<u8>,
168
    items: VecDeque<Fragment>,
169
}
170

171
impl EncodeContext {
172
    pub fn new() -> Self {
4,012✔
173
        Self::default()
4,012✔
174
    }
4,012✔
175

176
    pub fn push_line(&mut self) {
52✔
177
        self.items.push_back(Fragment::Line {
52✔
178
            data: std::mem::take(&mut self.accumulator),
52✔
179
        })
52✔
180
    }
52✔
181

182
    pub fn push_literal(&mut self, mode: LiteralMode) {
52✔
183
        self.items.push_back(Fragment::Literal {
52✔
184
            data: std::mem::take(&mut self.accumulator),
52✔
185
            mode,
52✔
186
        })
52✔
187
    }
52✔
188

189
    pub fn into_items(self) -> VecDeque<Fragment> {
4,012✔
190
        let Self {
4,012✔
191
            accumulator,
4,012✔
192
            mut items,
4,012✔
193
        } = self;
4,012✔
194

4,012✔
195
        if !accumulator.is_empty() {
4,012✔
196
            items.push_back(Fragment::Line { data: accumulator });
4,012✔
197
        }
4,012✔
198

199
        items
4,012✔
200
    }
4,012✔
201

202
    #[cfg(test)]
203
    pub(crate) fn dump(self) -> Vec<u8> {
448✔
204
        let mut out = Vec::new();
448✔
205

206
        for item in self.into_items() {
504✔
207
            match item {
504✔
208
                Fragment::Line { data } | Fragment::Literal { data, .. } => {
476✔
209
                    out.extend_from_slice(&data)
504✔
210
                }
211
            }
212
        }
213

214
        out
448✔
215
    }
448✔
216
}
217

218
impl Write for EncodeContext {
219
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
31,458✔
220
        self.accumulator.extend_from_slice(buf);
31,458✔
221
        Ok(buf.len())
31,458✔
222
    }
31,458✔
223

224
    fn flush(&mut self) -> std::io::Result<()> {
×
225
        Ok(())
×
226
    }
×
227
}
228

229
macro_rules! impl_encoder_for_codec {
230
    ($codec:ty, $message:ty) => {
231
        impl Encoder for $codec {
232
            type Message<'a> = $message;
233

234
            fn encode(&self, message: &Self::Message<'_>) -> Encoded {
3,058✔
235
                let mut encode_context = EncodeContext::new();
3,058✔
236
                EncodeIntoContext::encode_ctx(message.borrow(), &mut encode_context).unwrap();
3,058✔
237

3,058✔
238
                Encoded {
3,058✔
239
                    items: encode_context.into_items(),
3,058✔
240
                }
3,058✔
241
            }
3,058✔
242
        }
243
    };
244
}
245

246
impl_encoder_for_codec!(GreetingCodec, Greeting<'a>);
247
impl_encoder_for_codec!(CommandCodec, Command<'a>);
248
impl_encoder_for_codec!(AuthenticateDataCodec, AuthenticateData<'a>);
249
impl_encoder_for_codec!(ResponseCodec, Response<'a>);
250
impl_encoder_for_codec!(IdleDoneCodec, IdleDone);
251

252
// -------------------------------------------------------------------------------------------------
253

254
pub(crate) trait EncodeIntoContext {
255
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()>;
256
}
257

258
// ----- Primitive ---------------------------------------------------------------------------------
259

260
impl EncodeIntoContext for u32 {
261
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
56✔
262
        ctx.write_all(self.to_string().as_bytes())
56✔
263
    }
56✔
264
}
265

266
impl EncodeIntoContext for u64 {
267
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
4✔
268
        ctx.write_all(self.to_string().as_bytes())
4✔
269
    }
4✔
270
}
271

272
// ----- Command -----------------------------------------------------------------------------------
273

274
impl<'a> EncodeIntoContext for Command<'a> {
275
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,130✔
276
        self.tag.encode_ctx(ctx)?;
1,130✔
277
        ctx.write_all(b" ")?;
1,130✔
278
        self.body.encode_ctx(ctx)?;
1,130✔
279
        ctx.write_all(b"\r\n")
1,130✔
280
    }
1,130✔
281
}
282

283
impl<'a> EncodeIntoContext for Tag<'a> {
284
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2,180✔
285
        ctx.write_all(self.inner().as_bytes())
2,180✔
286
    }
2,180✔
287
}
288

289
impl<'a> EncodeIntoContext for CommandBody<'a> {
290
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,130✔
291
        match self {
1,130✔
292
            CommandBody::Capability => ctx.write_all(b"CAPABILITY"),
84✔
293
            CommandBody::Noop => ctx.write_all(b"NOOP"),
34✔
294
            CommandBody::Logout => ctx.write_all(b"LOGOUT"),
28✔
295
            #[cfg(feature = "starttls")]
296
            CommandBody::StartTLS => ctx.write_all(b"STARTTLS"),
28✔
297
            CommandBody::Authenticate {
298
                mechanism,
10✔
299
                initial_response,
10✔
300
            } => {
10✔
301
                ctx.write_all(b"AUTHENTICATE")?;
10✔
302
                ctx.write_all(b" ")?;
10✔
303
                mechanism.encode_ctx(ctx)?;
10✔
304

305
                if let Some(ir) = initial_response {
10✔
306
                    ctx.write_all(b" ")?;
6✔
307

308
                    // RFC 4959 (https://datatracker.ietf.org/doc/html/rfc4959#section-3)
309
                    // "To send a zero-length initial response, the client MUST send a single pad character ("=").
310
                    // This indicates that the response is present, but is a zero-length string."
311
                    if ir.declassify().is_empty() {
6✔
312
                        ctx.write_all(b"=")?;
2✔
313
                    } else {
314
                        ctx.write_all(base64.encode(ir.declassify()).as_bytes())?;
4✔
315
                    };
316
                };
4✔
317

318
                Ok(())
10✔
319
            }
320
            CommandBody::Login { username, password } => {
86✔
321
                ctx.write_all(b"LOGIN")?;
86✔
322
                ctx.write_all(b" ")?;
86✔
323
                username.encode_ctx(ctx)?;
86✔
324
                ctx.write_all(b" ")?;
86✔
325
                password.declassify().encode_ctx(ctx)
86✔
326
            }
327
            CommandBody::Select { mailbox } => {
32✔
328
                ctx.write_all(b"SELECT")?;
32✔
329
                ctx.write_all(b" ")?;
32✔
330
                mailbox.encode_ctx(ctx)
32✔
331
            }
332
            CommandBody::Unselect => ctx.write_all(b"UNSELECT"),
2✔
333
            CommandBody::Examine { mailbox } => {
14✔
334
                ctx.write_all(b"EXAMINE")?;
14✔
335
                ctx.write_all(b" ")?;
14✔
336
                mailbox.encode_ctx(ctx)
14✔
337
            }
338
            CommandBody::Create { mailbox } => {
28✔
339
                ctx.write_all(b"CREATE")?;
28✔
340
                ctx.write_all(b" ")?;
28✔
341
                mailbox.encode_ctx(ctx)
28✔
342
            }
343
            CommandBody::Delete { mailbox } => {
84✔
344
                ctx.write_all(b"DELETE")?;
84✔
345
                ctx.write_all(b" ")?;
84✔
346
                mailbox.encode_ctx(ctx)
84✔
347
            }
348
            CommandBody::Rename {
349
                from: mailbox,
42✔
350
                to: new_mailbox,
42✔
351
            } => {
42✔
352
                ctx.write_all(b"RENAME")?;
42✔
353
                ctx.write_all(b" ")?;
42✔
354
                mailbox.encode_ctx(ctx)?;
42✔
355
                ctx.write_all(b" ")?;
42✔
356
                new_mailbox.encode_ctx(ctx)
42✔
357
            }
358
            CommandBody::Subscribe { mailbox } => {
14✔
359
                ctx.write_all(b"SUBSCRIBE")?;
14✔
360
                ctx.write_all(b" ")?;
14✔
361
                mailbox.encode_ctx(ctx)
14✔
362
            }
363
            CommandBody::Unsubscribe { mailbox } => {
14✔
364
                ctx.write_all(b"UNSUBSCRIBE")?;
14✔
365
                ctx.write_all(b" ")?;
14✔
366
                mailbox.encode_ctx(ctx)
14✔
367
            }
368
            CommandBody::List {
369
                reference,
182✔
370
                mailbox_wildcard,
182✔
371
            } => {
182✔
372
                ctx.write_all(b"LIST")?;
182✔
373
                ctx.write_all(b" ")?;
182✔
374
                reference.encode_ctx(ctx)?;
182✔
375
                ctx.write_all(b" ")?;
182✔
376
                mailbox_wildcard.encode_ctx(ctx)
182✔
377
            }
378
            CommandBody::Lsub {
379
                reference,
28✔
380
                mailbox_wildcard,
28✔
381
            } => {
28✔
382
                ctx.write_all(b"LSUB")?;
28✔
383
                ctx.write_all(b" ")?;
28✔
384
                reference.encode_ctx(ctx)?;
28✔
385
                ctx.write_all(b" ")?;
28✔
386
                mailbox_wildcard.encode_ctx(ctx)
28✔
387
            }
388
            CommandBody::Status {
389
                mailbox,
16✔
390
                item_names,
16✔
391
            } => {
16✔
392
                ctx.write_all(b"STATUS")?;
16✔
393
                ctx.write_all(b" ")?;
16✔
394
                mailbox.encode_ctx(ctx)?;
16✔
395
                ctx.write_all(b" ")?;
16✔
396
                ctx.write_all(b"(")?;
16✔
397
                join_serializable(item_names, b" ", ctx)?;
16✔
398
                ctx.write_all(b")")
16✔
399
            }
400
            CommandBody::Append {
401
                mailbox,
×
402
                flags,
×
403
                date,
×
404
                message,
×
405
            } => {
×
406
                ctx.write_all(b"APPEND")?;
×
407
                ctx.write_all(b" ")?;
×
408
                mailbox.encode_ctx(ctx)?;
×
409

410
                if !flags.is_empty() {
×
411
                    ctx.write_all(b" ")?;
×
412
                    ctx.write_all(b"(")?;
×
413
                    join_serializable(flags, b" ", ctx)?;
×
414
                    ctx.write_all(b")")?;
×
415
                }
×
416

417
                if let Some(date) = date {
×
418
                    ctx.write_all(b" ")?;
×
419
                    date.encode_ctx(ctx)?;
×
420
                }
×
421

422
                ctx.write_all(b" ")?;
×
423
                message.encode_ctx(ctx)
×
424
            }
425
            CommandBody::Check => ctx.write_all(b"CHECK"),
14✔
426
            CommandBody::Close => ctx.write_all(b"CLOSE"),
14✔
427
            CommandBody::Expunge => ctx.write_all(b"EXPUNGE"),
28✔
428
            CommandBody::ExpungeUid { sequence_set } => {
6✔
429
                ctx.write_all(b"UID EXPUNGE ")?;
6✔
430
                sequence_set.encode_ctx(ctx)
6✔
431
            }
432
            CommandBody::Search {
433
                charset,
28✔
434
                criteria,
28✔
435
                uid,
28✔
436
            } => {
28✔
437
                if *uid {
28✔
438
                    ctx.write_all(b"UID SEARCH")?;
×
439
                } else {
440
                    ctx.write_all(b"SEARCH")?;
28✔
441
                }
442
                if let Some(charset) = charset {
28✔
443
                    ctx.write_all(b" CHARSET ")?;
×
444
                    charset.encode_ctx(ctx)?;
×
445
                }
28✔
446
                ctx.write_all(b" ")?;
28✔
447
                join_serializable(criteria.as_ref(), b" ", ctx)
28✔
448
            }
449
            CommandBody::Sort {
450
                sort_criteria,
42✔
451
                charset,
42✔
452
                search_criteria,
42✔
453
                uid,
42✔
454
            } => {
42✔
455
                if *uid {
42✔
456
                    ctx.write_all(b"UID SORT (")?;
×
457
                } else {
458
                    ctx.write_all(b"SORT (")?;
42✔
459
                }
460
                join_serializable(sort_criteria.as_ref(), b" ", ctx)?;
42✔
461
                ctx.write_all(b") ")?;
42✔
462
                charset.encode_ctx(ctx)?;
42✔
463
                ctx.write_all(b" ")?;
42✔
464
                join_serializable(search_criteria.as_ref(), b" ", ctx)
42✔
465
            }
466
            CommandBody::Thread {
467
                algorithm,
42✔
468
                charset,
42✔
469
                search_criteria,
42✔
470
                uid,
42✔
471
            } => {
42✔
472
                if *uid {
42✔
473
                    ctx.write_all(b"UID THREAD ")?;
×
474
                } else {
475
                    ctx.write_all(b"THREAD ")?;
42✔
476
                }
477
                algorithm.encode_ctx(ctx)?;
42✔
478
                ctx.write_all(b" ")?;
42✔
479
                charset.encode_ctx(ctx)?;
42✔
480
                ctx.write_all(b" ")?;
42✔
481
                join_serializable(search_criteria.as_ref(), b" ", ctx)
42✔
482
            }
483
            CommandBody::Fetch {
484
                sequence_set,
56✔
485
                macro_or_item_names,
56✔
486
                uid,
56✔
487
            } => {
56✔
488
                if *uid {
56✔
489
                    ctx.write_all(b"UID FETCH ")?;
14✔
490
                } else {
491
                    ctx.write_all(b"FETCH ")?;
42✔
492
                }
493

494
                sequence_set.encode_ctx(ctx)?;
56✔
495
                ctx.write_all(b" ")?;
56✔
496
                macro_or_item_names.encode_ctx(ctx)
56✔
497
            }
498
            CommandBody::Store {
499
                sequence_set,
28✔
500
                kind,
28✔
501
                response,
28✔
502
                flags,
28✔
503
                uid,
28✔
504
            } => {
28✔
505
                if *uid {
28✔
506
                    ctx.write_all(b"UID STORE ")?;
×
507
                } else {
508
                    ctx.write_all(b"STORE ")?;
28✔
509
                }
510

511
                sequence_set.encode_ctx(ctx)?;
28✔
512
                ctx.write_all(b" ")?;
28✔
513

514
                match kind {
28✔
515
                    StoreType::Add => ctx.write_all(b"+")?,
28✔
516
                    StoreType::Remove => ctx.write_all(b"-")?,
×
517
                    StoreType::Replace => {}
×
518
                }
519

520
                ctx.write_all(b"FLAGS")?;
28✔
521

522
                match response {
28✔
523
                    StoreResponse::Answer => {}
28✔
524
                    StoreResponse::Silent => ctx.write_all(b".SILENT")?,
×
525
                }
526

527
                ctx.write_all(b" (")?;
28✔
528
                join_serializable(flags, b" ", ctx)?;
28✔
529
                ctx.write_all(b")")
28✔
530
            }
531
            CommandBody::Copy {
532
                sequence_set,
42✔
533
                mailbox,
42✔
534
                uid,
42✔
535
            } => {
42✔
536
                if *uid {
42✔
537
                    ctx.write_all(b"UID COPY ")?;
×
538
                } else {
539
                    ctx.write_all(b"COPY ")?;
42✔
540
                }
541
                sequence_set.encode_ctx(ctx)?;
42✔
542
                ctx.write_all(b" ")?;
42✔
543
                mailbox.encode_ctx(ctx)
42✔
544
            }
545
            CommandBody::Idle => ctx.write_all(b"IDLE"),
4✔
546
            CommandBody::Enable { capabilities } => {
34✔
547
                ctx.write_all(b"ENABLE ")?;
34✔
548
                join_serializable(capabilities.as_ref(), b" ", ctx)
34✔
549
            }
550
            CommandBody::Compress { algorithm } => {
4✔
551
                ctx.write_all(b"COMPRESS ")?;
4✔
552
                algorithm.encode_ctx(ctx)
4✔
553
            }
554
            CommandBody::GetQuota { root } => {
10✔
555
                ctx.write_all(b"GETQUOTA ")?;
10✔
556
                root.encode_ctx(ctx)
10✔
557
            }
558
            CommandBody::GetQuotaRoot { mailbox } => {
6✔
559
                ctx.write_all(b"GETQUOTAROOT ")?;
6✔
560
                mailbox.encode_ctx(ctx)
6✔
561
            }
562
            CommandBody::SetQuota { root, quotas } => {
12✔
563
                ctx.write_all(b"SETQUOTA ")?;
12✔
564
                root.encode_ctx(ctx)?;
12✔
565
                ctx.write_all(b" (")?;
12✔
566
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
567
                ctx.write_all(b")")
12✔
568
            }
569
            CommandBody::Move {
570
                sequence_set,
6✔
571
                mailbox,
6✔
572
                uid,
6✔
573
            } => {
6✔
574
                if *uid {
6✔
575
                    ctx.write_all(b"UID MOVE ")?;
2✔
576
                } else {
577
                    ctx.write_all(b"MOVE ")?;
4✔
578
                }
579
                sequence_set.encode_ctx(ctx)?;
6✔
580
                ctx.write_all(b" ")?;
6✔
581
                mailbox.encode_ctx(ctx)
6✔
582
            }
583
            #[cfg(feature = "ext_id")]
584
            CommandBody::Id { parameters } => {
8✔
585
                ctx.write_all(b"ID ")?;
8✔
586

587
                match parameters {
8✔
588
                    Some(parameters) => {
4✔
589
                        if let Some((first, tail)) = parameters.split_first() {
4✔
590
                            ctx.write_all(b"(")?;
4✔
591

592
                            first.0.encode_ctx(ctx)?;
4✔
593
                            ctx.write_all(b" ")?;
4✔
594
                            first.1.encode_ctx(ctx)?;
4✔
595

596
                            for parameter in tail {
4✔
597
                                ctx.write_all(b" ")?;
×
598
                                parameter.0.encode_ctx(ctx)?;
×
599
                                ctx.write_all(b" ")?;
×
600
                                parameter.1.encode_ctx(ctx)?;
×
601
                            }
602

603
                            ctx.write_all(b")")
4✔
604
                        } else {
605
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
606
                            {
607
                                ctx.write_all(b"()")
608
                            }
609
                            #[cfg(feature = "quirk_id_empty_to_nil")]
610
                            {
611
                                ctx.write_all(b"NIL")
×
612
                            }
613
                        }
614
                    }
615
                    None => ctx.write_all(b"NIL"),
4✔
616
                }
617
            }
618
            #[cfg(feature = "ext_metadata")]
619
            CommandBody::SetMetadata {
620
                mailbox,
6✔
621
                entry_values,
6✔
622
            } => {
6✔
623
                ctx.write_all(b"SETMETADATA ")?;
6✔
624
                mailbox.encode_ctx(ctx)?;
6✔
625
                ctx.write_all(b" (")?;
6✔
626
                join_serializable(entry_values.as_ref(), b" ", ctx)?;
6✔
627
                ctx.write_all(b")")
6✔
628
            }
629
            #[cfg(feature = "ext_metadata")]
630
            CommandBody::GetMetadata {
631
                options,
14✔
632
                mailbox,
14✔
633
                entries,
14✔
634
            } => {
14✔
635
                ctx.write_all(b"GETMETADATA")?;
14✔
636

637
                if !options.is_empty() {
14✔
638
                    ctx.write_all(b" (")?;
10✔
639
                    join_serializable(options, b" ", ctx)?;
10✔
640
                    ctx.write_all(b")")?;
10✔
641
                }
4✔
642

643
                ctx.write_all(b" ")?;
14✔
644
                mailbox.encode_ctx(ctx)?;
14✔
645

646
                ctx.write_all(b" ")?;
14✔
647

648
                if entries.as_ref().len() == 1 {
14✔
649
                    entries.as_ref()[0].encode_ctx(ctx)
14✔
650
                } else {
651
                    ctx.write_all(b"(")?;
×
652
                    join_serializable(entries.as_ref(), b" ", ctx)?;
×
653
                    ctx.write_all(b")")
×
654
                }
655
            }
656
        }
657
    }
1,130✔
658
}
659

660
impl<'a> EncodeIntoContext for AuthMechanism<'a> {
661
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
22✔
662
        write!(ctx, "{}", self)
22✔
663
    }
22✔
664
}
665

666
impl EncodeIntoContext for AuthenticateData<'_> {
667
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
668
        match self {
8✔
669
            Self::Continue(data) => {
6✔
670
                let encoded = base64.encode(data.declassify());
6✔
671
                ctx.write_all(encoded.as_bytes())?;
6✔
672
                ctx.write_all(b"\r\n")
6✔
673
            }
674
            Self::Cancel => ctx.write_all(b"*\r\n"),
2✔
675
        }
676
    }
8✔
677
}
678

679
impl<'a> EncodeIntoContext for AString<'a> {
680
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,278✔
681
        match self {
1,278✔
682
            AString::Atom(atom) => atom.encode_ctx(ctx),
958✔
683
            AString::String(imap_str) => imap_str.encode_ctx(ctx),
320✔
684
        }
685
    }
1,278✔
686
}
687

688
impl<'a> EncodeIntoContext for Atom<'a> {
689
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
90✔
690
        ctx.write_all(self.inner().as_bytes())
90✔
691
    }
90✔
692
}
693

694
impl<'a> EncodeIntoContext for AtomExt<'a> {
695
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
958✔
696
        ctx.write_all(self.inner().as_bytes())
958✔
697
    }
958✔
698
}
699

700
impl<'a> EncodeIntoContext for IString<'a> {
701
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
798✔
702
        match self {
798✔
703
            Self::Literal(val) => val.encode_ctx(ctx),
50✔
704
            Self::Quoted(val) => val.encode_ctx(ctx),
748✔
705
        }
706
    }
798✔
707
}
708

709
impl<'a> EncodeIntoContext for Literal<'a> {
710
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
50✔
711
        match self.mode() {
50✔
712
            LiteralMode::Sync => write!(ctx, "{{{}}}\r\n", self.as_ref().len())?,
36✔
713
            LiteralMode::NonSync => write!(ctx, "{{{}+}}\r\n", self.as_ref().len())?,
14✔
714
        }
715

716
        ctx.push_line();
50✔
717
        ctx.write_all(self.as_ref())?;
50✔
718
        ctx.push_literal(self.mode());
50✔
719

50✔
720
        Ok(())
50✔
721
    }
50✔
722
}
723

724
impl<'a> EncodeIntoContext for Quoted<'a> {
725
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
756✔
726
        write!(ctx, "\"{}\"", escape_quoted(self.inner()))
756✔
727
    }
756✔
728
}
729

730
impl<'a> EncodeIntoContext for Mailbox<'a> {
731
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,036✔
732
        match self {
1,036✔
733
            Mailbox::Inbox => ctx.write_all(b"INBOX"),
110✔
734
            Mailbox::Other(other) => other.encode_ctx(ctx),
926✔
735
        }
736
    }
1,036✔
737
}
738

739
impl<'a> EncodeIntoContext for MailboxOther<'a> {
740
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
926✔
741
        self.inner().encode_ctx(ctx)
926✔
742
    }
926✔
743
}
744

745
impl<'a> EncodeIntoContext for ListMailbox<'a> {
746
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
210✔
747
        match self {
210✔
748
            ListMailbox::Token(lcs) => lcs.encode_ctx(ctx),
140✔
749
            ListMailbox::String(istr) => istr.encode_ctx(ctx),
70✔
750
        }
751
    }
210✔
752
}
753

754
impl<'a> EncodeIntoContext for ListCharString<'a> {
755
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
140✔
756
        ctx.write_all(self.as_ref())
140✔
757
    }
140✔
758
}
759

760
impl EncodeIntoContext for StatusDataItemName {
761
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
48✔
762
        match self {
48✔
763
            Self::Messages => ctx.write_all(b"MESSAGES"),
18✔
764
            Self::Recent => ctx.write_all(b"RECENT"),
2✔
765
            Self::UidNext => ctx.write_all(b"UIDNEXT"),
16✔
766
            Self::UidValidity => ctx.write_all(b"UIDVALIDITY"),
2✔
767
            Self::Unseen => ctx.write_all(b"UNSEEN"),
2✔
768
            Self::Deleted => ctx.write_all(b"DELETED"),
4✔
769
            Self::DeletedStorage => ctx.write_all(b"DELETED-STORAGE"),
4✔
770
            #[cfg(feature = "ext_condstore_qresync")]
771
            Self::HighestModSeq => ctx.write_all(b"HIGHESTMODSEQ"),
×
772
        }
773
    }
48✔
774
}
775

776
impl<'a> EncodeIntoContext for Flag<'a> {
777
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
546✔
778
        write!(ctx, "{}", self)
546✔
779
    }
546✔
780
}
781

782
impl<'a> EncodeIntoContext for FlagFetch<'a> {
783
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
210✔
784
        match self {
210✔
785
            Self::Flag(flag) => flag.encode_ctx(ctx),
210✔
786
            Self::Recent => ctx.write_all(b"\\Recent"),
×
787
        }
788
    }
210✔
789
}
790

791
impl<'a> EncodeIntoContext for FlagPerm<'a> {
792
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
42✔
793
        match self {
42✔
794
            Self::Flag(flag) => flag.encode_ctx(ctx),
28✔
795
            Self::Asterisk => ctx.write_all(b"\\*"),
14✔
796
        }
797
    }
42✔
798
}
799

800
impl EncodeIntoContext for DateTime {
801
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
802
        self.as_ref().encode_ctx(ctx)
20✔
803
    }
20✔
804
}
805

806
impl<'a> EncodeIntoContext for Charset<'a> {
807
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
94✔
808
        match self {
94✔
809
            Charset::Atom(atom) => atom.encode_ctx(ctx),
86✔
810
            Charset::Quoted(quoted) => quoted.encode_ctx(ctx),
8✔
811
        }
812
    }
94✔
813
}
814

815
impl<'a> EncodeIntoContext for SearchKey<'a> {
816
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
242✔
817
        match self {
242✔
818
            SearchKey::All => ctx.write_all(b"ALL"),
16✔
819
            SearchKey::Answered => ctx.write_all(b"ANSWERED"),
6✔
820
            SearchKey::Bcc(astring) => {
2✔
821
                ctx.write_all(b"BCC ")?;
2✔
822
                astring.encode_ctx(ctx)
2✔
823
            }
824
            SearchKey::Before(date) => {
2✔
825
                ctx.write_all(b"BEFORE ")?;
2✔
826
                date.encode_ctx(ctx)
2✔
827
            }
828
            SearchKey::Body(astring) => {
2✔
829
                ctx.write_all(b"BODY ")?;
2✔
830
                astring.encode_ctx(ctx)
2✔
831
            }
832
            SearchKey::Cc(astring) => {
2✔
833
                ctx.write_all(b"CC ")?;
2✔
834
                astring.encode_ctx(ctx)
2✔
835
            }
836
            SearchKey::Deleted => ctx.write_all(b"DELETED"),
2✔
837
            SearchKey::Flagged => ctx.write_all(b"FLAGGED"),
16✔
838
            SearchKey::From(astring) => {
16✔
839
                ctx.write_all(b"FROM ")?;
16✔
840
                astring.encode_ctx(ctx)
16✔
841
            }
842
            SearchKey::Keyword(flag_keyword) => {
2✔
843
                ctx.write_all(b"KEYWORD ")?;
2✔
844
                flag_keyword.encode_ctx(ctx)
2✔
845
            }
846
            SearchKey::New => ctx.write_all(b"NEW"),
6✔
847
            SearchKey::Old => ctx.write_all(b"OLD"),
2✔
848
            SearchKey::On(date) => {
2✔
849
                ctx.write_all(b"ON ")?;
2✔
850
                date.encode_ctx(ctx)
2✔
851
            }
852
            SearchKey::Recent => ctx.write_all(b"RECENT"),
4✔
853
            SearchKey::Seen => ctx.write_all(b"SEEN"),
4✔
854
            SearchKey::Since(date) => {
58✔
855
                ctx.write_all(b"SINCE ")?;
58✔
856
                date.encode_ctx(ctx)
58✔
857
            }
858
            SearchKey::Subject(astring) => {
2✔
859
                ctx.write_all(b"SUBJECT ")?;
2✔
860
                astring.encode_ctx(ctx)
2✔
861
            }
862
            SearchKey::Text(astring) => {
44✔
863
                ctx.write_all(b"TEXT ")?;
44✔
864
                astring.encode_ctx(ctx)
44✔
865
            }
866
            SearchKey::To(astring) => {
2✔
867
                ctx.write_all(b"TO ")?;
2✔
868
                astring.encode_ctx(ctx)
2✔
869
            }
870
            SearchKey::Unanswered => ctx.write_all(b"UNANSWERED"),
2✔
871
            SearchKey::Undeleted => ctx.write_all(b"UNDELETED"),
2✔
872
            SearchKey::Unflagged => ctx.write_all(b"UNFLAGGED"),
2✔
873
            SearchKey::Unkeyword(flag_keyword) => {
2✔
874
                ctx.write_all(b"UNKEYWORD ")?;
2✔
875
                flag_keyword.encode_ctx(ctx)
2✔
876
            }
877
            SearchKey::Unseen => ctx.write_all(b"UNSEEN"),
2✔
878
            SearchKey::Draft => ctx.write_all(b"DRAFT"),
2✔
879
            SearchKey::Header(header_fld_name, astring) => {
2✔
880
                ctx.write_all(b"HEADER ")?;
2✔
881
                header_fld_name.encode_ctx(ctx)?;
2✔
882
                ctx.write_all(b" ")?;
2✔
883
                astring.encode_ctx(ctx)
2✔
884
            }
885
            SearchKey::Larger(number) => write!(ctx, "LARGER {number}"),
2✔
886
            SearchKey::Not(search_key) => {
16✔
887
                ctx.write_all(b"NOT ")?;
16✔
888
                search_key.encode_ctx(ctx)
16✔
889
            }
890
            SearchKey::Or(search_key_a, search_key_b) => {
2✔
891
                ctx.write_all(b"OR ")?;
2✔
892
                search_key_a.encode_ctx(ctx)?;
2✔
893
                ctx.write_all(b" ")?;
2✔
894
                search_key_b.encode_ctx(ctx)
2✔
895
            }
896
            SearchKey::SentBefore(date) => {
2✔
897
                ctx.write_all(b"SENTBEFORE ")?;
2✔
898
                date.encode_ctx(ctx)
2✔
899
            }
900
            SearchKey::SentOn(date) => {
2✔
901
                ctx.write_all(b"SENTON ")?;
2✔
902
                date.encode_ctx(ctx)
2✔
903
            }
904
            SearchKey::SentSince(date) => {
2✔
905
                ctx.write_all(b"SENTSINCE ")?;
2✔
906
                date.encode_ctx(ctx)
2✔
907
            }
908
            SearchKey::Smaller(number) => write!(ctx, "SMALLER {number}"),
2✔
909
            SearchKey::Uid(sequence_set) => {
2✔
910
                ctx.write_all(b"UID ")?;
2✔
911
                sequence_set.encode_ctx(ctx)
2✔
912
            }
913
            SearchKey::Undraft => ctx.write_all(b"UNDRAFT"),
2✔
914
            SearchKey::SequenceSet(sequence_set) => sequence_set.encode_ctx(ctx),
2✔
915
            SearchKey::And(search_keys) => {
4✔
916
                ctx.write_all(b"(")?;
4✔
917
                join_serializable(search_keys.as_ref(), b" ", ctx)?;
4✔
918
                ctx.write_all(b")")
4✔
919
            }
920
        }
921
    }
242✔
922
}
923

924
impl EncodeIntoContext for SequenceSet {
925
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
142✔
926
        join_serializable(self.0.as_ref(), b",", ctx)
142✔
927
    }
142✔
928
}
929

930
impl EncodeIntoContext for Sequence {
931
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
148✔
932
        match self {
148✔
933
            Sequence::Single(seq_no) => seq_no.encode_ctx(ctx),
56✔
934
            Sequence::Range(from, to) => {
92✔
935
                from.encode_ctx(ctx)?;
92✔
936
                ctx.write_all(b":")?;
92✔
937
                to.encode_ctx(ctx)
92✔
938
            }
939
        }
940
    }
148✔
941
}
942

943
impl EncodeIntoContext for SeqOrUid {
944
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
240✔
945
        match self {
240✔
946
            SeqOrUid::Value(number) => write!(ctx, "{number}"),
230✔
947
            SeqOrUid::Asterisk => ctx.write_all(b"*"),
10✔
948
        }
949
    }
240✔
950
}
951

952
impl EncodeIntoContext for NaiveDate {
953
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
68✔
954
        write!(ctx, "\"{}\"", self.as_ref().format("%d-%b-%Y"))
68✔
955
    }
68✔
956
}
957

958
impl<'a> EncodeIntoContext for MacroOrMessageDataItemNames<'a> {
959
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
56✔
960
        match self {
56✔
961
            Self::Macro(m) => m.encode_ctx(ctx),
14✔
962
            Self::MessageDataItemNames(item_names) => {
42✔
963
                if item_names.len() == 1 {
42✔
964
                    item_names[0].encode_ctx(ctx)
28✔
965
                } else {
966
                    ctx.write_all(b"(")?;
14✔
967
                    join_serializable(item_names.as_slice(), b" ", ctx)?;
14✔
968
                    ctx.write_all(b")")
14✔
969
                }
970
            }
971
        }
972
    }
56✔
973
}
974

975
impl EncodeIntoContext for Macro {
976
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
977
        write!(ctx, "{}", self)
14✔
978
    }
14✔
979
}
980

981
impl<'a> EncodeIntoContext for MessageDataItemName<'a> {
982
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
78✔
983
        match self {
78✔
984
            Self::Body => ctx.write_all(b"BODY"),
2✔
985
            Self::BodyExt {
30✔
986
                section,
30✔
987
                partial,
30✔
988
                peek,
30✔
989
            } => {
30✔
990
                if *peek {
30✔
991
                    ctx.write_all(b"BODY.PEEK[")?;
×
992
                } else {
993
                    ctx.write_all(b"BODY[")?;
30✔
994
                }
995
                if let Some(section) = section {
30✔
996
                    section.encode_ctx(ctx)?;
28✔
997
                }
2✔
998
                ctx.write_all(b"]")?;
30✔
999
                if let Some((a, b)) = partial {
30✔
1000
                    write!(ctx, "<{a}.{b}>")?;
×
1001
                }
30✔
1002

1003
                Ok(())
30✔
1004
            }
1005
            Self::BodyStructure => ctx.write_all(b"BODYSTRUCTURE"),
2✔
1006
            Self::Envelope => ctx.write_all(b"ENVELOPE"),
2✔
1007
            Self::Flags => ctx.write_all(b"FLAGS"),
30✔
1008
            Self::InternalDate => ctx.write_all(b"INTERNALDATE"),
2✔
1009
            Self::Rfc822 => ctx.write_all(b"RFC822"),
2✔
1010
            Self::Rfc822Header => ctx.write_all(b"RFC822.HEADER"),
2✔
1011
            Self::Rfc822Size => ctx.write_all(b"RFC822.SIZE"),
2✔
1012
            Self::Rfc822Text => ctx.write_all(b"RFC822.TEXT"),
2✔
1013
            Self::Uid => ctx.write_all(b"UID"),
2✔
1014
            MessageDataItemName::Binary {
1015
                section,
×
1016
                partial,
×
1017
                peek,
×
1018
            } => {
×
1019
                ctx.write_all(b"BINARY")?;
×
1020
                if *peek {
×
1021
                    ctx.write_all(b".PEEK")?;
×
1022
                }
×
1023

1024
                ctx.write_all(b"[")?;
×
1025
                join_serializable(section, b".", ctx)?;
×
1026
                ctx.write_all(b"]")?;
×
1027

1028
                if let Some((a, b)) = partial {
×
1029
                    ctx.write_all(b"<")?;
×
1030
                    a.encode_ctx(ctx)?;
×
1031
                    ctx.write_all(b".")?;
×
1032
                    b.encode_ctx(ctx)?;
×
1033
                    ctx.write_all(b">")?;
×
1034
                }
×
1035

1036
                Ok(())
×
1037
            }
1038
            MessageDataItemName::BinarySize { section } => {
×
1039
                ctx.write_all(b"BINARY.SIZE")?;
×
1040

1041
                ctx.write_all(b"[")?;
×
1042
                join_serializable(section, b".", ctx)?;
×
1043
                ctx.write_all(b"]")
×
1044
            }
1045
        }
1046
    }
78✔
1047
}
1048

1049
impl<'a> EncodeIntoContext for Section<'a> {
1050
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
62✔
1051
        match self {
62✔
1052
            Section::Part(part) => part.encode_ctx(ctx),
2✔
1053
            Section::Header(maybe_part) => match maybe_part {
32✔
1054
                Some(part) => {
2✔
1055
                    part.encode_ctx(ctx)?;
2✔
1056
                    ctx.write_all(b".HEADER")
2✔
1057
                }
1058
                None => ctx.write_all(b"HEADER"),
30✔
1059
            },
1060
            Section::HeaderFields(maybe_part, header_list) => {
18✔
1061
                match maybe_part {
18✔
1062
                    Some(part) => {
2✔
1063
                        part.encode_ctx(ctx)?;
2✔
1064
                        ctx.write_all(b".HEADER.FIELDS (")?;
2✔
1065
                    }
1066
                    None => ctx.write_all(b"HEADER.FIELDS (")?,
16✔
1067
                };
1068
                join_serializable(header_list.as_ref(), b" ", ctx)?;
18✔
1069
                ctx.write_all(b")")
18✔
1070
            }
1071
            Section::HeaderFieldsNot(maybe_part, header_list) => {
4✔
1072
                match maybe_part {
4✔
1073
                    Some(part) => {
2✔
1074
                        part.encode_ctx(ctx)?;
2✔
1075
                        ctx.write_all(b".HEADER.FIELDS.NOT (")?;
2✔
1076
                    }
1077
                    None => ctx.write_all(b"HEADER.FIELDS.NOT (")?,
2✔
1078
                };
1079
                join_serializable(header_list.as_ref(), b" ", ctx)?;
4✔
1080
                ctx.write_all(b")")
4✔
1081
            }
1082
            Section::Text(maybe_part) => match maybe_part {
4✔
1083
                Some(part) => {
2✔
1084
                    part.encode_ctx(ctx)?;
2✔
1085
                    ctx.write_all(b".TEXT")
2✔
1086
                }
1087
                None => ctx.write_all(b"TEXT"),
2✔
1088
            },
1089
            Section::Mime(part) => {
2✔
1090
                part.encode_ctx(ctx)?;
2✔
1091
                ctx.write_all(b".MIME")
2✔
1092
            }
1093
        }
1094
    }
62✔
1095
}
1096

1097
impl EncodeIntoContext for Part {
1098
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
12✔
1099
        join_serializable(self.0.as_ref(), b".", ctx)
12✔
1100
    }
12✔
1101
}
1102

1103
impl EncodeIntoContext for NonZeroU32 {
1104
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
396✔
1105
        write!(ctx, "{self}")
396✔
1106
    }
396✔
1107
}
1108

1109
impl<'a> EncodeIntoContext for Capability<'a> {
1110
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
388✔
1111
        write!(ctx, "{}", self)
388✔
1112
    }
388✔
1113
}
1114

1115
// ----- Responses ---------------------------------------------------------------------------------
1116

1117
impl<'a> EncodeIntoContext for Response<'a> {
1118
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
2,628✔
1119
        match self {
2,628✔
1120
            Response::Status(status) => status.encode_ctx(ctx),
1,406✔
1121
            Response::Data(data) => data.encode_ctx(ctx),
1,208✔
1122
            Response::CommandContinuationRequest(continue_request) => {
14✔
1123
                continue_request.encode_ctx(ctx)
14✔
1124
            }
1125
        }
1126
    }
2,628✔
1127
}
1128

1129
impl<'a> EncodeIntoContext for Greeting<'a> {
1130
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1131
        ctx.write_all(b"* ")?;
32✔
1132
        self.kind.encode_ctx(ctx)?;
32✔
1133
        ctx.write_all(b" ")?;
32✔
1134

1135
        if let Some(ref code) = self.code {
32✔
1136
            ctx.write_all(b"[")?;
12✔
1137
            code.encode_ctx(ctx)?;
12✔
1138
            ctx.write_all(b"] ")?;
12✔
1139
        }
20✔
1140

1141
        self.text.encode_ctx(ctx)?;
32✔
1142
        ctx.write_all(b"\r\n")
32✔
1143
    }
32✔
1144
}
1145

1146
impl EncodeIntoContext for GreetingKind {
1147
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
32✔
1148
        match self {
32✔
1149
            GreetingKind::Ok => ctx.write_all(b"OK"),
12✔
1150
            GreetingKind::PreAuth => ctx.write_all(b"PREAUTH"),
18✔
1151
            GreetingKind::Bye => ctx.write_all(b"BYE"),
2✔
1152
        }
1153
    }
32✔
1154
}
1155

1156
impl<'a> EncodeIntoContext for Status<'a> {
1157
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,406✔
1158
        fn format_status(
1,527✔
1159
            tag: Option<&Tag>,
1,527✔
1160
            status: &str,
1,527✔
1161
            code: &Option<Code>,
1,527✔
1162
            comment: &Text,
1,527✔
1163
            ctx: &mut EncodeContext,
1,527✔
1164
        ) -> std::io::Result<()> {
1,527✔
1165
            match tag {
1,527✔
1166
                Some(tag) => tag.encode_ctx(ctx)?,
1,487✔
1167
                None => ctx.write_all(b"*")?,
1,446✔
1168
            }
1,406✔
1169
            ctx.write_all(b" ")?;
1,527✔
1170
            ctx.write_all(status.as_bytes())?;
1,527✔
1171
            ctx.write_all(b" ")?;
1,527✔
1172
            if let Some(code) = code {
1,527✔
1173
                ctx.write_all(b"[")?;
1,438✔
1174
                code.encode_ctx(ctx)?;
1,438✔
1175
                ctx.write_all(b"] ")?;
1,438✔
1176
            }
1,495✔
1177
            comment.encode_ctx(ctx)?;
1,527✔
1178
            ctx.write_all(b"\r\n")
1,527✔
1179
        }
1,527✔
1180

1,406✔
1181
        match self {
1,406✔
1182
            Self::Untagged(StatusBody { kind, code, text }) => match kind {
312✔
1183
                StatusKind::Ok => format_status(None, "OK", code, text, ctx),
218✔
1184
                StatusKind::No => format_status(None, "NO", code, text, ctx),
48✔
1185
                StatusKind::Bad => format_status(None, "BAD", code, text, ctx),
46✔
1186
            },
1187
            Self::Tagged(Tagged {
1,050✔
1188
                tag,
1,050✔
1189
                body: StatusBody { kind, code, text },
1,050✔
1190
            }) => match kind {
1,050✔
1191
                StatusKind::Ok => format_status(Some(tag), "OK", code, text, ctx),
998✔
1192
                StatusKind::No => format_status(Some(tag), "NO", code, text, ctx),
34✔
1193
                StatusKind::Bad => format_status(Some(tag), "BAD", code, text, ctx),
18✔
1194
            },
1195
            Self::Bye(Bye { code, text }) => format_status(None, "BYE", code, text, ctx),
44✔
1196
        }
1197
    }
1,406✔
1198
}
1199

1200
impl<'a> EncodeIntoContext for Code<'a> {
1201
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
244✔
1202
        match self {
244✔
1203
            Code::Alert => ctx.write_all(b"ALERT"),
30✔
1204
            Code::BadCharset { allowed } => {
2✔
1205
                if allowed.is_empty() {
2✔
1206
                    ctx.write_all(b"BADCHARSET")
2✔
1207
                } else {
1208
                    ctx.write_all(b"BADCHARSET (")?;
×
1209
                    join_serializable(allowed, b" ", ctx)?;
×
1210
                    ctx.write_all(b")")
×
1211
                }
1212
            }
1213
            Code::Capability(caps) => {
4✔
1214
                ctx.write_all(b"CAPABILITY ")?;
4✔
1215
                join_serializable(caps.as_ref(), b" ", ctx)
4✔
1216
            }
1217
            Code::Parse => ctx.write_all(b"PARSE"),
×
1218
            Code::PermanentFlags(flags) => {
28✔
1219
                ctx.write_all(b"PERMANENTFLAGS (")?;
28✔
1220
                join_serializable(flags, b" ", ctx)?;
28✔
1221
                ctx.write_all(b")")
28✔
1222
            }
1223
            Code::ReadOnly => ctx.write_all(b"READ-ONLY"),
14✔
1224
            Code::ReadWrite => ctx.write_all(b"READ-WRITE"),
28✔
1225
            Code::TryCreate => ctx.write_all(b"TRYCREATE"),
×
1226
            Code::UidNext(next) => {
28✔
1227
                ctx.write_all(b"UIDNEXT ")?;
28✔
1228
                next.encode_ctx(ctx)
28✔
1229
            }
1230
            Code::UidValidity(validity) => {
42✔
1231
                ctx.write_all(b"UIDVALIDITY ")?;
42✔
1232
                validity.encode_ctx(ctx)
42✔
1233
            }
1234
            Code::Unseen(seq) => {
46✔
1235
                ctx.write_all(b"UNSEEN ")?;
46✔
1236
                seq.encode_ctx(ctx)
46✔
1237
            }
1238
            // RFC 2221
1239
            #[cfg(any(feature = "ext_login_referrals", feature = "ext_mailbox_referrals"))]
1240
            Code::Referral(url) => {
×
1241
                ctx.write_all(b"REFERRAL ")?;
×
1242
                ctx.write_all(url.as_bytes())
×
1243
            }
1244
            Code::CompressionActive => ctx.write_all(b"COMPRESSIONACTIVE"),
×
1245
            Code::OverQuota => ctx.write_all(b"OVERQUOTA"),
4✔
1246
            Code::TooBig => ctx.write_all(b"TOOBIG"),
×
1247
            #[cfg(feature = "ext_metadata")]
1248
            Code::Metadata(code) => {
12✔
1249
                ctx.write_all(b"METADATA ")?;
12✔
1250
                code.encode_ctx(ctx)
12✔
1251
            }
1252
            Code::UnknownCte => ctx.write_all(b"UNKNOWN-CTE"),
×
1253
            Code::AppendUid { uid_validity, uid } => {
2✔
1254
                ctx.write_all(b"APPENDUID ")?;
2✔
1255
                uid_validity.encode_ctx(ctx)?;
2✔
1256
                ctx.write_all(b" ")?;
2✔
1257
                uid.encode_ctx(ctx)
2✔
1258
            }
1259
            Code::CopyUid {
1260
                uid_validity,
2✔
1261
                source,
2✔
1262
                destination,
2✔
1263
            } => {
2✔
1264
                ctx.write_all(b"COPYUID ")?;
2✔
1265
                uid_validity.encode_ctx(ctx)?;
2✔
1266
                ctx.write_all(b" ")?;
2✔
1267
                source.encode_ctx(ctx)?;
2✔
1268
                ctx.write_all(b" ")?;
2✔
1269
                destination.encode_ctx(ctx)
2✔
1270
            }
1271
            Code::UidNotSticky => ctx.write_all(b"UIDNOTSTICKY"),
2✔
1272
            Code::Other(unknown) => unknown.encode_ctx(ctx),
×
1273
        }
1274
    }
244✔
1275
}
1276

1277
impl<'a> EncodeIntoContext for CodeOther<'a> {
1278
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1279
        ctx.write_all(self.inner())
×
1280
    }
×
1281
}
1282

1283
impl<'a> EncodeIntoContext for Text<'a> {
1284
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,452✔
1285
        ctx.write_all(self.inner().as_bytes())
1,452✔
1286
    }
1,452✔
1287
}
1288

1289
impl<'a> EncodeIntoContext for Data<'a> {
1290
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
1,208✔
1291
        match self {
1,208✔
1292
            Data::Capability(caps) => {
106✔
1293
                ctx.write_all(b"* CAPABILITY ")?;
106✔
1294
                join_serializable(caps.as_ref(), b" ", ctx)?;
106✔
1295
            }
1296
            Data::List {
1297
                items,
366✔
1298
                delimiter,
366✔
1299
                mailbox,
366✔
1300
            } => {
366✔
1301
                ctx.write_all(b"* LIST (")?;
366✔
1302
                join_serializable(items, b" ", ctx)?;
366✔
1303
                ctx.write_all(b") ")?;
366✔
1304

1305
                if let Some(delimiter) = delimiter {
366✔
1306
                    ctx.write_all(b"\"")?;
366✔
1307
                    delimiter.encode_ctx(ctx)?;
366✔
1308
                    ctx.write_all(b"\"")?;
366✔
1309
                } else {
1310
                    ctx.write_all(b"NIL")?;
×
1311
                }
1312
                ctx.write_all(b" ")?;
366✔
1313
                mailbox.encode_ctx(ctx)?;
366✔
1314
            }
1315
            Data::Lsub {
1316
                items,
56✔
1317
                delimiter,
56✔
1318
                mailbox,
56✔
1319
            } => {
56✔
1320
                ctx.write_all(b"* LSUB (")?;
56✔
1321
                join_serializable(items, b" ", ctx)?;
56✔
1322
                ctx.write_all(b") ")?;
56✔
1323

1324
                if let Some(delimiter) = delimiter {
56✔
1325
                    ctx.write_all(b"\"")?;
56✔
1326
                    delimiter.encode_ctx(ctx)?;
56✔
1327
                    ctx.write_all(b"\"")?;
56✔
1328
                } else {
1329
                    ctx.write_all(b"NIL")?;
×
1330
                }
1331
                ctx.write_all(b" ")?;
56✔
1332
                mailbox.encode_ctx(ctx)?;
56✔
1333
            }
1334
            Data::Status { mailbox, items } => {
30✔
1335
                ctx.write_all(b"* STATUS ")?;
30✔
1336
                mailbox.encode_ctx(ctx)?;
30✔
1337
                ctx.write_all(b" (")?;
30✔
1338
                join_serializable(items, b" ", ctx)?;
30✔
1339
                ctx.write_all(b")")?;
30✔
1340
            }
1341
            Data::Search(seqs) => {
62✔
1342
                if seqs.is_empty() {
62✔
1343
                    ctx.write_all(b"* SEARCH")?;
14✔
1344
                } else {
1345
                    ctx.write_all(b"* SEARCH ")?;
48✔
1346
                    join_serializable(seqs, b" ", ctx)?;
48✔
1347
                }
1348
            }
1349
            Data::Sort(seqs) => {
42✔
1350
                if seqs.is_empty() {
42✔
1351
                    ctx.write_all(b"* SORT")?;
14✔
1352
                } else {
1353
                    ctx.write_all(b"* SORT ")?;
28✔
1354
                    join_serializable(seqs, b" ", ctx)?;
28✔
1355
                }
1356
            }
1357
            Data::Thread(threads) => {
42✔
1358
                if threads.is_empty() {
42✔
1359
                    ctx.write_all(b"* THREAD")?;
14✔
1360
                } else {
1361
                    ctx.write_all(b"* THREAD ")?;
28✔
1362
                    for thread in threads {
700✔
1363
                        thread.encode_ctx(ctx)?;
672✔
1364
                    }
1365
                }
1366
            }
1367
            Data::Flags(flags) => {
56✔
1368
                ctx.write_all(b"* FLAGS (")?;
56✔
1369
                join_serializable(flags, b" ", ctx)?;
56✔
1370
                ctx.write_all(b")")?;
56✔
1371
            }
1372
            Data::Exists(count) => write!(ctx, "* {count} EXISTS")?,
72✔
1373
            Data::Recent(count) => write!(ctx, "* {count} RECENT")?,
72✔
1374
            Data::Expunge(msg) => write!(ctx, "* {msg} EXPUNGE")?,
86✔
1375
            Data::Fetch { seq, items } => {
162✔
1376
                write!(ctx, "* {seq} FETCH (")?;
162✔
1377
                join_serializable(items.as_ref(), b" ", ctx)?;
162✔
1378
                ctx.write_all(b")")?;
162✔
1379
            }
1380
            Data::Enabled { capabilities } => {
28✔
1381
                write!(ctx, "* ENABLED")?;
28✔
1382

1383
                for cap in capabilities {
56✔
1384
                    ctx.write_all(b" ")?;
28✔
1385
                    cap.encode_ctx(ctx)?;
28✔
1386
                }
1387
            }
1388
            Data::Quota { root, quotas } => {
12✔
1389
                ctx.write_all(b"* QUOTA ")?;
12✔
1390
                root.encode_ctx(ctx)?;
12✔
1391
                ctx.write_all(b" (")?;
12✔
1392
                join_serializable(quotas.as_ref(), b" ", ctx)?;
12✔
1393
                ctx.write_all(b")")?;
12✔
1394
            }
1395
            Data::QuotaRoot { mailbox, roots } => {
10✔
1396
                ctx.write_all(b"* QUOTAROOT ")?;
10✔
1397
                mailbox.encode_ctx(ctx)?;
10✔
1398
                for root in roots {
20✔
1399
                    ctx.write_all(b" ")?;
10✔
1400
                    root.encode_ctx(ctx)?;
10✔
1401
                }
1402
            }
1403
            #[cfg(feature = "ext_id")]
1404
            Data::Id { parameters } => {
2✔
1405
                ctx.write_all(b"* ID ")?;
2✔
1406

1407
                match parameters {
2✔
1408
                    Some(parameters) => {
×
1409
                        if let Some((first, tail)) = parameters.split_first() {
×
1410
                            ctx.write_all(b"(")?;
×
1411

1412
                            first.0.encode_ctx(ctx)?;
×
1413
                            ctx.write_all(b" ")?;
×
1414
                            first.1.encode_ctx(ctx)?;
×
1415

1416
                            for parameter in tail {
×
1417
                                ctx.write_all(b" ")?;
×
1418
                                parameter.0.encode_ctx(ctx)?;
×
1419
                                ctx.write_all(b" ")?;
×
1420
                                parameter.1.encode_ctx(ctx)?;
×
1421
                            }
1422

1423
                            ctx.write_all(b")")?;
×
1424
                        } else {
1425
                            #[cfg(not(feature = "quirk_id_empty_to_nil"))]
1426
                            {
1427
                                ctx.write_all(b"()")?;
1428
                            }
1429
                            #[cfg(feature = "quirk_id_empty_to_nil")]
1430
                            {
1431
                                ctx.write_all(b"NIL")?;
×
1432
                            }
1433
                        }
1434
                    }
1435
                    None => {
1436
                        ctx.write_all(b"NIL")?;
2✔
1437
                    }
1438
                }
1439
            }
1440
            #[cfg(feature = "ext_metadata")]
1441
            Data::Metadata { mailbox, items } => {
4✔
1442
                ctx.write_all(b"* METADATA ")?;
4✔
1443
                mailbox.encode_ctx(ctx)?;
4✔
1444
                ctx.write_all(b" ")?;
4✔
1445
                items.encode_ctx(ctx)?;
4✔
1446
            }
1447
        }
1448

1449
        ctx.write_all(b"\r\n")
1,208✔
1450
    }
1,208✔
1451
}
1452

1453
impl<'a> EncodeIntoContext for FlagNameAttribute<'a> {
1454
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
156✔
1455
        write!(ctx, "{}", self)
156✔
1456
    }
156✔
1457
}
1458

1459
impl EncodeIntoContext for QuotedChar {
1460
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
422✔
1461
        match self.inner() {
422✔
1462
            '\\' => ctx.write_all(b"\\\\"),
×
1463
            '"' => ctx.write_all(b"\\\""),
×
1464
            other => ctx.write_all(&[other as u8]),
422✔
1465
        }
1466
    }
422✔
1467
}
1468

1469
impl EncodeIntoContext for StatusDataItem {
1470
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
76✔
1471
        match self {
76✔
1472
            Self::Messages(count) => {
32✔
1473
                ctx.write_all(b"MESSAGES ")?;
32✔
1474
                count.encode_ctx(ctx)
32✔
1475
            }
1476
            Self::Recent(count) => {
2✔
1477
                ctx.write_all(b"RECENT ")?;
2✔
1478
                count.encode_ctx(ctx)
2✔
1479
            }
1480
            Self::UidNext(next) => {
30✔
1481
                ctx.write_all(b"UIDNEXT ")?;
30✔
1482
                next.encode_ctx(ctx)
30✔
1483
            }
1484
            Self::UidValidity(identifier) => {
2✔
1485
                ctx.write_all(b"UIDVALIDITY ")?;
2✔
1486
                identifier.encode_ctx(ctx)
2✔
1487
            }
1488
            Self::Unseen(count) => {
2✔
1489
                ctx.write_all(b"UNSEEN ")?;
2✔
1490
                count.encode_ctx(ctx)
2✔
1491
            }
1492
            Self::Deleted(count) => {
4✔
1493
                ctx.write_all(b"DELETED ")?;
4✔
1494
                count.encode_ctx(ctx)
4✔
1495
            }
1496
            Self::DeletedStorage(count) => {
4✔
1497
                ctx.write_all(b"DELETED-STORAGE ")?;
4✔
1498
                count.encode_ctx(ctx)
4✔
1499
            }
1500
        }
1501
    }
76✔
1502
}
1503

1504
impl<'a> EncodeIntoContext for MessageDataItem<'a> {
1505
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
298✔
1506
        match self {
298✔
1507
            Self::BodyExt {
22✔
1508
                section,
22✔
1509
                origin,
22✔
1510
                data,
22✔
1511
            } => {
22✔
1512
                ctx.write_all(b"BODY[")?;
22✔
1513
                if let Some(section) = section {
22✔
1514
                    section.encode_ctx(ctx)?;
14✔
1515
                }
8✔
1516
                ctx.write_all(b"]")?;
22✔
1517
                if let Some(origin) = origin {
22✔
1518
                    write!(ctx, "<{origin}>")?;
2✔
1519
                }
20✔
1520
                ctx.write_all(b" ")?;
22✔
1521
                data.encode_ctx(ctx)
22✔
1522
            }
1523
            // FIXME: do not return body-ext-1part and body-ext-mpart here
1524
            Self::Body(body) => {
16✔
1525
                ctx.write_all(b"BODY ")?;
16✔
1526
                body.encode_ctx(ctx)
16✔
1527
            }
1528
            Self::BodyStructure(body) => {
4✔
1529
                ctx.write_all(b"BODYSTRUCTURE ")?;
4✔
1530
                body.encode_ctx(ctx)
4✔
1531
            }
1532
            Self::Envelope(envelope) => {
16✔
1533
                ctx.write_all(b"ENVELOPE ")?;
16✔
1534
                envelope.encode_ctx(ctx)
16✔
1535
            }
1536
            Self::Flags(flags) => {
142✔
1537
                ctx.write_all(b"FLAGS (")?;
142✔
1538
                join_serializable(flags, b" ", ctx)?;
142✔
1539
                ctx.write_all(b")")
142✔
1540
            }
1541
            Self::InternalDate(datetime) => {
16✔
1542
                ctx.write_all(b"INTERNALDATE ")?;
16✔
1543
                datetime.encode_ctx(ctx)
16✔
1544
            }
1545
            Self::Rfc822(nstring) => {
4✔
1546
                ctx.write_all(b"RFC822 ")?;
4✔
1547
                nstring.encode_ctx(ctx)
4✔
1548
            }
1549
            Self::Rfc822Header(nstring) => {
2✔
1550
                ctx.write_all(b"RFC822.HEADER ")?;
2✔
1551
                nstring.encode_ctx(ctx)
2✔
1552
            }
1553
            Self::Rfc822Size(size) => write!(ctx, "RFC822.SIZE {size}"),
30✔
1554
            Self::Rfc822Text(nstring) => {
2✔
1555
                ctx.write_all(b"RFC822.TEXT ")?;
2✔
1556
                nstring.encode_ctx(ctx)
2✔
1557
            }
1558
            Self::Uid(uid) => write!(ctx, "UID {uid}"),
44✔
1559
            Self::Binary { section, value } => {
×
1560
                ctx.write_all(b"BINARY[")?;
×
1561
                join_serializable(section, b".", ctx)?;
×
1562
                ctx.write_all(b"] ")?;
×
1563
                value.encode_ctx(ctx)
×
1564
            }
1565
            Self::BinarySize { section, size } => {
×
1566
                ctx.write_all(b"BINARY.SIZE[")?;
×
1567
                join_serializable(section, b".", ctx)?;
×
1568
                ctx.write_all(b"] ")?;
×
1569
                size.encode_ctx(ctx)
×
1570
            }
1571
        }
1572
    }
298✔
1573
}
1574

1575
impl<'a> EncodeIntoContext for NString<'a> {
1576
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
504✔
1577
        match &self.0 {
504✔
1578
            Some(imap_str) => imap_str.encode_ctx(ctx),
308✔
1579
            None => ctx.write_all(b"NIL"),
196✔
1580
        }
1581
    }
504✔
1582
}
1583

1584
impl<'a> EncodeIntoContext for NString8<'a> {
1585
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
8✔
1586
        match self {
8✔
1587
            NString8::NString(nstring) => nstring.encode_ctx(ctx),
6✔
1588
            NString8::Literal8(literal8) => literal8.encode_ctx(ctx),
2✔
1589
        }
1590
    }
8✔
1591
}
1592

1593
impl<'a> EncodeIntoContext for BodyStructure<'a> {
1594
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
38✔
1595
        ctx.write_all(b"(")?;
38✔
1596
        match self {
38✔
1597
            BodyStructure::Single {
1598
                body,
26✔
1599
                extension_data: extension,
26✔
1600
            } => {
26✔
1601
                body.encode_ctx(ctx)?;
26✔
1602
                if let Some(extension) = extension {
26✔
1603
                    ctx.write_all(b" ")?;
4✔
1604
                    extension.encode_ctx(ctx)?;
4✔
1605
                }
22✔
1606
            }
1607
            BodyStructure::Multi {
1608
                bodies,
12✔
1609
                subtype,
12✔
1610
                extension_data,
12✔
1611
            } => {
1612
                for body in bodies.as_ref() {
12✔
1613
                    body.encode_ctx(ctx)?;
12✔
1614
                }
1615
                ctx.write_all(b" ")?;
12✔
1616
                subtype.encode_ctx(ctx)?;
12✔
1617

1618
                if let Some(extension) = extension_data {
12✔
1619
                    ctx.write_all(b" ")?;
×
1620
                    extension.encode_ctx(ctx)?;
×
1621
                }
12✔
1622
            }
1623
        }
1624
        ctx.write_all(b")")
38✔
1625
    }
38✔
1626
}
1627

1628
impl<'a> EncodeIntoContext for Body<'a> {
1629
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1630
        match self.specific {
26✔
1631
            SpecificFields::Basic {
1632
                r#type: ref type_,
4✔
1633
                ref subtype,
4✔
1634
            } => {
4✔
1635
                type_.encode_ctx(ctx)?;
4✔
1636
                ctx.write_all(b" ")?;
4✔
1637
                subtype.encode_ctx(ctx)?;
4✔
1638
                ctx.write_all(b" ")?;
4✔
1639
                self.basic.encode_ctx(ctx)
4✔
1640
            }
1641
            SpecificFields::Message {
1642
                ref envelope,
×
1643
                ref body_structure,
×
1644
                number_of_lines,
×
1645
            } => {
×
1646
                ctx.write_all(b"\"MESSAGE\" \"RFC822\" ")?;
×
1647
                self.basic.encode_ctx(ctx)?;
×
1648
                ctx.write_all(b" ")?;
×
1649
                envelope.encode_ctx(ctx)?;
×
1650
                ctx.write_all(b" ")?;
×
1651
                body_structure.encode_ctx(ctx)?;
×
1652
                ctx.write_all(b" ")?;
×
1653
                write!(ctx, "{number_of_lines}")
×
1654
            }
1655
            SpecificFields::Text {
1656
                ref subtype,
22✔
1657
                number_of_lines,
22✔
1658
            } => {
22✔
1659
                ctx.write_all(b"\"TEXT\" ")?;
22✔
1660
                subtype.encode_ctx(ctx)?;
22✔
1661
                ctx.write_all(b" ")?;
22✔
1662
                self.basic.encode_ctx(ctx)?;
22✔
1663
                ctx.write_all(b" ")?;
22✔
1664
                write!(ctx, "{number_of_lines}")
22✔
1665
            }
1666
        }
1667
    }
26✔
1668
}
1669

1670
impl<'a> EncodeIntoContext for BasicFields<'a> {
1671
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1672
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
26✔
1673
        ctx.write_all(b" ")?;
26✔
1674
        self.id.encode_ctx(ctx)?;
26✔
1675
        ctx.write_all(b" ")?;
26✔
1676
        self.description.encode_ctx(ctx)?;
26✔
1677
        ctx.write_all(b" ")?;
26✔
1678
        self.content_transfer_encoding.encode_ctx(ctx)?;
26✔
1679
        ctx.write_all(b" ")?;
26✔
1680
        write!(ctx, "{}", self.size)
26✔
1681
    }
26✔
1682
}
1683

1684
impl<'a> EncodeIntoContext for Envelope<'a> {
1685
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
16✔
1686
        ctx.write_all(b"(")?;
16✔
1687
        self.date.encode_ctx(ctx)?;
16✔
1688
        ctx.write_all(b" ")?;
16✔
1689
        self.subject.encode_ctx(ctx)?;
16✔
1690
        ctx.write_all(b" ")?;
16✔
1691
        List1OrNil(&self.from, b"").encode_ctx(ctx)?;
16✔
1692
        ctx.write_all(b" ")?;
16✔
1693
        List1OrNil(&self.sender, b"").encode_ctx(ctx)?;
16✔
1694
        ctx.write_all(b" ")?;
16✔
1695
        List1OrNil(&self.reply_to, b"").encode_ctx(ctx)?;
16✔
1696
        ctx.write_all(b" ")?;
16✔
1697
        List1OrNil(&self.to, b"").encode_ctx(ctx)?;
16✔
1698
        ctx.write_all(b" ")?;
16✔
1699
        List1OrNil(&self.cc, b"").encode_ctx(ctx)?;
16✔
1700
        ctx.write_all(b" ")?;
16✔
1701
        List1OrNil(&self.bcc, b"").encode_ctx(ctx)?;
16✔
1702
        ctx.write_all(b" ")?;
16✔
1703
        self.in_reply_to.encode_ctx(ctx)?;
16✔
1704
        ctx.write_all(b" ")?;
16✔
1705
        self.message_id.encode_ctx(ctx)?;
16✔
1706
        ctx.write_all(b")")
16✔
1707
    }
16✔
1708
}
1709

1710
impl<'a> EncodeIntoContext for Address<'a> {
1711
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
84✔
1712
        ctx.write_all(b"(")?;
84✔
1713
        self.name.encode_ctx(ctx)?;
84✔
1714
        ctx.write_all(b" ")?;
84✔
1715
        self.adl.encode_ctx(ctx)?;
84✔
1716
        ctx.write_all(b" ")?;
84✔
1717
        self.mailbox.encode_ctx(ctx)?;
84✔
1718
        ctx.write_all(b" ")?;
84✔
1719
        self.host.encode_ctx(ctx)?;
84✔
1720
        ctx.write_all(b")")?;
84✔
1721

1722
        Ok(())
84✔
1723
    }
84✔
1724
}
1725

1726
impl<'a> EncodeIntoContext for SinglePartExtensionData<'a> {
1727
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1728
        self.md5.encode_ctx(ctx)?;
6✔
1729

1730
        if let Some(disposition) = &self.tail {
6✔
1731
            ctx.write_all(b" ")?;
6✔
1732
            disposition.encode_ctx(ctx)?;
6✔
1733
        }
×
1734

1735
        Ok(())
6✔
1736
    }
6✔
1737
}
1738

1739
impl<'a> EncodeIntoContext for MultiPartExtensionData<'a> {
1740
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
×
1741
        List1AttributeValueOrNil(&self.parameter_list).encode_ctx(ctx)?;
×
1742

1743
        if let Some(disposition) = &self.tail {
×
1744
            ctx.write_all(b" ")?;
×
1745
            disposition.encode_ctx(ctx)?;
×
1746
        }
×
1747

1748
        Ok(())
×
1749
    }
×
1750
}
1751

1752
impl<'a> EncodeIntoContext for Disposition<'a> {
1753
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1754
        match &self.disposition {
6✔
1755
            Some((s, param)) => {
×
1756
                ctx.write_all(b"(")?;
×
1757
                s.encode_ctx(ctx)?;
×
1758
                ctx.write_all(b" ")?;
×
1759
                List1AttributeValueOrNil(param).encode_ctx(ctx)?;
×
1760
                ctx.write_all(b")")?;
×
1761
            }
1762
            None => ctx.write_all(b"NIL")?,
6✔
1763
        }
1764

1765
        if let Some(language) = &self.tail {
6✔
1766
            ctx.write_all(b" ")?;
6✔
1767
            language.encode_ctx(ctx)?;
6✔
1768
        }
×
1769

1770
        Ok(())
6✔
1771
    }
6✔
1772
}
1773

1774
impl<'a> EncodeIntoContext for Language<'a> {
1775
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1776
        List1OrNil(&self.language, b" ").encode_ctx(ctx)?;
6✔
1777

1778
        if let Some(location) = &self.tail {
6✔
1779
            ctx.write_all(b" ")?;
6✔
1780
            location.encode_ctx(ctx)?;
6✔
1781
        }
×
1782

1783
        Ok(())
6✔
1784
    }
6✔
1785
}
1786

1787
impl<'a> EncodeIntoContext for Location<'a> {
1788
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1789
        self.location.encode_ctx(ctx)?;
6✔
1790

1791
        for body_extension in &self.extensions {
10✔
1792
            ctx.write_all(b" ")?;
4✔
1793
            body_extension.encode_ctx(ctx)?;
4✔
1794
        }
1795

1796
        Ok(())
6✔
1797
    }
6✔
1798
}
1799

1800
impl<'a> EncodeIntoContext for BodyExtension<'a> {
1801
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
6✔
1802
        match self {
6✔
1803
            BodyExtension::NString(nstring) => nstring.encode_ctx(ctx),
×
1804
            BodyExtension::Number(number) => number.encode_ctx(ctx),
4✔
1805
            BodyExtension::List(list) => {
2✔
1806
                ctx.write_all(b"(")?;
2✔
1807
                join_serializable(list.as_ref(), b" ", ctx)?;
2✔
1808
                ctx.write_all(b")")
2✔
1809
            }
1810
        }
1811
    }
6✔
1812
}
1813

1814
impl EncodeIntoContext for ChronoDateTime<FixedOffset> {
1815
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
20✔
1816
        write!(ctx, "\"{}\"", self.format("%d-%b-%Y %H:%M:%S %z"))
20✔
1817
    }
20✔
1818
}
1819

1820
impl<'a> EncodeIntoContext for CommandContinuationRequest<'a> {
1821
    fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
14✔
1822
        match self {
14✔
1823
            Self::Basic(continue_basic) => match continue_basic.code() {
14✔
1824
                Some(code) => {
×
1825
                    ctx.write_all(b"+ [")?;
×
1826
                    code.encode_ctx(ctx)?;
×
1827
                    ctx.write_all(b"] ")?;
×
1828
                    continue_basic.text().encode_ctx(ctx)?;
×
1829
                    ctx.write_all(b"\r\n")
×
1830
                }
1831
                None => {
1832
                    ctx.write_all(b"+ ")?;
14✔
1833
                    continue_basic.text().encode_ctx(ctx)?;
14✔
1834
                    ctx.write_all(b"\r\n")
14✔
1835
                }
1836
            },
1837
            Self::Base64(data) => {
×
1838
                ctx.write_all(b"+ ")?;
×
1839
                ctx.write_all(base64.encode(data).as_bytes())?;
×
1840
                ctx.write_all(b"\r\n")
×
1841
            }
1842
        }
1843
    }
14✔
1844
}
1845

1846
pub(crate) mod utils {
1847
    use std::io::Write;
1848

1849
    use super::{EncodeContext, EncodeIntoContext};
1850

1851
    pub struct List1OrNil<'a, T>(pub &'a Vec<T>, pub &'a [u8]);
1852

1853
    pub struct List1AttributeValueOrNil<'a, T>(pub &'a Vec<(T, T)>);
1854

1855
    pub(crate) fn join_serializable<I: EncodeIntoContext>(
1,502✔
1856
        elements: &[I],
1,502✔
1857
        sep: &[u8],
1,502✔
1858
        ctx: &mut EncodeContext,
1,502✔
1859
    ) -> std::io::Result<()> {
1,502✔
1860
        if let Some((last, head)) = elements.split_last() {
1,502✔
1861
            for item in head {
2,224✔
1862
                item.encode_ctx(ctx)?;
1,006✔
1863
                ctx.write_all(sep)?;
1,006✔
1864
            }
1865

1866
            last.encode_ctx(ctx)
1,218✔
1867
        } else {
1868
            Ok(())
284✔
1869
        }
1870
    }
1,502✔
1871

1872
    impl<'a, T> EncodeIntoContext for List1OrNil<'a, T>
1873
    where
1874
        T: EncodeIntoContext,
1875
    {
1876
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
102✔
1877
            if let Some((last, head)) = self.0.split_last() {
102✔
1878
                ctx.write_all(b"(")?;
70✔
1879

1880
                for item in head {
84✔
1881
                    item.encode_ctx(ctx)?;
14✔
1882
                    ctx.write_all(self.1)?;
14✔
1883
                }
1884

1885
                last.encode_ctx(ctx)?;
70✔
1886

1887
                ctx.write_all(b")")
70✔
1888
            } else {
1889
                ctx.write_all(b"NIL")
32✔
1890
            }
1891
        }
102✔
1892
    }
1893

1894
    impl<'a, T> EncodeIntoContext for List1AttributeValueOrNil<'a, T>
1895
    where
1896
        T: EncodeIntoContext,
1897
    {
1898
        fn encode_ctx(&self, ctx: &mut EncodeContext) -> std::io::Result<()> {
26✔
1899
            if let Some((last, head)) = self.0.split_last() {
26✔
1900
                ctx.write_all(b"(")?;
14✔
1901

1902
                for (attribute, value) in head {
14✔
1903
                    attribute.encode_ctx(ctx)?;
×
1904
                    ctx.write_all(b" ")?;
×
1905
                    value.encode_ctx(ctx)?;
×
1906
                    ctx.write_all(b" ")?;
×
1907
                }
1908

1909
                let (attribute, value) = last;
14✔
1910
                attribute.encode_ctx(ctx)?;
14✔
1911
                ctx.write_all(b" ")?;
14✔
1912
                value.encode_ctx(ctx)?;
14✔
1913

1914
                ctx.write_all(b")")
14✔
1915
            } else {
1916
                ctx.write_all(b"NIL")
12✔
1917
            }
1918
        }
26✔
1919
    }
1920
}
1921

1922
#[cfg(test)]
1923
mod tests {
1924
    use std::num::NonZeroU32;
1925

1926
    use imap_types::{
1927
        auth::AuthMechanism,
1928
        command::{Command, CommandBody},
1929
        core::{AString, Literal, NString, Vec1},
1930
        fetch::MessageDataItem,
1931
        response::{Data, Response},
1932
        utils::escape_byte_string,
1933
    };
1934

1935
    use super::*;
1936

1937
    #[test]
1938
    fn test_api_encoder_usage() {
2✔
1939
        let cmd = Command::new(
2✔
1940
            "A",
2✔
1941
            CommandBody::login(
2✔
1942
                AString::from(Literal::unvalidated_non_sync(b"alice".as_ref())),
2✔
1943
                "password",
2✔
1944
            )
2✔
1945
            .unwrap(),
2✔
1946
        )
2✔
1947
        .unwrap();
2✔
1948

2✔
1949
        // Dump.
2✔
1950
        let got_encoded = CommandCodec::default().encode(&cmd).dump();
2✔
1951

2✔
1952
        // Encoded.
2✔
1953
        let encoded = CommandCodec::default().encode(&cmd);
2✔
1954

2✔
1955
        let mut out = Vec::new();
2✔
1956

1957
        for x in encoded {
8✔
1958
            match x {
6✔
1959
                Fragment::Line { data } => {
4✔
1960
                    println!("C: {}", escape_byte_string(&data));
4✔
1961
                    out.extend_from_slice(&data);
4✔
1962
                }
4✔
1963
                Fragment::Literal { data, mode } => {
2✔
1964
                    match mode {
2✔
1965
                        LiteralMode::Sync => println!("C: <Waiting for continuation request>"),
×
1966
                        LiteralMode::NonSync => println!("C: <Skipped continuation request>"),
2✔
1967
                    }
1968

1969
                    println!("C: {}", escape_byte_string(&data));
2✔
1970
                    out.extend_from_slice(&data);
2✔
1971
                }
1972
            }
1973
        }
1974

1975
        assert_eq!(got_encoded, out);
2✔
1976
    }
2✔
1977

1978
    #[test]
1979
    fn test_encode_command() {
2✔
1980
        kat_encoder::<CommandCodec, Command<'_>, &[Fragment]>(&[
2✔
1981
            (
2✔
1982
                Command::new("A", CommandBody::login("alice", "pass").unwrap()).unwrap(),
2✔
1983
                [Fragment::Line {
2✔
1984
                    data: b"A LOGIN alice pass\r\n".to_vec(),
2✔
1985
                }]
2✔
1986
                .as_ref(),
2✔
1987
            ),
2✔
1988
            (
2✔
1989
                Command::new(
2✔
1990
                    "A",
2✔
1991
                    CommandBody::login("alice", b"\xCA\xFE".as_ref()).unwrap(),
2✔
1992
                )
2✔
1993
                .unwrap(),
2✔
1994
                [
2✔
1995
                    Fragment::Line {
2✔
1996
                        data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
1997
                    },
2✔
1998
                    Fragment::Literal {
2✔
1999
                        data: b"\xCA\xFE".to_vec(),
2✔
2000
                        mode: LiteralMode::Sync,
2✔
2001
                    },
2✔
2002
                    Fragment::Line {
2✔
2003
                        data: b"\r\n".to_vec(),
2✔
2004
                    },
2✔
2005
                ]
2✔
2006
                .as_ref(),
2✔
2007
            ),
2✔
2008
            (
2✔
2009
                Command::new("A", CommandBody::authenticate(AuthMechanism::Login)).unwrap(),
2✔
2010
                [Fragment::Line {
2✔
2011
                    data: b"A AUTHENTICATE LOGIN\r\n".to_vec(),
2✔
2012
                }]
2✔
2013
                .as_ref(),
2✔
2014
            ),
2✔
2015
            (
2✔
2016
                Command::new(
2✔
2017
                    "A",
2✔
2018
                    CommandBody::authenticate_with_ir(AuthMechanism::Login, b"alice".as_ref()),
2✔
2019
                )
2✔
2020
                .unwrap(),
2✔
2021
                [Fragment::Line {
2✔
2022
                    data: b"A AUTHENTICATE LOGIN YWxpY2U=\r\n".to_vec(),
2✔
2023
                }]
2✔
2024
                .as_ref(),
2✔
2025
            ),
2✔
2026
            (
2✔
2027
                Command::new("A", CommandBody::authenticate(AuthMechanism::Plain)).unwrap(),
2✔
2028
                [Fragment::Line {
2✔
2029
                    data: b"A AUTHENTICATE PLAIN\r\n".to_vec(),
2✔
2030
                }]
2✔
2031
                .as_ref(),
2✔
2032
            ),
2✔
2033
            (
2✔
2034
                Command::new(
2✔
2035
                    "A",
2✔
2036
                    CommandBody::authenticate_with_ir(
2✔
2037
                        AuthMechanism::Plain,
2✔
2038
                        b"\x00alice\x00pass".as_ref(),
2✔
2039
                    ),
2✔
2040
                )
2✔
2041
                .unwrap(),
2✔
2042
                [Fragment::Line {
2✔
2043
                    data: b"A AUTHENTICATE PLAIN AGFsaWNlAHBhc3M=\r\n".to_vec(),
2✔
2044
                }]
2✔
2045
                .as_ref(),
2✔
2046
            ),
2✔
2047
        ]);
2✔
2048
    }
2✔
2049

2050
    #[test]
2051
    fn test_encode_response() {
2✔
2052
        kat_encoder::<ResponseCodec, Response<'_>, &[Fragment]>(&[
2✔
2053
            (
2✔
2054
                Response::Data(Data::Fetch {
2✔
2055
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2056
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2057
                        section: None,
2✔
2058
                        origin: None,
2✔
2059
                        data: NString::from(Literal::unvalidated(b"ABCDE".as_ref())),
2✔
2060
                    }),
2✔
2061
                }),
2✔
2062
                [
2✔
2063
                    Fragment::Line {
2✔
2064
                        data: b"* 12345 FETCH (BODY[] {5}\r\n".to_vec(),
2✔
2065
                    },
2✔
2066
                    Fragment::Literal {
2✔
2067
                        data: b"ABCDE".to_vec(),
2✔
2068
                        mode: LiteralMode::Sync,
2✔
2069
                    },
2✔
2070
                    Fragment::Line {
2✔
2071
                        data: b")\r\n".to_vec(),
2✔
2072
                    },
2✔
2073
                ]
2✔
2074
                .as_ref(),
2✔
2075
            ),
2✔
2076
            (
2✔
2077
                Response::Data(Data::Fetch {
2✔
2078
                    seq: NonZeroU32::new(12345).unwrap(),
2✔
2079
                    items: Vec1::from(MessageDataItem::BodyExt {
2✔
2080
                        section: None,
2✔
2081
                        origin: None,
2✔
2082
                        data: NString::from(Literal::unvalidated_non_sync(b"ABCDE".as_ref())),
2✔
2083
                    }),
2✔
2084
                }),
2✔
2085
                [
2✔
2086
                    Fragment::Line {
2✔
2087
                        data: b"* 12345 FETCH (BODY[] {5+}\r\n".to_vec(),
2✔
2088
                    },
2✔
2089
                    Fragment::Literal {
2✔
2090
                        data: b"ABCDE".to_vec(),
2✔
2091
                        mode: LiteralMode::NonSync,
2✔
2092
                    },
2✔
2093
                    Fragment::Line {
2✔
2094
                        data: b")\r\n".to_vec(),
2✔
2095
                    },
2✔
2096
                ]
2✔
2097
                .as_ref(),
2✔
2098
            ),
2✔
2099
        ])
2✔
2100
    }
2✔
2101

2102
    fn kat_encoder<'a, E, M, F>(tests: &'a [(M, F)])
4✔
2103
    where
4✔
2104
        E: Encoder<Message<'a> = M> + Default,
4✔
2105
        F: AsRef<[Fragment]>,
4✔
2106
    {
4✔
2107
        for (i, (obj, actions)) in tests.iter().enumerate() {
16✔
2108
            println!("# Testing {i}");
16✔
2109

16✔
2110
            let encoder = E::default().encode(obj);
16✔
2111
            let actions = actions.as_ref();
16✔
2112

16✔
2113
            assert_eq!(encoder.collect::<Vec<_>>(), actions);
16✔
2114
        }
2115
    }
4✔
2116

2117
    #[cfg(feature = "serde")]
2118
    #[test]
2119
    fn test_serialize_fragment() {
2✔
2120
        let fragments = [
2✔
2121
            Fragment::Line {
2✔
2122
                data: b"A LOGIN alice {2}\r\n".to_vec(),
2✔
2123
            },
2✔
2124
            Fragment::Literal {
2✔
2125
                data: b"\xCA\xFE".to_vec(),
2✔
2126
                mode: LiteralMode::Sync,
2✔
2127
            },
2✔
2128
            Fragment::Line {
2✔
2129
                data: b"\r\n".to_vec(),
2✔
2130
            },
2✔
2131
        ];
2✔
2132

2✔
2133
        let json = serde_json::to_string(&fragments).unwrap();
2✔
2134
        assert_eq!(
2✔
2135
            json,
2✔
2136
            r#"[{"Line":{"data":[65,32,76,79,71,73,78,32,97,108,105,99,101,32,123,50,125,13,10]}},{"Literal":{"data":[202,254],"mode":"Sync"}},{"Line":{"data":[13,10]}}]"#
2✔
2137
        );
2✔
2138
        assert_eq!(
2✔
2139
            serde_json::from_str::<[Fragment; 3]>(&json).unwrap(),
2✔
2140
            fragments
2✔
2141
        );
2✔
2142
    }
2✔
2143
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc