1use bytes::Bytes;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::io::AsyncRead;
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct MessageId(Uuid);
13
14impl MessageId {
15 pub fn new() -> Self {
17 Self(Uuid::new_v4())
18 }
19
20 pub fn from_uuid(uuid: Uuid) -> Self {
22 Self(uuid)
23 }
24
25 pub fn as_uuid(&self) -> &Uuid {
27 &self.0
28 }
29}
30
31impl Default for MessageId {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl std::fmt::Display for MessageId {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 write!(f, "{}", self.0)
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct MimeMessage {
46 headers: HeaderMap,
47 body: MessageBody,
48}
49
50impl MimeMessage {
51 pub fn new(headers: HeaderMap, body: MessageBody) -> Self {
53 Self { headers, body }
54 }
55
56 pub fn headers(&self) -> &HeaderMap {
58 &self.headers
59 }
60
61 pub fn headers_mut(&mut self) -> &mut HeaderMap {
63 &mut self.headers
64 }
65
66 pub fn body(&self) -> &MessageBody {
68 &self.body
69 }
70
71 pub fn extract_text(&self) -> crate::error::Result<String> {
73 match &self.body {
74 MessageBody::Small(bytes) => String::from_utf8(bytes.to_vec())
75 .map_err(|e| crate::error::MailError::Parse(e.to_string())),
76 MessageBody::Large(_) => {
77 Ok(String::new())
79 }
80 }
81 }
82
83 pub fn size(&self) -> usize {
85 match &self.body {
86 MessageBody::Small(bytes) => bytes.len(),
87 MessageBody::Large(_) => 0, }
89 }
90
91 pub fn parse_from_bytes(data: &[u8]) -> crate::error::Result<Self> {
93 let (headers_map, body_offset) = crate::mime::parse_headers(data)?;
94
95 let mut header_map = HeaderMap::new();
97 for (name, value) in headers_map {
98 header_map.insert(name, value);
99 }
100
101 let body_bytes = if body_offset < data.len() {
103 Bytes::copy_from_slice(&data[body_offset..])
104 } else {
105 Bytes::new()
106 };
107
108 let body = MessageBody::Small(body_bytes);
109
110 Ok(MimeMessage::new(header_map, body))
111 }
112
113 pub fn content_type(&self) -> crate::error::Result<Option<crate::mime::ContentType>> {
115 if let Some(ct) = self.headers.get_first("content-type") {
116 Ok(Some(crate::mime::ContentType::parse(ct)?))
117 } else {
118 Ok(None)
119 }
120 }
121
122 pub fn content_transfer_encoding(&self) -> crate::mime::ContentTransferEncoding {
124 if let Some(cte) = self.headers.get_first("content-transfer-encoding") {
125 crate::mime::ContentTransferEncoding::parse(cte.trim())
126 } else {
127 crate::mime::ContentTransferEncoding::SevenBit
128 }
129 }
130
131 pub fn parse_multipart(&self) -> crate::error::Result<Vec<crate::mime::MimePart>> {
133 let content_type = self
134 .content_type()?
135 .ok_or_else(|| crate::error::MailError::Parse("No Content-Type header".to_string()))?;
136
137 if !content_type.is_multipart() {
138 return Err(crate::error::MailError::Parse(
139 "Not a multipart message".to_string(),
140 ));
141 }
142
143 let boundary = content_type.boundary().ok_or_else(|| {
144 crate::error::MailError::Parse("No boundary in multipart".to_string())
145 })?;
146
147 match &self.body {
148 MessageBody::Small(bytes) => crate::mime::split_multipart(bytes, boundary),
149 MessageBody::Large(_) => Err(crate::error::MailError::Parse(
150 "Cannot parse multipart from large message stream".to_string(),
151 )),
152 }
153 }
154
155 pub fn decode_body(&self) -> crate::error::Result<Vec<u8>> {
157 let encoding = self.content_transfer_encoding();
158
159 match &self.body {
160 MessageBody::Small(bytes) => match encoding {
161 crate::mime::ContentTransferEncoding::Base64 => crate::mime::decode_base64(bytes),
162 crate::mime::ContentTransferEncoding::QuotedPrintable => {
163 crate::mime::decode_quoted_printable(bytes)
164 }
165 _ => Ok(bytes.to_vec()),
166 },
167 MessageBody::Large(_) => Err(crate::error::MailError::Parse(
168 "Cannot decode large message stream".to_string(),
169 )),
170 }
171 }
172}
173
174#[derive(Clone)]
176pub enum MessageBody {
177 Small(Bytes),
179 Large(Arc<dyn AsyncRead + Send + Sync>),
181}
182
183impl std::fmt::Debug for MessageBody {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 match self {
186 MessageBody::Small(bytes) => f.debug_tuple("Small").field(&bytes.len()).finish(),
187 MessageBody::Large(_) => f.debug_tuple("Large").field(&"<stream>").finish(),
188 }
189 }
190}
191
192#[derive(Debug, Clone, Default)]
194pub struct HeaderMap {
195 headers: HashMap<String, Vec<String>>,
196}
197
198impl HeaderMap {
199 pub fn new() -> Self {
201 Self {
202 headers: HashMap::new(),
203 }
204 }
205
206 pub fn insert(&mut self, name: impl Into<String>, value: impl Into<String>) {
208 let name = name.into().to_lowercase();
209 let value = value.into();
210 self.headers.entry(name).or_default().push(value);
211 }
212
213 pub fn get(&self, name: &str) -> Option<&[String]> {
215 self.headers.get(&name.to_lowercase()).map(|v| v.as_slice())
216 }
217
218 pub fn get_first(&self, name: &str) -> Option<&str> {
220 self.get(name).and_then(|v| v.first().map(|s| s.as_str()))
221 }
222
223 pub fn remove(&mut self, name: &str) -> Option<Vec<String>> {
225 self.headers.remove(&name.to_lowercase())
226 }
227
228 pub fn contains(&self, name: &str) -> bool {
230 self.headers.contains_key(&name.to_lowercase())
231 }
232
233 pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
235 self.headers.iter()
236 }
237
238 pub fn parse_from_bytes(data: &[u8]) -> crate::error::Result<(Self, usize)> {
240 let (headers_map, offset) = crate::mime::parse_headers(data)?;
241
242 let mut header_map = HeaderMap::new();
243 for (name, value) in headers_map {
244 header_map.insert(name, value);
245 }
246
247 Ok((header_map, offset))
248 }
249
250 pub fn fold_value(value: &str) -> String {
252 crate::mime::fold_header(value, 78)
253 }
254
255 pub fn unfold_value(value: &str) -> String {
257 crate::mime::unfold_header(value)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_message_id_unique() {
267 let id1 = MessageId::new();
268 let id2 = MessageId::new();
269 assert_ne!(id1, id2);
270 }
271
272 #[test]
273 fn test_header_map_case_insensitive() {
274 let mut headers = HeaderMap::new();
275 headers.insert("Content-Type", "text/plain");
276 headers.insert("content-type", "text/html");
277
278 let values = headers.get("CONTENT-TYPE").unwrap();
279 assert_eq!(values.len(), 2);
280 assert!(values.contains(&"text/plain".to_string()));
281 assert!(values.contains(&"text/html".to_string()));
282 }
283
284 #[test]
285 fn test_small_message_body() {
286 let body = MessageBody::Small(Bytes::from("Hello, World!"));
287 let headers = HeaderMap::new();
288 let msg = MimeMessage::new(headers, body);
289 assert_eq!(msg.size(), 13);
290 }
291}