• 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

95.02
/imap-codec/src/command.rs
1
use std::borrow::Cow;
2

3
#[cfg(not(feature = "quirk_crlf_relaxed"))]
4
use abnf_core::streaming::crlf;
5
#[cfg(feature = "quirk_crlf_relaxed")]
6
use abnf_core::streaming::crlf_relaxed as crlf;
7
use abnf_core::streaming::sp;
8
use imap_types::{
9
    auth::AuthMechanism,
10
    command::{Command, CommandBody},
11
    core::AString,
12
    extensions::binary::LiteralOrLiteral8,
13
    fetch::{Macro, MacroOrMessageDataItemNames},
14
    flag::{Flag, StoreResponse, StoreType},
15
    secret::Secret,
16
};
17
use nom::{
18
    branch::alt,
19
    bytes::streaming::{tag, tag_no_case},
20
    combinator::{map, opt, value},
21
    multi::{separated_list0, separated_list1},
22
    sequence::{delimited, preceded, terminated, tuple},
23
};
24

25
#[cfg(feature = "ext_id")]
26
use crate::extensions::id::id;
27
#[cfg(feature = "ext_metadata")]
28
use crate::extensions::metadata::{getmetadata, setmetadata};
29
use crate::{
30
    auth::auth_type,
31
    core::{astring, base64, literal, tag_imap},
32
    datetime::date_time,
33
    decode::{IMAPErrorKind, IMAPResult},
34
    extensions::{
35
        binary::literal8,
36
        compress::compress,
37
        enable::enable,
38
        idle::idle,
39
        quota::{getquota, getquotaroot, setquota},
40
        r#move::r#move,
41
        sort::sort,
42
        thread::thread,
43
        uidplus::uid_expunge,
44
    },
45
    fetch::fetch_att,
46
    flag::{flag, flag_list},
47
    mailbox::{list_mailbox, mailbox},
48
    search::search,
49
    sequence::sequence_set,
50
    status::status_att,
51
};
52

53
/// `command = tag SP (
54
///                     command-any /
55
///                     command-auth /
56
///                     command-nonauth /
57
///                     command-select
58
///                   ) CRLF`
59
pub(crate) fn command(input: &[u8]) -> IMAPResult<&[u8], Command> {
2,344✔
60
    let mut parser_tag = terminated(tag_imap, sp);
2,344✔
61
    let mut parser_body = terminated(
2,344✔
62
        alt((command_any, command_auth, command_nonauth, command_select)),
2,344✔
63
        crlf,
2,344✔
64
    );
2,344✔
65

66
    let (remaining, obtained_tag) = parser_tag(input)?;
2,344✔
67

68
    match parser_body(remaining) {
2,328✔
69
        Ok((remaining, body)) => Ok((
2,252✔
70
            remaining,
2,252✔
71
            Command {
2,252✔
72
                tag: obtained_tag,
2,252✔
73
                body,
2,252✔
74
            },
2,252✔
75
        )),
2,252✔
76
        Err(mut error) => {
76✔
77
            // If we got an `IMAPErrorKind::Literal`, we fill in the missing `tag`.
78
            if let nom::Err::Error(ref mut err) | nom::Err::Failure(ref mut err) = error {
76✔
79
                if let IMAPErrorKind::Literal { ref mut tag, .. } = err.kind {
20✔
80
                    *tag = Some(obtained_tag);
12✔
81
                }
12✔
82
            }
56✔
83

84
            Err(error)
76✔
85
        }
86
    }
87
}
2,344✔
88

89
// # Command Any
90

91
/// ```abnf
92
/// command-any = "CAPABILITY" /
93
///               "LOGOUT" /
94
///               "NOOP" /
95
///               x-command /
96
///               id ; adds id command to command_any (See RFC 2971)
97
/// ```
98
///
99
/// Note: Valid in all states
100
pub(crate) fn command_any(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
2,328✔
101
    alt((
2,328✔
102
        value(CommandBody::Capability, tag_no_case(b"CAPABILITY")),
2,328✔
103
        value(CommandBody::Logout, tag_no_case(b"LOGOUT")),
2,328✔
104
        value(CommandBody::Noop, tag_no_case(b"NOOP")),
2,328✔
105
        // x-command = "X" atom <experimental command arguments>
2,328✔
106
        #[cfg(feature = "ext_id")]
2,328✔
107
        map(id, |parameters| CommandBody::Id { parameters }),
2,336✔
108
    ))(input)
2,328✔
109
}
2,328✔
110

