1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

use crate::text::misc_token::{unstructured, Unstructured};
use crate::text::whitespace::{foldable_line, obs_crlf};
use nom::{
    branch::alt,
    bytes::complete::{tag, tag_no_case, take_while1},
    character::complete::space0,
    combinator::map,
    multi::{fold_many0},
    sequence::{pair, terminated, tuple},
    IResult,
};

#[derive(Debug, PartialEq)]
pub enum CompField<'a, T> {
    Known(T),
    Unknown(Kv<'a>),
    Bad(&'a [u8]),
}

#[derive(Debug, PartialEq, Clone)]
pub struct Kv<'a>(pub &'a [u8], pub Unstructured<'a>);


pub fn header<'a, T>(
    fx: impl Fn(&'a [u8]) -> IResult<&'a [u8], T> + Copy,
) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], (Vec::<T>, Vec::<Kv>, Vec<&'a [u8]>)> {
    move |input| {
            terminated(
                fold_many0(
                    alt((
                        map(fx, CompField::Known),
                        map(opt_field, |(k, v)| CompField::Unknown(Kv(k, v))),
                        map(foldable_line, CompField::Bad),
                    )),
                    || (Vec::<T>::new(), Vec::<Kv>::new(), Vec::<&'a [u8]>::new()),
                    |(mut known, mut unknown, mut bad), item| {
                        match item {
                            CompField::Known(v) => known.push(v),
                            CompField::Unknown(v) => unknown.push(v),
                            CompField::Bad(v) => bad.push(v),
                        };
                        (known, unknown, bad)
                    }
                ),
                obs_crlf,
            )(input)
    }
}

pub fn field_name<'a>(name: &'static [u8]) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8]> {
    move |input| terminated(tag_no_case(name), tuple((space0, tag(b":"), space0)))(input)
}

/// Optional field
///
/// ```abnf
/// field      =   field-name ":" unstructured CRLF
/// field-name =   1*ftext
/// ftext      =   %d33-57 /          ; Printable US-ASCII
///                %d59-126           ;  characters not including
///                                   ;  ":".
/// ```
pub fn opt_field(input: &[u8]) -> IResult<&[u8], (&[u8], Unstructured)> {
    terminated(
        pair(
            terminated(
                take_while1(|c| (0x21..=0x7E).contains(&c) && c != 0x3A),
                tuple((space0, tag(b":"), space0)),
            ),
            unstructured,
        ),
        obs_crlf,
    )(input)
}