7
7
//! `MsgId.get_html()` will return HTML -
8
8
//! this allows nice quoting, handling linebreaks properly etc.
9
9
10
+ use std:: mem;
11
+
10
12
use anyhow:: { Context as _, Result } ;
11
13
use base64:: Engine as _;
12
14
use lettre_email:: mime:: Mime ;
@@ -77,21 +79,26 @@ fn get_mime_multipart_type(ctype: &ParsedContentType) -> MimeMultipartType {
77
79
struct HtmlMsgParser {
78
80
pub html : String ,
79
81
pub plain : Option < PlainText > ,
82
+ pub ( crate ) msg_html : String ,
80
83
}
81
84
82
85
impl HtmlMsgParser {
83
86
/// Function takes a raw mime-message string,
84
87
/// searches for the main-text part
85
88
/// and returns that as parser.html
86
- pub async fn from_bytes ( context : & Context , rawmime : & [ u8 ] ) -> Result < Self > {
89
+ pub async fn from_bytes < ' a > (
90
+ context : & Context ,
91
+ rawmime : & ' a [ u8 ] ,
92
+ ) -> Result < ( Self , mailparse:: ParsedMail < ' a > ) > {
87
93
let mut parser = HtmlMsgParser {
88
94
html : "" . to_string ( ) ,
89
95
plain : None ,
96
+ msg_html : "" . to_string ( ) ,
90
97
} ;
91
98
92
- let parsedmail = mailparse:: parse_mail ( rawmime) ?;
99
+ let parsedmail = mailparse:: parse_mail ( rawmime) . context ( "Failed to parse mail" ) ?;
93
100
94
- parser. collect_texts_recursive ( & parsedmail) . await ?;
101
+ parser. collect_texts_recursive ( context , & parsedmail) . await ?;
95
102
96
103
if parser. html . is_empty ( ) {
97
104
if let Some ( plain) = & parser. plain {
@@ -100,8 +107,8 @@ impl HtmlMsgParser {
100
107
} else {
101
108
parser. cid_to_data_recursive ( context, & parsedmail) . await ?;
102
109
}
103
-
104
- Ok ( parser)
110
+ parser . html += & mem :: take ( & mut parser . msg_html ) ;
111
+ Ok ( ( parser, parsedmail ) )
105
112
}
106
113
107
114
/// Function iterates over all mime-parts
@@ -114,12 +121,13 @@ impl HtmlMsgParser {
114
121
/// therefore we use the first one.
115
122
async fn collect_texts_recursive < ' a > (
116
123
& ' a mut self ,
124
+ context : & ' a Context ,
117
125
mail : & ' a mailparse:: ParsedMail < ' a > ,
118
126
) -> Result < ( ) > {
119
127
match get_mime_multipart_type ( & mail. ctype ) {
120
128
MimeMultipartType :: Multiple => {
121
129
for cur_data in & mail. subparts {
122
- Box :: pin ( self . collect_texts_recursive ( cur_data) ) . await ?
130
+ Box :: pin ( self . collect_texts_recursive ( context , cur_data) ) . await ?
123
131
}
124
132
Ok ( ( ) )
125
133
}
@@ -128,8 +136,35 @@ impl HtmlMsgParser {
128
136
if raw. is_empty ( ) {
129
137
return Ok ( ( ) ) ;
130
138
}
131
- let mail = mailparse:: parse_mail ( & raw ) . context ( "failed to parse mail" ) ?;
132
- Box :: pin ( self . collect_texts_recursive ( & mail) ) . await
139
+ let ( parser, mail) = Box :: pin ( HtmlMsgParser :: from_bytes ( context, & raw ) ) . await ?;
140
+ if !parser. html . is_empty ( ) {
141
+ let mut text = "\r \n \r \n " . to_string ( ) ;
142
+ for h in mail. headers {
143
+ let key = h. get_key ( ) ;
144
+ if matches ! (
145
+ key. to_lowercase( ) . as_str( ) ,
146
+ "date"
147
+ | "from"
148
+ | "sender"
149
+ | "reply-to"
150
+ | "to"
151
+ | "cc"
152
+ | "bcc"
153
+ | "subject"
154
+ ) {
155
+ text += & format ! ( "{key}: {}\r \n " , h. get_value( ) ) ;
156
+ }
157
+ }
158
+ text += "\r \n " ;
159
+ self . msg_html += & PlainText {
160
+ text,
161
+ flowed : false ,
162
+ delsp : false ,
163
+ }
164
+ . to_html ( ) ;
165
+ self . msg_html += & parser. html ;
166
+ }
167
+ Ok ( ( ) )
133
168
}
134
169
MimeMultipartType :: Single => {
135
170
let mimetype = mail. ctype . mimetype . parse :: < Mime > ( ) ?;
@@ -175,14 +210,7 @@ impl HtmlMsgParser {
175
210
}
176
211
Ok ( ( ) )
177
212
}
178
- MimeMultipartType :: Message => {
179
- let raw = mail. get_body_raw ( ) ?;
180
- if raw. is_empty ( ) {
181
- return Ok ( ( ) ) ;
182
- }
183
- let mail = mailparse:: parse_mail ( & raw ) . context ( "failed to parse mail" ) ?;
184
- Box :: pin ( self . cid_to_data_recursive ( context, & mail) ) . await
185
- }
213
+ MimeMultipartType :: Message => Ok ( ( ) ) ,
186
214
MimeMultipartType :: Single => {
187
215
let mimetype = mail. ctype . mimetype . parse :: < Mime > ( ) ?;
188
216
if mimetype. type_ ( ) == mime:: IMAGE {
@@ -240,7 +268,7 @@ impl MsgId {
240
268
warn ! ( context, "get_html: parser error: {:#}" , err) ;
241
269
Ok ( None )
242
270
}
243
- Ok ( parser) => Ok ( Some ( parser. html ) ) ,
271
+ Ok ( ( parser, _ ) ) => Ok ( Some ( parser. html ) ) ,
244
272
}
245
273
} else {
246
274
warn ! ( context, "get_html: no mime for {}" , self ) ;
@@ -274,7 +302,7 @@ mod tests {
274
302
async fn test_htmlparse_plain_unspecified ( ) {
275
303
let t = TestContext :: new ( ) . await ;
276
304
let raw = include_bytes ! ( "../test-data/message/text_plain_unspecified.eml" ) ;
277
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
305
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
278
306
assert_eq ! (
279
307
parser. html,
280
308
r#"<!DOCTYPE html>
@@ -292,7 +320,7 @@ This message does not have Content-Type nor Subject.<br/>
292
320
async fn test_htmlparse_plain_iso88591 ( ) {
293
321
let t = TestContext :: new ( ) . await ;
294
322
let raw = include_bytes ! ( "../test-data/message/text_plain_iso88591.eml" ) ;
295
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
323
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
296
324
assert_eq ! (
297
325
parser. html,
298
326
r#"<!DOCTYPE html>
@@ -310,7 +338,7 @@ message with a non-UTF-8 encoding: äöüßÄÖÜ<br/>
310
338
async fn test_htmlparse_plain_flowed ( ) {
311
339
let t = TestContext :: new ( ) . await ;
312
340
let raw = include_bytes ! ( "../test-data/message/text_plain_flowed.eml" ) ;
313
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
341
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
314
342
assert ! ( parser. plain. unwrap( ) . flowed) ;
315
343
assert_eq ! (
316
344
parser. html,
@@ -332,7 +360,7 @@ and will be wrapped as usual.<br/>
332
360
async fn test_htmlparse_alt_plain ( ) {
333
361
let t = TestContext :: new ( ) . await ;
334
362
let raw = include_bytes ! ( "../test-data/message/text_alt_plain.eml" ) ;
335
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
363
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
336
364
assert_eq ! (
337
365
parser. html,
338
366
r#"<!DOCTYPE html>
@@ -353,7 +381,7 @@ test some special html-characters as < > and & but also " and &#x
353
381
async fn test_htmlparse_html ( ) {
354
382
let t = TestContext :: new ( ) . await ;
355
383
let raw = include_bytes ! ( "../test-data/message/text_html.eml" ) ;
356
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
384
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
357
385
358
386
// on windows, `\r\n` linends are returned from mimeparser,
359
387
// however, rust multiline-strings use just `\n`;
@@ -371,7 +399,7 @@ test some special html-characters as < > and & but also " and &#x
371
399
async fn test_htmlparse_alt_html ( ) {
372
400
let t = TestContext :: new ( ) . await ;
373
401
let raw = include_bytes ! ( "../test-data/message/text_alt_html.eml" ) ;
374
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
402
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
375
403
assert_eq ! (
376
404
parser. html. replace( '\r' , "" ) , // see comment in test_htmlparse_html()
377
405
r##"<html>
@@ -386,7 +414,7 @@ test some special html-characters as < > and & but also " and &#x
386
414
async fn test_htmlparse_alt_plain_html ( ) {
387
415
let t = TestContext :: new ( ) . await ;
388
416
let raw = include_bytes ! ( "../test-data/message/text_alt_plain_html.eml" ) ;
389
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
417
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
390
418
assert_eq ! (
391
419
parser. html. replace( '\r' , "" ) , // see comment in test_htmlparse_html()
392
420
r##"<html>
@@ -411,7 +439,7 @@ test some special html-characters as < > and & but also " and &#x
411
439
assert ! ( test. find( "data:" ) . is_none( ) ) ;
412
440
413
441
// parsing converts cid: to data:
414
- let parser = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
442
+ let ( parser, _ ) = HtmlMsgParser :: from_bytes ( & t. ctx , raw) . await . unwrap ( ) ;
415
443
assert ! ( parser. html. contains( "<html>" ) ) ;
416
444
assert ! ( !parser. html. contains( "Content-Id:" ) ) ;
417
445
assert ! ( parser. html. contains( "data:image/jpeg;base64,/9j/4AAQ" ) ) ;
0 commit comments