imap_types/
arbitrary.rs

1use arbitrary::{Arbitrary, Unstructured};
2use chrono::{FixedOffset, TimeZone};
3
4use crate::{
5    auth::AuthMechanism,
6    body::{
7        BasicFields, Body, BodyExtension, BodyStructure, MultiPartExtensionData,
8        SinglePartExtensionData, SpecificFields,
9    },
10    core::{
11        AString, Atom, AtomExt, IString, Literal, LiteralMode, NString, NonEmptyVec, Quoted,
12        QuotedChar, Tag, Text,
13    },
14    datetime::{DateTime, NaiveDate},
15    envelope::Envelope,
16    extensions::{enable::CapabilityEnable, quota::Resource},
17    flag::{Flag, FlagNameAttribute},
18    mailbox::{ListCharString, Mailbox, MailboxOther},
19    response::{
20        Capability, Code, CodeOther, CommandContinuationRequestBasic, Greeting, GreetingKind,
21        Status,
22    },
23    search::SearchKey,
24    sequence::SequenceSet,
25};
26
27macro_rules! implement_tryfrom {
28    ($target:ty, $from:ty) => {
29        impl<'a> Arbitrary<'a> for $target {
30            fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
31                match <$target>::try_from(<$from>::arbitrary(u)?) {
32                    Ok(passed) => Ok(passed),
33                    Err(_) => Err(arbitrary::Error::IncorrectFormat),
34                }
35            }
36        }
37    };
38}
39
40macro_rules! implement_tryfrom_t {
41    ($target:ty, $from:ty) => {
42        impl<'a, T> Arbitrary<'a> for $target
43        where
44            T: Arbitrary<'a>,
45        {
46            fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
47                match <$target>::try_from(<$from>::arbitrary(u)?) {
48                    Ok(passed) => Ok(passed),
49                    Err(_) => Err(arbitrary::Error::IncorrectFormat),
50                }
51            }
52        }
53    };
54}
55
56implement_tryfrom! { Atom<'a>, &str }
57implement_tryfrom! { AtomExt<'a>, &str }
58implement_tryfrom! { Quoted<'a>, &str }
59implement_tryfrom! { Tag<'a>, &str }
60implement_tryfrom! { Text<'a>, &str }
61implement_tryfrom! { ListCharString<'a>, &str }
62implement_tryfrom! { QuotedChar, char }
63implement_tryfrom! { Mailbox<'a>, &str }
64implement_tryfrom! { Capability<'a>, Atom<'a> }
65implement_tryfrom! { Flag<'a>, &str }
66implement_tryfrom! { FlagNameAttribute<'a>, Atom<'a> }
67implement_tryfrom! { MailboxOther<'a>, AString<'a> }
68implement_tryfrom! { CapabilityEnable<'a>, &str }
69implement_tryfrom! { Resource<'a>, &str }
70implement_tryfrom! { AuthMechanism<'a>, &str }
71implement_tryfrom_t! { NonEmptyVec<T>, Vec<T> }
72
73impl<'a> Arbitrary<'a> for CommandContinuationRequestBasic<'a> {
74    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
75        Self::new(Option::<Code>::arbitrary(u)?, Text::arbitrary(u)?)
76            .map_err(|_| arbitrary::Error::IncorrectFormat)
77    }
78}
79
80// TODO(#301): This is due to the `Code`/`Text` ambiguity.
81impl<'a> Arbitrary<'a> for Greeting<'a> {
82    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
83        Ok(Greeting {
84            kind: GreetingKind::arbitrary(u)?,
85            code: Option::<Code>::arbitrary(u)?,
86            text: {
87                let text = Text::arbitrary(u)?;
88
89                if text.as_ref().starts_with('[') {
90                    Text::unvalidated("...")
91                } else {
92                    text
93                }
94            },
95        })
96    }
97}
98
99// TODO(#301): This is due to the `Code`/`Text` ambiguity.
100impl<'a> Arbitrary<'a> for Status<'a> {
101    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
102        let code = Option::<Code>::arbitrary(u)?;
103        let text = if code.is_some() {
104            Arbitrary::arbitrary(u)?
105        } else {
106            let text = Text::arbitrary(u)?;
107
108            if text.as_ref().starts_with('[') {
109                Text::unvalidated("...")
110            } else {
111                text
112            }
113        };
114
115        Ok(match u.int_in_range(0u8..=3)? {
116            0 => Status::Ok {
117                tag: Arbitrary::arbitrary(u)?,
118                code,
119                text,
120            },
121            1 => Status::No {
122                tag: Arbitrary::arbitrary(u)?,
123                code,
124                text,
125            },
126            2 => Status::Bad {
127                tag: Arbitrary::arbitrary(u)?,
128                code,
129                text,
130            },
131            3 => Status::Bye { code, text },
132            _ => unreachable!(),
133        })
134    }
135}
136
137impl<'a> Arbitrary<'a> for Literal<'a> {
138    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
139        match Literal::try_from(<&[u8]>::arbitrary(u)?) {
140            Ok(mut passed) => {
141                passed.mode = LiteralMode::arbitrary(u)?;
142                Ok(passed)
143            }
144            Err(_) => Err(arbitrary::Error::IncorrectFormat),
145        }
146    }
147}
148
149impl<'a> Arbitrary<'a> for CodeOther<'a> {
150    fn arbitrary(_: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
151        // `CodeOther` is a fallback and should usually not be created.
152        Ok(CodeOther::unvalidated(b"IMAP-CODEC-CODE-OTHER>".as_ref()))
153    }
154}
155
156impl<'a> Arbitrary<'a> for SearchKey<'a> {
157    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
158        fn make_search_key<'a>(u: &mut Unstructured<'a>) -> arbitrary::Result<SearchKey<'a>> {
159            Ok(match u.int_in_range(0u8..=33)? {
160                0 => SearchKey::SequenceSet(SequenceSet::arbitrary(u)?),
161                1 => SearchKey::All,
162                2 => SearchKey::Answered,
163                3 => SearchKey::Bcc(AString::arbitrary(u)?),
164                4 => SearchKey::Before(NaiveDate::arbitrary(u)?),
165                5 => SearchKey::Body(AString::arbitrary(u)?),
166                6 => SearchKey::Cc(AString::arbitrary(u)?),
167                7 => SearchKey::Deleted,
168                8 => SearchKey::Draft,
169                9 => SearchKey::Flagged,
170                10 => SearchKey::From(AString::arbitrary(u)?),
171                11 => SearchKey::Header(AString::arbitrary(u)?, AString::arbitrary(u)?),
172                12 => SearchKey::Keyword(Atom::arbitrary(u)?),
173                13 => SearchKey::Larger(u32::arbitrary(u)?),
174                14 => SearchKey::New,
175                15 => SearchKey::Old,
176                16 => SearchKey::On(NaiveDate::arbitrary(u)?),
177                17 => SearchKey::Recent,
178                18 => SearchKey::Seen,
179                19 => SearchKey::SentBefore(NaiveDate::arbitrary(u)?),
180                20 => SearchKey::SentOn(NaiveDate::arbitrary(u)?),
181                21 => SearchKey::SentSince(NaiveDate::arbitrary(u)?),
182                22 => SearchKey::Since(NaiveDate::arbitrary(u)?),
183                23 => SearchKey::Smaller(u32::arbitrary(u)?),
184                24 => SearchKey::Subject(AString::arbitrary(u)?),
185                25 => SearchKey::Text(AString::arbitrary(u)?),
186                26 => SearchKey::To(AString::arbitrary(u)?),
187                27 => SearchKey::Uid(SequenceSet::arbitrary(u)?),
188                28 => SearchKey::Unanswered,
189                29 => SearchKey::Undeleted,
190                30 => SearchKey::Undraft,
191                31 => SearchKey::Unflagged,
192                32 => SearchKey::Unkeyword(Atom::arbitrary(u)?),
193                33 => SearchKey::Unseen,
194                _ => unreachable!(),
195            })
196        }
197
198        fn make_search_key_rec<'a>(
199            u: &mut Unstructured<'a>,
200            depth: u8,
201        ) -> arbitrary::Result<SearchKey<'a>> {
202            if depth == 0 {
203                return make_search_key(u);
204            }
205
206            Ok(match u.int_in_range(0u8..=36)? {
207                0 => SearchKey::And({
208                    let keys = {
209                        let len = u.arbitrary_len::<SearchKey>()?;
210                        let mut tmp = Vec::with_capacity(len);
211
212                        for _ in 0..len {
213                            tmp.push(make_search_key_rec(u, depth - 1)?);
214                        }
215
216                        tmp
217                    };
218
219                    if !keys.is_empty() {
220                        NonEmptyVec::try_from(keys).unwrap()
221                    } else {
222                        NonEmptyVec::from(make_search_key(u)?)
223                    }
224                }),
225                1 => SearchKey::SequenceSet(SequenceSet::arbitrary(u)?),
226                2 => SearchKey::All,
227                3 => SearchKey::Answered,
228                4 => SearchKey::Bcc(AString::arbitrary(u)?),
229                5 => SearchKey::Before(NaiveDate::arbitrary(u)?),
230                6 => SearchKey::Body(AString::arbitrary(u)?),
231                7 => SearchKey::Cc(AString::arbitrary(u)?),
232                8 => SearchKey::Deleted,
233                9 => SearchKey::Draft,
234                10 => SearchKey::Flagged,
235                11 => SearchKey::From(AString::arbitrary(u)?),
236                12 => SearchKey::Header(AString::arbitrary(u)?, AString::arbitrary(u)?),
237                13 => SearchKey::Keyword(Atom::arbitrary(u)?),
238                14 => SearchKey::Larger(u32::arbitrary(u)?),
239                15 => SearchKey::New,
240                16 => SearchKey::Not(Box::new(make_search_key_rec(u, depth - 1)?)),
241                17 => SearchKey::Old,
242                18 => SearchKey::On(NaiveDate::arbitrary(u)?),
243                19 => SearchKey::Or(
244                    Box::new(make_search_key_rec(u, depth - 1)?),
245                    Box::new(make_search_key_rec(u, depth - 1)?),
246                ),
247                20 => SearchKey::Recent,
248                21 => SearchKey::Seen,
249                22 => SearchKey::SentBefore(NaiveDate::arbitrary(u)?),
250                23 => SearchKey::SentOn(NaiveDate::arbitrary(u)?),
251                24 => SearchKey::SentSince(NaiveDate::arbitrary(u)?),
252                25 => SearchKey::Since(NaiveDate::arbitrary(u)?),
253                26 => SearchKey::Smaller(u32::arbitrary(u)?),
254                27 => SearchKey::Subject(AString::arbitrary(u)?),
255                28 => SearchKey::Text(AString::arbitrary(u)?),
256                29 => SearchKey::To(AString::arbitrary(u)?),
257                30 => SearchKey::Uid(SequenceSet::arbitrary(u)?),
258                31 => SearchKey::Unanswered,
259                32 => SearchKey::Undeleted,
260                33 => SearchKey::Undraft,
261                34 => SearchKey::Unflagged,
262                35 => SearchKey::Unkeyword(Atom::arbitrary(u)?),
263                36 => SearchKey::Unseen,
264                _ => unreachable!(),
265            })
266        }
267
268        make_search_key_rec(u, 7)
269    }
270}
271
272impl<'a> Arbitrary<'a> for BodyStructure<'a> {
273    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
274        fn make_body_structure_terminator<'a>(
275            u: &mut Unstructured<'a>,
276        ) -> arbitrary::Result<BodyStructure<'a>> {
277            Ok(BodyStructure::Single {
278                body: Body {
279                    basic: BasicFields::arbitrary(u)?,
280                    specific: match u.int_in_range(1..=2)? {
281                        1 => SpecificFields::Basic {
282                            r#type: IString::arbitrary(u)?,
283                            subtype: IString::arbitrary(u)?,
284                        },
285                        // No SpecificFields::Message because it would recurse.
286                        2 => SpecificFields::Text {
287                            subtype: IString::arbitrary(u)?,
288                            number_of_lines: u32::arbitrary(u)?,
289                        },
290                        _ => unreachable!(),
291                    },
292                },
293                extension_data: Option::<SinglePartExtensionData>::arbitrary(u)?,
294            })
295        }
296
297        fn make_body_structure_rec<'a>(
298            u: &mut Unstructured<'a>,
299            depth: u8,
300        ) -> arbitrary::Result<BodyStructure<'a>> {
301            if depth == 0 {
302                return make_body_structure_terminator(u);
303            }
304
305            Ok(match u.int_in_range(1..=2)? {
306                1 => BodyStructure::Single {
307                    body: Body {
308                        basic: BasicFields::arbitrary(u)?,
309                        specific: match u.int_in_range(1..=3)? {
310                            1 => SpecificFields::Basic {
311                                r#type: IString::arbitrary(u)?,
312                                subtype: IString::arbitrary(u)?,
313                            },
314                            2 => SpecificFields::Message {
315                                envelope: Box::<Envelope>::arbitrary(u)?,
316                                body_structure: Box::new(make_body_structure_rec(u, depth - 1)?),
317                                number_of_lines: u32::arbitrary(u)?,
318                            },
319                            3 => SpecificFields::Text {
320                                subtype: IString::arbitrary(u)?,
321                                number_of_lines: u32::arbitrary(u)?,
322                            },
323                            _ => unreachable!(),
324                        },
325                    },
326                    extension_data: Option::<SinglePartExtensionData>::arbitrary(u)?,
327                },
328                2 => BodyStructure::Multi {
329                    bodies: {
330                        let bodies = {
331                            let len = u.arbitrary_len::<BodyStructure>()?;
332                            let mut tmp = Vec::with_capacity(len);
333
334                            for _ in 0..len {
335                                tmp.push(make_body_structure_rec(u, depth - 1)?);
336                            }
337
338                            tmp
339                        };
340
341                        if !bodies.is_empty() {
342                            NonEmptyVec::try_from(bodies).unwrap()
343                        } else {
344                            NonEmptyVec::from(make_body_structure_terminator(u)?)
345                        }
346                    },
347                    subtype: IString::arbitrary(u)?,
348                    extension_data: Option::<MultiPartExtensionData>::arbitrary(u)?,
349                },
350                _ => unreachable!(),
351            })
352        }
353
354        make_body_structure_rec(u, 3)
355    }
356}
357
358impl<'a> Arbitrary<'a> for BodyExtension<'a> {
359    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
360        fn make_body_extension_terminator<'a>(
361            u: &mut Unstructured<'a>,
362        ) -> arbitrary::Result<BodyExtension<'a>> {
363            Ok(match u.int_in_range(1..=2)? {
364                1 => BodyExtension::NString(NString::arbitrary(u)?),
365                2 => BodyExtension::Number(u32::arbitrary(u)?),
366                // No `BodyExtension::List` because it could recurse.
367                _ => unreachable!(),
368            })
369        }
370
371        fn make_body_extension_rec<'a>(
372            u: &mut Unstructured<'a>,
373            depth: u8,
374        ) -> arbitrary::Result<BodyExtension<'a>> {
375            if depth == 0 {
376                return make_body_extension_terminator(u);
377            }
378
379            Ok(match u.int_in_range(1..=2)? {
380                1 => BodyExtension::NString(NString::arbitrary(u)?),
381                2 => BodyExtension::Number(u32::arbitrary(u)?),
382                3 => BodyExtension::List({
383                    let body_extensions = {
384                        let len = u.arbitrary_len::<BodyExtension>()?;
385                        let mut tmp = Vec::with_capacity(len);
386
387                        for _ in 0..len {
388                            tmp.push(make_body_extension_rec(u, depth - 1)?);
389                        }
390
391                        tmp
392                    };
393
394                    if !body_extensions.is_empty() {
395                        NonEmptyVec::try_from(body_extensions).unwrap()
396                    } else {
397                        NonEmptyVec::from(make_body_extension_terminator(u)?)
398                    }
399                }),
400                _ => unreachable!(),
401            })
402        }
403
404        make_body_extension_rec(u, 3)
405    }
406}
407
408impl<'a> Arbitrary<'a> for DateTime {
409    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
410        // Note: `chrono`s `NaiveDate::arbitrary` may `panic!`.
411        //       Thus, we implement this manually here.
412        let local_datetime = chrono::NaiveDateTime::new(
413            chrono::NaiveDate::from_ymd_opt(
414                u.int_in_range(0..=9999)?,
415                u.int_in_range(1..=12)?,
416                u.int_in_range(1..=31)?,
417            )
418            .ok_or(arbitrary::Error::IncorrectFormat)?,
419            chrono::NaiveTime::arbitrary(u)?,
420        );
421
422        let hours = u.int_in_range(0..=23 * 3600)?;
423        let minutes = u.int_in_range(0..=59)? * 60;
424        // Seconds must be zero due to IMAPs encoding.
425
426        DateTime::try_from(
427            FixedOffset::east_opt(hours + minutes)
428                .unwrap()
429                .from_local_datetime(&local_datetime)
430                .unwrap(),
431        )
432        .map_err(|_| arbitrary::Error::IncorrectFormat)
433    }
434}
435
436impl<'a> Arbitrary<'a> for NaiveDate {
437    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
438        NaiveDate::try_from(chrono::NaiveDate::arbitrary(u)?)
439            .map_err(|_| arbitrary::Error::IncorrectFormat)
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use arbitrary::{Arbitrary, Error, Unstructured};
446    #[cfg(feature = "bounded-static")]
447    use bounded_static::{IntoBoundedStatic, ToBoundedStatic};
448    use rand::{rngs::SmallRng, Rng, SeedableRng};
449
450    use crate::{
451        command::Command,
452        response::{Greeting, Response},
453    };
454
455    /// Note: We could encode/decode/etc. here but only want to exercise the arbitrary logic itself.
456    macro_rules! impl_test_arbitrary {
457        ($object:ty) => {
458            let mut rng = SmallRng::seed_from_u64(1337);
459            let mut data = [0u8; 256];
460
461            // Randomize.
462            rng.try_fill(&mut data).unwrap();
463            let mut unstructured = Unstructured::new(&data);
464
465            let mut count = 0;
466            loop {
467                match <$object>::arbitrary(&mut unstructured) {
468                    Ok(_out) => {
469                        count += 1;
470
471                        #[cfg(feature = "bounded-static")]
472                        {
473                            let out_to_static = _out.to_static();
474                            assert_eq!(_out, out_to_static);
475
476                            let out_into_static = _out.into_static();
477                            assert_eq!(out_to_static, out_into_static);
478                        }
479
480                        if count >= 1_000 {
481                            break;
482                        }
483                    }
484                    Err(Error::NotEnoughData | Error::IncorrectFormat) => {
485                        // Randomize.
486                        rng.try_fill(&mut data).unwrap();
487                        unstructured = Unstructured::new(&data);
488                    }
489                    Err(Error::EmptyChoose) => {
490                        unreachable!();
491                    }
492                    Err(_) => {
493                        unimplemented!()
494                    }
495                }
496            }
497        };
498    }
499
500    #[test]
501    fn test_arbitrary_greeting() {
502        impl_test_arbitrary! {Greeting};
503    }
504
505    #[test]
506    fn test_arbitrary_command() {
507        impl_test_arbitrary! {Command};
508    }
509
510    #[test]
511    fn test_arbitrary_response() {
512        impl_test_arbitrary! {Response};
513    }
514}