111
// # Command Auth
112

113
/// ```abnf
114
/// command-auth = append /
115
///                create /
116
///                delete /
117
///                examine /
118
///                list /
119
///                lsub /
120
///                rename /
121
///                select /
122
///                status /
123
///                subscribe /
124
///                unsubscribe /
125
///                idle /         ; RFC 2177
126
///                enable /       ; RFC 5161
127
///                compress /     ; RFC 4978
128
///                getquota /     ; RFC 9208
129
///                getquotaroot / ; RFC 9208
130
///                setquota /     ; RFC 9208
131
///                setmetadata /  ; RFC 5464
132
///                getmetadata    ; RFC 5464
133
/// ```
134
///
135
/// Note: Valid only in Authenticated or Selected state
136
pub(crate) fn command_auth(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,966✔
137
    alt((
1,966✔
138
        append,
1,966✔
139
        create,
1,966✔
140
        delete,
1,966✔
141
        examine,
1,966✔
142
        list,
1,966✔
143
        lsub,
1,966✔
144
        rename,
1,966✔
145
        select,
1,966✔
146
        status,
1,966✔
147
        subscribe,
1,966✔
148
        unsubscribe,
1,966✔
149
        idle,
1,966✔
150
        enable,
1,966✔
151
        compress,
1,966✔
152
        getquota,
1,966✔
153
        getquotaroot,
1,966✔
154
        setquota,
1,966✔
155
        #[cfg(feature = "ext_metadata")]
1,966✔
156
        setmetadata,
1,966✔
157
        #[cfg(feature = "ext_metadata")]
1,966✔
158
        getmetadata,
1,966✔
159
    ))(input)
1,966✔
160
}
1,966✔
161

162
/// `append = "APPEND" SP mailbox [SP flag-list] [SP date-time] SP literal`
163
pub(crate) fn append(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,966✔
164
    let mut parser = tuple((
1,966✔
165
        tag_no_case(b"APPEND "),
1,966✔
166
        mailbox,
1,966✔
167
        opt(preceded(sp, flag_list)),
1,966✔
168
        opt(preceded(sp, date_time)),
1,966✔
169
        sp,
1,966✔
170
        alt((
1,966✔
171
            map(literal, LiteralOrLiteral8::Literal),
1,966✔
172
            map(literal8, LiteralOrLiteral8::Literal8),
1,966✔
173
        )),
1,966✔
174
    ));
1,966✔
175

176
    let (remaining, (_, mailbox, flags, date, _, message)) = parser(input)?;
1,966✔
177

178
    Ok((
×
179
        remaining,
×
180
        CommandBody::Append {
×
181
            mailbox,
×
182
            flags: flags.unwrap_or_default(),
×
183
            date,
×
184
            message,
×
185
        },
×
186
    ))
×
187
}
1,966✔
188

189
/// `create = "CREATE" SP mailbox`
190
///
191
/// Note: Use of INBOX gives a NO error
192
pub(crate) fn create(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,966✔
193
    let mut parser = preceded(tag_no_case(b"CREATE "), mailbox);
1,966✔
194

195
    let (remaining, mailbox) = parser(input)?;
1,966✔
196

197
    Ok((remaining, CommandBody::Create { mailbox }))
56✔
198
}
1,966✔
199

200
/// `delete = "DELETE" SP mailbox`
201
///
202
/// Note: Use of INBOX gives a NO error
203
pub(crate) fn delete(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,910✔
204
    let mut parser = preceded(tag_no_case(b"DELETE "), mailbox);
1,910✔
205

206
    let (remaining, mailbox) = parser(input)?;
1,910✔
207

208
    Ok((remaining, CommandBody::Delete { mailbox }))
168✔
209
}
1,910✔
210

211
/// `examine = "EXAMINE" SP mailbox`
212
pub(crate) fn examine(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,742✔
213
    let mut parser = preceded(tag_no_case(b"EXAMINE "), mailbox);
1,742✔
214

215
    let (remaining, mailbox) = parser(input)?;
1,742✔
216

217
    Ok((remaining, CommandBody::Examine { mailbox }))
28✔
218
}
1,742✔
219

220
/// `list = "LIST" SP mailbox SP list-mailbox`
221
pub(crate) fn list(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,714✔
222
    let mut parser = tuple((tag_no_case(b"LIST "), mailbox, sp, list_mailbox));
1,714✔
223

224
    let (remaining, (_, reference, _, mailbox_wildcard)) = parser(input)?;
1,714✔
225

226
    Ok((
364✔
227
        remaining,
364✔
228
        CommandBody::List {
364✔
229
            reference,
364✔
230
            mailbox_wildcard,
364✔
231
        },
364✔
232
    ))
364✔
233
}
1,714✔
234

