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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! # List of email headers.

use mailparse::{MailHeader, MailHeaderMap};

#[derive(Debug, Display, Clone, PartialEq, Eq, IntoStaticStr)]
#[strum(serialize_all = "kebab_case")]
#[allow(missing_docs)]
pub enum HeaderDef {
    MessageId,
    Subject,
    Date,
    From_,
    To,

    /// Carbon copy.
    Cc,
    Disposition,

    /// Used in the "Body Part Header" of MDNs as of RFC 8098.
    /// Indicates the Message-ID of the message for which the MDN is being issued.
    OriginalMessageId,

    /// Delta Chat extension for message IDs in combined MDNs
    AdditionalMessageIds,

    /// Outlook-SMTP-server replace the `Message-ID:`-header
    /// and write the original ID to `X-Microsoft-Original-Message-ID`.
    /// To sort things correctly and to not show outgoing messages twice,
    /// we need to check that header as well.
    XMicrosoftOriginalMessageId,

    /// Thunderbird header used to store Draft information.
    ///
    /// Thunderbird 78.11.0 does not set \Draft flag on messages saved as "Template", but sets this
    /// header, so it can be used to ignore such messages.
    XMozillaDraftInfo,

    /// Mailing list ID defined in [RFC 2919](https://tools.ietf.org/html/rfc2919).
    ListId,
    ListPost,

    /// List-Help header defined in [RFC 2369](https://datatracker.ietf.org/doc/html/rfc2369).
    ListHelp,
    References,

    /// In-Reply-To header containing Message-ID of the parent message.
    InReplyTo,

    /// Used to detect mailing lists if contains "list" value
    /// as described in [RFC 3834](https://tools.ietf.org/html/rfc3834)
    Precedence,

    ContentType,
    ContentId,
    ChatVersion,
    ChatGroupId,
    ChatGroupName,
    ChatGroupNameChanged,
    ChatVerified,
    ChatGroupAvatar,
    ChatUserAvatar,
    ChatVoiceMessage,
    ChatGroupMemberRemoved,
    ChatGroupMemberAdded,
    ChatContent,

    /// Duration of the attached media file.
    ChatDuration,

    ChatDispositionNotificationTo,
    ChatWebrtcRoom,

    /// [Autocrypt](https://autocrypt.org/) header.
    Autocrypt,
    AutocryptSetupMessage,
    SecureJoin,

    /// Deprecated header containing Group-ID in `vg-request-with-auth` message.
    ///
    /// It is not used by Alice as Alice knows the group corresponding to the AUTH token.
    /// Bob still sends it for backwards compatibility.
    SecureJoinGroup,
    SecureJoinFingerprint,
    SecureJoinInvitenumber,
    SecureJoinAuth,
    Sender,

    /// Ephemeral message timer.
    EphemeralTimer,
    Received,

    /// A header that includes the results of the DKIM, SPF and DMARC checks.
    /// See <https://datatracker.ietf.org/doc/html/rfc8601>
    AuthenticationResults,

    #[cfg(test)]
    TestHeader,
}

impl HeaderDef {
    /// Returns the corresponding header string.
    pub fn get_headername(&self) -> &'static str {
        self.into()
    }
}

#[allow(missing_docs)]
pub trait HeaderDefMap {
    /// Returns requested header value if it exists.
    fn get_header_value(&self, headerdef: HeaderDef) -> Option<String>;

    /// Returns requested header if it exists.
    fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader>;
}

impl HeaderDefMap for [MailHeader<'_>] {
    fn get_header_value(&self, headerdef: HeaderDef) -> Option<String> {
        self.get_first_value(headerdef.get_headername())
    }
    fn get_header(&self, headerdef: HeaderDef) -> Option<&MailHeader> {
        self.get_first_header(headerdef.get_headername())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    /// Test that kebab_case serialization works as expected
    fn kebab_test() {
        assert_eq!(HeaderDef::From_.get_headername(), "from");

        assert_eq!(HeaderDef::TestHeader.get_headername(), "test-header");
    }

    #[test]
    /// Test that headers are parsed case-insensitively
    fn test_get_header_value_case() {
        let (headers, _) =
            mailparse::parse_headers(b"fRoM: Bob\naUtoCryPt-SeTup-MessAge: v99").unwrap();
        assert_eq!(
            headers.get_header_value(HeaderDef::AutocryptSetupMessage),
            Some("v99".to_string())
        );
        assert_eq!(
            headers.get_header_value(HeaderDef::From_),
            Some("Bob".to_string())
        );
        assert_eq!(headers.get_header_value(HeaderDef::Autocrypt), None);
    }
}