Skip to main content

rusmes_proto/
message.rs

1//! MIME message types
2
3use bytes::Bytes;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::Arc;
7use tokio::io::AsyncRead;
8use uuid::Uuid;
9
10/// Unique identifier for a message
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct MessageId(Uuid);
13
14impl MessageId {
15    /// Create a new unique message ID
16    pub fn new() -> Self {
17        Self(Uuid::new_v4())
18    }
19
20    /// Create a MessageId from an existing UUID
21    pub fn from_uuid(uuid: Uuid) -> Self {
22        Self(uuid)
23    }
24
25    /// Get the UUID value
26    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/// MIME message structure
44#[derive(Debug, Clone)]
45pub struct MimeMessage {
46    headers: HeaderMap,
47    body: MessageBody,
48}
49
50impl MimeMessage {
51    /// Create a new MIME message
52    pub fn new(headers: HeaderMap, body: MessageBody) -> Self {
53        Self { headers, body }
54    }
55
56    /// Get message headers
57    pub fn headers(&self) -> &HeaderMap {
58        &self.headers
59    }
60
61    /// Get mutable message headers
62    pub fn headers_mut(&mut self) -> &mut HeaderMap {
63        &mut self.headers
64    }
65
66    /// Get message body
67    pub fn body(&self) -> &MessageBody {
68        &self.body
69    }
70
71    /// Extract text content from message (simplified for now)
72    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                // For large messages, we'd need to stream and decode
78                Ok(String::new())
79            }
80        }
81    }
82
83    /// Get message size in bytes
84    pub fn size(&self) -> usize {
85        match &self.body {
86            MessageBody::Small(bytes) => bytes.len(),
87            MessageBody::Large(_) => 0, // Would need to track separately
88        }
89    }
90
91    /// Parse MIME message from raw bytes
92    pub fn parse_from_bytes(data: &[u8]) -> crate::error::Result<Self> {
93        let (headers_map, body_offset) = crate::mime::parse_headers(data)?;
94
95        // Convert to HeaderMap
96        let mut header_map = HeaderMap::new();
97        for (name, value) in headers_map {
98            header_map.insert(name, value);
99        }
100
101        // Extract body
102        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    /// Get Content-Type header parsed
114    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    /// Get Content-Transfer-Encoding
123    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    /// Parse multipart message into parts
132    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    /// Decode message body according to Content-Transfer-Encoding
156    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/// Message body - optimized for small and large messages
175#[derive(Clone)]
176pub enum MessageBody {
177    /// Small message stored in memory (<1MB)
178    Small(Bytes),
179    /// Large message reference (streaming support)
180    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/// Message headers as a map
193#[derive(Debug, Clone, Default)]
194pub struct HeaderMap {
195    headers: HashMap<String, Vec<String>>,
196}
197
198impl HeaderMap {
199    /// Create a new empty header map
200    pub fn new() -> Self {
201        Self {
202            headers: HashMap::new(),
203        }
204    }
205
206    /// Insert a header value
207    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    /// Get all values for a header
214    pub fn get(&self, name: &str) -> Option<&[String]> {
215        self.headers.get(&name.to_lowercase()).map(|v| v.as_slice())
216    }
217
218    /// Get the first value for a header
219    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    /// Remove a header
224    pub fn remove(&mut self, name: &str) -> Option<Vec<String>> {
225        self.headers.remove(&name.to_lowercase())
226    }
227
228    /// Check if a header exists
229    pub fn contains(&self, name: &str) -> bool {
230        self.headers.contains_key(&name.to_lowercase())
231    }
232
233    /// Iterate over all headers
234    pub fn iter(&self) -> impl Iterator<Item = (&String, &Vec<String>)> {
235        self.headers.iter()
236    }
237
238    /// Parse headers from raw bytes with folding support
239    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    /// Fold a header value for proper line length
251    pub fn fold_value(value: &str) -> String {
252        crate::mime::fold_header(value, 78)
253    }
254
255    /// Unfold a header value
256    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}