235
/// `lsub = "LSUB" SP mailbox SP list-mailbox`
236
pub(crate) fn lsub(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,350✔
237
    let mut parser = tuple((tag_no_case(b"LSUB "), mailbox, sp, list_mailbox));
1,350✔
238

239
    let (remaining, (_, reference, _, mailbox_wildcard)) = parser(input)?;
1,350✔
240

241
    Ok((
56✔
242
        remaining,
56✔
243
        CommandBody::Lsub {
56✔
244
            reference,
56✔
245
            mailbox_wildcard,
56✔
246
        },
56✔
247
    ))
56✔
248
}
1,350✔
249

250
/// `rename = "RENAME" SP mailbox SP mailbox`
251
///
252
/// Note: Use of INBOX as a destination gives a NO error
253
pub(crate) fn rename(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,294✔
254
    let mut parser = tuple((tag_no_case(b"RENAME "), mailbox, sp, mailbox));
1,294✔
255

256
    let (remaining, (_, mailbox, _, new_mailbox)) = parser(input)?;
1,294✔
257

258
    Ok((
84✔
259
        remaining,
84✔
260
        CommandBody::Rename {
84✔
261
            from: mailbox,
84✔
262
            to: new_mailbox,
84✔
263
        },
84✔
264
    ))
84✔
265
}
1,294✔
266

267
/// `select = "SELECT" SP mailbox`
268
pub(crate) fn select(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,210✔
269
    let mut parser = preceded(tag_no_case(b"SELECT "), mailbox);
1,210✔
270

271
    let (remaining, mailbox) = parser(input)?;
1,210✔
272

273
    Ok((remaining, CommandBody::Select { mailbox }))
72✔
274
}
1,210✔
275

276
/// `status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")"`
277
pub(crate) fn status(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,118✔
278
    let mut parser = tuple((
1,118✔
279
        tag_no_case(b"STATUS "),
1,118✔
280
        mailbox,
1,118✔
281
        delimited(tag(b" ("), separated_list0(sp, status_att), tag(b")")),
1,118✔
282
    ));
1,118✔
283

284
    let (remaining, (_, mailbox, item_names)) = parser(input)?;
1,118✔
285

286
    Ok((
32✔
287
        remaining,
32✔
288
        CommandBody::Status {
32✔
289
            mailbox,
32✔
290
            item_names: item_names.into(),
32✔
291
        },
32✔
292
    ))
32✔
293
}
1,118✔
294

295
/// `subscribe = "SUBSCRIBE" SP mailbox`
296
pub(crate) fn subscribe(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,086✔
297
    let mut parser = preceded(tag_no_case(b"SUBSCRIBE "), mailbox);
1,086✔
298

299
    let (remaining, mailbox) = parser(input)?;
1,086✔
300

301
    Ok((remaining, CommandBody::Subscribe { mailbox }))
28✔
302
}
1,086✔
303

304
/// `unsubscribe = "UNSUBSCRIBE" SP mailbox`
305
pub(crate) fn unsubscribe(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
1,058✔
306
    let mut parser = preceded(tag_no_case(b"UNSUBSCRIBE "), mailbox);
1,058✔
307

308
    let (remaining, mailbox) = parser(input)?;
1,058✔
309

310
    Ok((remaining, CommandBody::Unsubscribe { mailbox }))
28✔
311
}
1,058✔
312

313
// # Command NonAuth
314

315
/// `command-nonauth = login / authenticate / "STARTTLS"`
316
///
317
/// Note: Valid only when in Not Authenticated state
318
pub(crate) fn command_nonauth(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
850✔
319
    let mut parser = alt((
850✔
320
        login,
850✔
321
        map(authenticate, |(mechanism, initial_response)| {
850✔
322
            CommandBody::Authenticate {
×
323
                mechanism,
×
324
                initial_response,
×
325
            }
×
326
        }),
850✔
327
        #[cfg(feature = "starttls")]
850✔
328
        value(CommandBody::StartTLS, tag_no_case(b"STARTTLS")),
850✔
329
    ));
850✔
330

331
    let (remaining, parsed_command_nonauth) = parser(input)?;
850✔
332

333
    Ok((remaining, parsed_command_nonauth))
226✔
334
}
850✔
335

