Skip to main content

mail_parser/view/
content_type.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use super::Resolver;
8use crate::store::{ContentTypeEntry, MessageData, ParamEntry};
9use std::fmt;
10
11struct Attributes<'m>(ContentType<'m>);
12
13/// A Content-Type or Content-Disposition value. Type, subtype and
14/// parameter names are lowercase; RFC 2231 and RFC 2047 encoded parameter
15/// values are decoded. Values compare by content.
16///
17/// ```
18/// use mail_parser::MessageParser;
19///
20/// let raw = b"Content-Type: Text/Plain; Charset=\"utf-8\"; format=flowed\r\n\r\nbody";
21/// let message = MessageParser::new().parse(raw).expect("message");
22/// let content_type = message.content_type().expect("content type");
23/// assert_eq!((content_type.ctype(), content_type.subtype()), ("text", Some("plain")));
24/// assert_eq!(content_type.attribute("charset"), Some("utf-8"));
25/// assert!(message.is_content_type("text", "plain"));
26/// ```
27#[derive(Clone, Copy)]
28pub struct ContentType<'m> {
29    resolver: Resolver<'m>,
30    entry: &'m ContentTypeEntry,
31    params: &'m [ParamEntry],
32}
33
34impl<'m> ContentType<'m> {
35    #[inline]
36    pub(crate) fn new(
37        resolver: Resolver<'m>,
38        data: &'m MessageData,
39        entry: &'m ContentTypeEntry,
40    ) -> Self {
41        ContentType {
42            resolver,
43            entry,
44            params: data.params.get(entry.params.range()).unwrap_or_default(),
45        }
46    }
47
48    /// The type (`text`, `multipart`, `attachment`, ...).
49    #[inline]
50    pub fn ctype(&self) -> &'m str {
51        self.resolver.str(self.entry.ctype)
52    }
53
54    /// The subtype (`plain`, `mixed`, ...); `None` for a disposition or a
55    /// type without `/`.
56    #[inline]
57    pub fn subtype(&self) -> Option<&'m str> {
58        self.resolver.opt(self.entry.subtype)
59    }
60
61    /// The first parameter with this (lowercase) name.
62    pub fn attribute(&self, name: &str) -> Option<&'m str> {
63        self.attributes()
64            .find(|(param, _)| *param == name)
65            .map(|(_, value)| value)
66    }
67
68    /// Every parameter in document order.
69    #[inline]
70    pub fn attributes(
71        &self,
72    ) -> impl ExactSizeIterator<Item = (&'m str, &'m str)> + DoubleEndedIterator + use<'m> {
73        let resolver = self.resolver;
74        self.params
75            .iter()
76            .map(move |param| (resolver.str(param.name), resolver.str(param.value)))
77    }
78
79    /// Whether a parameter with this (lowercase) name is present.
80    pub fn has_attribute(&self, name: &str) -> bool {
81        self.attribute(name).is_some()
82    }
83
84    /// Whether this is `Content-Disposition: attachment`.
85    pub fn is_attachment(&self) -> bool {
86        self.ctype().eq_ignore_ascii_case("attachment")
87    }
88
89    /// Whether this is `Content-Disposition: inline`.
90    pub fn is_inline(&self) -> bool {
91        self.ctype().eq_ignore_ascii_case("inline")
92    }
93}
94
95impl fmt::Debug for Attributes<'_> {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.debug_map().entries(self.0.attributes()).finish()
98    }
99}
100
101impl fmt::Debug for ContentType<'_> {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        f.debug_struct("ContentType")
104            .field("type", &self.ctype())
105            .field("subtype", &self.subtype())
106            .field("attributes", &Attributes(*self))
107            .finish()
108    }
109}
110
111impl PartialEq for ContentType<'_> {
112    fn eq(&self, other: &Self) -> bool {
113        self.ctype() == other.ctype()
114            && self.subtype() == other.subtype()
115            && self.params.len() == other.params.len()
116            && self.attributes().eq(other.attributes())
117    }
118}
119
120impl Eq for ContentType<'_> {}