@@ -2,38 +2,64 @@ use std::borrow::Cow;
22
33use anyhow:: { Context , Result } ;
44use imap:: { types:: Fetch , Session } ;
5- use mail_parser:: { HeaderValue , Message , MessageParser } ;
5+ use mailparse:: * ;
6+
7+ use crate :: error:: AppResult ;
68
7- #[ derive( Debug ) ]
89pub struct EmailMessage {
9- // 1) “ Original sender” best-effort (From / Resent-From / Sender / envelope)
10- original_sender : String ,
10+ // 1) " Original sender" best-effort (From / Resent-From / Sender / envelope)
11+ pub original_sender : String ,
1112 // 2) All DKIM-Signature header values (there can be multiple)
1213 dkim_signatures : Vec < String > ,
1314 // 3) Subject (decoded)
1415 subject : Option < String > ,
1516 // 4) Body (best-effort: prefer text/plain part; fall back to full text)
16- body_text : Option < String > ,
17+ pub body_text : Option < String > ,
18+ // 5) Raw email bytes (can be parsed on demand)
19+ pub raw_bytes : Vec < u8 > ,
20+ }
21+
22+ impl std:: fmt:: Debug for EmailMessage {
23+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
24+ f. debug_struct ( "EmailMessage" )
25+ . field ( "original_sender" , & self . original_sender )
26+ . field ( "dkim_signatures" , & self . dkim_signatures )
27+ . field ( "subject" , & self . subject )
28+ . field (
29+ "body_text" ,
30+ & self
31+ . body_text
32+ . as_ref ( )
33+ . map ( |s| Cow :: Owned ( format ! ( "{}..." , & s[ ..s. len( ) . min( 30 ) ] ) ) )
34+ . unwrap_or ( Cow :: Borrowed ( "None" ) ) ,
35+ )
36+ . field ( "raw_bytes_len" , & self . raw_bytes . len ( ) )
37+ . finish ( )
38+ }
1739}
1840
1941impl EmailMessage {
2042 pub fn parse ( f : & Fetch ) -> anyhow:: Result < Self > {
21- let msg = MessageParser :: new ( )
22- . parse ( f . body ( ) . context ( "Missing email body" ) ? )
23- . ok_or_else ( | | anyhow:: anyhow!( "Failed to parse email message" ) ) ?;
43+ let body_bytes = f . body ( ) . context ( "Missing email body" ) ? ;
44+ let msg = parse_mail ( body_bytes )
45+ . map_err ( |e | anyhow:: anyhow!( "Failed to parse email message: {:?}" , e ) ) ?;
2446
25- let subject = msg. subject ( ) ;
47+ let subject = msg
48+ . headers
49+ . iter ( )
50+ . find ( |h| h. get_key ( ) . eq_ignore_ascii_case ( "Subject" ) )
51+ . map ( |h| h. get_value ( ) ) ;
2652
2753 // best effort original sender extraction
2854 let original_sender = msg
29- . header ( "Resent-From" )
30- . or_else ( || msg . header ( "From" ) )
31- . or_else ( || msg . header ( "Sender" ) )
32- . and_then ( |h| match h {
33- HeaderValue :: Address ( addr ) => addr . first ( ) . map ( |a| a . address ( ) ) . flatten ( ) ,
34- _ => h . as_text ( ) ,
55+ . headers
56+ . iter ( )
57+ . find ( |h| {
58+ h . get_key ( ) . eq_ignore_ascii_case ( "Resent-From" )
59+ || h . get_key ( ) . eq_ignore_ascii_case ( "From" )
60+ || h . get_key ( ) . eq_ignore_ascii_case ( "Sender" )
3561 } )
36- . map ( |s| s . to_string ( ) )
62+ . map ( |h| h . get_value ( ) )
3763 . or_else ( || {
3864 f. envelope ( )
3965 . and_then ( |env| {
@@ -51,18 +77,26 @@ impl EmailMessage {
5177
5278 // there can be multiple signatures
5379 let dkim_signatures = msg
54- . header_values ( "DKIM-Signature" )
55- . flat_map ( |h| h. as_text ( ) )
80+ . headers
81+ . iter ( )
82+ . filter ( |h| h. get_key ( ) . eq_ignore_ascii_case ( "DKIM-Signature" ) )
83+ . map ( |h| h. get_value ( ) )
5684 . collect :: < Vec < _ > > ( ) ;
5785
5886 // prefer text/plain part; fall back to html text
59- let body_text = msg. body_text ( 0 ) . or_else ( || msg . body_html ( 0 ) ) ;
87+ let body_text = msg. get_body ( ) . ok ( ) ;
6088
6189 Ok ( Self {
6290 subject : subject. map ( |s| s. to_string ( ) ) ,
63- original_sender,
91+ original_sender : original_sender . to_string ( ) ,
6492 dkim_signatures : dkim_signatures. into_iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ,
6593 body_text : body_text. map ( |s| s. to_string ( ) ) ,
94+ raw_bytes : body_bytes. to_vec ( ) ,
6695 } )
6796 }
97+
98+ /// Parse the raw email bytes and return a ParsedMail
99+ pub fn get_parsed ( & self ) -> AppResult < ParsedMail > {
100+ Ok ( parse_mail ( & self . raw_bytes ) ?)
101+ }
68102}
0 commit comments