336
/// `login = "LOGIN" SP userid SP password`
337
pub(crate) fn login(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
850✔
338
    let mut parser = tuple((tag_no_case(b"LOGIN"), sp, userid, sp, password));
850✔
339

340
    let (remaining, (_, _, username, _, password)) = parser(input)?;
850✔
341

342
    Ok((
170✔
343
        remaining,
170✔
344
        CommandBody::Login {
170✔
345
            username,
170✔
346
            password: Secret::new(password),
170✔
347
        },
170✔
348
    ))
170✔
349
}
850✔
350

351
#[inline]
352
/// `userid = astring`
353
pub(crate) fn userid(input: &[u8]) -> IMAPResult<&[u8], AString> {
170✔
354
    astring(input)
170✔
355
}
170✔
356

357
#[inline]
358
/// `password = astring`
359
pub(crate) fn password(input: &[u8]) -> IMAPResult<&[u8], AString> {
170✔
360
    astring(input)
170✔
361
}
170✔
362

363
/// `authenticate = "AUTHENTICATE" SP auth-type *(CRLF base64)` (edited)
364
///
365
/// ```text
366
///                                            Added by SASL-IR
367
///                                            |
368
///                                            vvvvvvvvvvvvvvvvvvv
369
/// authenticate = "AUTHENTICATE" SP auth-type [SP (base64 / "=")] *(CRLF base64)
370
///                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
371
///                |
372
///                This is parsed here.
373
///                CRLF is parsed by upper command parser.
374
/// ```
375
#[allow(clippy::type_complexity)]
376
pub(crate) fn authenticate(
680✔
377
    input: &[u8],
680✔
378
) -> IMAPResult<&[u8], (AuthMechanism, Option<Secret<Cow<[u8]>>>)> {
680✔
379
    let mut parser = tuple((
680✔
380
        tag_no_case(b"AUTHENTICATE "),
680✔
381
        auth_type,
680✔
382
        opt(preceded(
680✔
383
            sp,
680✔
384
            alt((
680✔
385
                map(base64, |data| Secret::new(Cow::Owned(data))),
680✔
386
                value(Secret::new(Cow::Borrowed(&b""[..])), tag("=")),
680✔
387
            )),
680✔
388
        )),
680✔
389
    ));
680✔
390

391
    let (remaining, (_, auth_type, raw_data)) = parser(input)?;
680✔
392

393
    // Server must send continuation ("+ ") at this point...
394

395
    Ok((remaining, (auth_type, raw_data)))
×
396
}
680✔
397

398
// # Command Select
399

400
/// `command-select = "CHECK" /
401
///                   "CLOSE" /
402
///                   "EXPUNGE" /
403
///                   copy /
404
///                   fetch /
405
///                   store /
406
///                   uid /
407
///                   search`
408
///
409
/// Note: Valid only when in Selected state
410
pub(crate) fn command_select(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
624✔
411
    alt((
624✔
412
        value(CommandBody::Check, tag_no_case(b"CHECK")),
624✔
413
        value(CommandBody::Close, tag_no_case(b"CLOSE")),
624✔
414
        value(CommandBody::Expunge, tag_no_case(b"EXPUNGE")),
624✔
415
        uid_expunge,
624✔
416
        copy,
624✔
417
        fetch,
624✔
418
        store,
624✔
419
        uid,
624✔
420
        search,
624✔
421
        sort,
624✔
422
        thread,
624✔
423
        value(CommandBody::Unselect, tag_no_case(b"UNSELECT")),
624✔
424
        r#move,
624✔
425
    ))(input)
624✔
426
}
624✔
427

428
/// `copy = "COPY" SP sequence-set SP mailbox`
429
pub(crate) fn copy(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
532✔
430
    let mut parser = tuple((tag_no_case(b"COPY"), sp, sequence_set, sp, mailbox));
532✔
431

432
    let (remaining, (_, _, sequence_set, _, mailbox)) = parser(input)?;
532✔
433

434
    Ok((
84✔
435
        remaining,
84✔
436
        CommandBody::Copy {
84✔
437
            sequence_set,
84✔
438
            mailbox,
84✔
439
            uid: false,
84✔
440
        },
84✔
441
    ))
84✔
442
}
532✔
443

444
/// `fetch = "FETCH" SP sequence-set SP ("ALL" /
445
///                                      "FULL" /
446
///                                      "FAST" /
447
///                                      fetch-att / "(" fetch-att *(SP fetch-att) ")")`
448
pub(crate) fn fetch(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
450✔
449
    let mut parser = tuple((
450✔
450
        tag_no_case(b"FETCH"),
450✔
451
        sp,
450✔
452
        sequence_set,
450✔
453
        sp,
450✔
454
        alt((
450✔
455
            value(
450✔
456
                MacroOrMessageDataItemNames::Macro(Macro::All),
450✔
457
                tag_no_case(b"ALL"),
450✔
458
            ),
450✔
459
            value(
450✔
460
                MacroOrMessageDataItemNames::Macro(Macro::Fast),
450✔
461
                tag_no_case(b"FAST"),
450✔
462
            ),
450✔
463
            value(
450✔
464
                MacroOrMessageDataItemNames::Macro(Macro::Full),
450✔
465
                tag_no_case(b"FULL"),
450✔
466
            ),
450✔
467
            map(fetch_att, |fetch_att| {
454✔
468
                MacroOrMessageDataItemNames::MessageDataItemNames(vec![fetch_att])
56✔
469
            }),
454✔
470
            map(
450✔
471
                delimited(tag(b"("), separated_list0(sp, fetch_att), tag(b")")),
450✔
472
                MacroOrMessageDataItemNames::MessageDataItemNames,
450✔
473
            ),
450✔
474
        )),
450✔
475
    ));
450✔
476

477
    let (remaining, (_, _, sequence_set, _, macro_or_item_names)) = parser(input)?;
450✔
478

479
    Ok((
114✔
480
        remaining,
114✔
481
        CommandBody::Fetch {
114✔
482
            sequence_set,
114✔
483
            macro_or_item_names,
114✔
484
            uid: false,
114✔
485
        },
114✔
486
    ))
114✔
487
}
450✔
488

489
/// `store = "STORE" SP sequence-set SP store-att-flags`
490
pub(crate) fn store(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
336✔
491
    let mut parser = tuple((tag_no_case(b"STORE"), sp, sequence_set, sp, store_att_flags));
336✔
492

493
    let (remaining, (_, _, sequence_set, _, (kind, response, flags))) = parser(input)?;
336✔
494

495
    Ok((
56✔
496
        remaining,
56✔
497
        CommandBody::Store {
56✔
498
            sequence_set,
56✔
499
            kind,
56✔
500
            response,
56✔
501
            flags,
56✔
502
            uid: false,
56✔
503
        },
56✔
504
    ))
56✔
505
}
336✔
506

507
/// `store-att-flags = (["+" / "-"] "FLAGS" [".SILENT"]) SP (flag-list / (flag *(SP flag)))`
508
pub(crate) fn store_att_flags(
56✔
509
    input: &[u8],
56✔
510
) -> IMAPResult<&[u8], (StoreType, StoreResponse, Vec<Flag>)> {
56✔
511
    let mut parser = tuple((
56✔
512
        tuple((
56✔
513
            map(
56✔
514
                opt(alt((
56✔
515
                    value(StoreType::Add, tag(b"+")),
56✔
516
                    value(StoreType::Remove, tag(b"-")),
56✔
517
                ))),
56✔
518
                |type_| match type_ {
60✔
519
                    Some(type_) => type_,
56✔
520
                    None => StoreType::Replace,
×
521
                },
60✔
522
            ),
56✔
523
            tag_no_case(b"FLAGS"),
56✔
524
            map(opt(tag_no_case(b".SILENT")), |x| match x {
60✔
525
                Some(_) => StoreResponse::Silent,
×
526
                None => StoreResponse::Answer,
56✔
527
            }),
60✔
528
        )),
56✔
529
        sp,
56✔
530
        alt((flag_list, separated_list1(sp, flag))),
56✔
531
    ));
56✔
532

533
    let (remaining, ((store_type, _, store_response), _, flag_list)) = parser(input)?;
56✔
534

535
    Ok((remaining, (store_type, store_response, flag_list)))
56✔
536
}
56✔
537

538
/// `uid = "UID" SP (copy / fetch / search / store)`
539
///
540
/// Note: Unique identifiers used instead of message sequence numbers
541
pub(crate) fn uid(input: &[u8]) -> IMAPResult<&[u8], CommandBody> {
276✔
542
    let mut parser = tuple((
276✔
543
        tag_no_case(b"UID"),
276✔
544
        sp,
276✔
545
        alt((copy, fetch, search, store, r#move)),
276✔
546
    ));
276✔
547

548
    let (remaining, (_, _, mut cmd)) = parser(input)?;
276✔
549

550
    match cmd {
32✔
551
        CommandBody::Copy { ref mut uid, .. }
×
552
        | CommandBody::Fetch { ref mut uid, .. }
28✔
553
        | CommandBody::Search { ref mut uid, .. }
×
554
        | CommandBody::Store { ref mut uid, .. }
×
555
        | CommandBody::Move { ref mut uid, .. } => *uid = true,
32✔
556
        _ => unreachable!(),
×
557
    }
558

559
    Ok((remaining, cmd))
32✔
560
}
276✔
561

562
#[cfg(test)]
563
mod tests {
564
    use std::num::NonZeroU32;
565

566
    use imap_types::{
567
        core::Tag,
568
        fetch::{MessageDataItemName, Section},
569
    };
570

571
    use super::*;
572
    use crate::{encode::Encoder, CommandCodec};
573

574
    #[test]
575
    fn test_parse_fetch() {
2✔
576
        println!("{:#?}", fetch(b"fetch 1:1 (flags)???"));
2✔
577
    }
2✔
578

579
    #[test]
580
    fn test_parse_fetch_att() {
2✔
581
        let tests = [
2✔
582
            (MessageDataItemName::Envelope, "ENVELOPE???"),
2✔
583
            (MessageDataItemName::Flags, "FLAGS???"),
2✔
584
            (MessageDataItemName::InternalDate, "INTERNALDATE???"),
2✔
585
            (MessageDataItemName::Rfc822, "RFC822???"),
2✔
586
            (MessageDataItemName::Rfc822Header, "RFC822.HEADER???"),
2✔
587
            (MessageDataItemName::Rfc822Size, "RFC822.SIZE???"),
2✔
588
            (MessageDataItemName::Rfc822Text, "RFC822.TEXT???"),
2✔
589
            (MessageDataItemName::Body, "BODY???"),
2✔
590
            (MessageDataItemName::BodyStructure, "BODYSTRUCTURE???"),
2✔
591
            (MessageDataItemName::Uid, "UID???"),
2✔
592
            (
2✔
593
                MessageDataItemName::BodyExt {
2✔
594
                    partial: None,
2✔
595
                    peek: false,
2✔
596
                    section: None,
2✔
597
                },
2✔
598
                "BODY[]???",
2✔
599
            ),
2✔
600
            (
2✔
601
                MessageDataItemName::BodyExt {
2✔
602
                    partial: None,
2✔
603
                    peek: true,
2✔
604
                    section: None,
2✔
605
                },
2✔
606
                "BODY.PEEK[]???",
2✔
607
            ),
2✔
608
            (
2✔
609
                MessageDataItemName::BodyExt {
2✔
610
                    partial: None,
2✔
611
                    peek: true,
2✔
612
                    section: Some(Section::Text(None)),
2✔
613
                },
2✔
614
                "BODY.PEEK[TEXT]???",
2✔
615
            ),
2✔
616
            (
2✔
617
                MessageDataItemName::BodyExt {
2✔
618
                    partial: Some((42, NonZeroU32::try_from(1337).unwrap())),
2✔
619
                    peek: true,
2✔
620
                    section: Some(Section::Text(None)),
2✔
621
                },
2✔
622
                "BODY.PEEK[TEXT]<42.1337>???",
2✔
623
            ),
2✔
624
        ];
2✔
625

2✔
626
        let expected_remainder = "???".as_bytes();
2✔
627

628
        for (expected, test) in tests {
30✔
629
            let (got_remainder, got) = fetch_att(test.as_bytes()).unwrap();
28✔
630

28✔
631
            assert_eq!(expected, got);
28✔
632
            assert_eq!(expected_remainder, got_remainder);
28✔
633
        }
634
    }
2✔
635

636
    #[test]
637
    fn test_that_empty_ir_is_encoded_correctly() {
2✔
638
        let command = Command::new(
2✔
639
            Tag::try_from("A").unwrap(),
2✔
640
            CommandBody::Authenticate {
2✔
641
                mechanism: AuthMechanism::Plain,
2✔
642
                initial_response: Some(Secret::new(Cow::Borrowed(&b""[..]))),
2✔
643
            },
2✔
644
        )
2✔
645
        .unwrap();
2✔
646

2✔
647
        let buffer = CommandCodec::default().encode(&command).dump();
2✔
648

2✔
649
        assert_eq!(buffer, b"A AUTHENTICATE PLAIN =\r\n")
2✔
650
    }
2✔
651
}
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