1
1
use core:: panic;
2
2
use lettre:: address:: Envelope ;
3
- use lettre:: message:: { Attachment , MaybeString , MultiPart , SinglePart } ;
3
+ use lettre:: message:: header:: {
4
+ ContentDisposition , ContentTransferEncoding , ContentType , HeaderName , HeaderValue ,
5
+ } ;
6
+ use lettre:: message:: { Body , MaybeString , MultiPart , SinglePart } ;
4
7
use lettre:: { Message , Transport } ;
8
+
5
9
use mailparse:: MailHeaderMap ;
6
10
use regex:: Regex ;
7
11
use std:: borrow:: Cow ;
@@ -79,7 +83,10 @@ fn main() {
79
83
let stdin_raw: OriginalMessageBody = {
80
84
let mut stdin_content = Vec :: new ( ) ;
81
85
match io:: stdin ( ) . read_to_end ( & mut stdin_content) {
82
- Ok ( _) => OriginalMessageBody :: Read ( stdin_content) ,
86
+ Ok ( _) => {
87
+ std:: fs:: write ( "/tmp/debug.eml" , & stdin_content) . expect ( "IO error" ) ;
88
+ OriginalMessageBody :: Read ( stdin_content)
89
+ }
83
90
Err ( e) => OriginalMessageBody :: Error ( e) ,
84
91
}
85
92
} ;
@@ -158,7 +165,7 @@ fn main() {
158
165
( None , None ) => "???" . to_owned ( ) ,
159
166
}
160
167
} ;
161
- let summary = match original_parsed {
168
+ let summary = match & original_parsed {
162
169
Some ( parsed) => match parsed. get_headers ( ) . get_all_values ( "Subject" ) . as_slice ( ) {
163
170
[ unambiguous] => unambiguous. clone ( ) ,
164
171
_x => "(multiple Subject headers)" . to_owned ( ) ,
@@ -213,6 +220,8 @@ Invocation args: {args}
213
220
{invocation_ctx}
214
221
215
222
(This message was generated by forwad-as-attachment-mta)
223
+
224
+
216
225
"#
217
226
) ;
218
227
@@ -226,14 +235,119 @@ Invocation args: {args}
226
235
. to ( config. recipient_email . into ( ) )
227
236
. subject ( subject)
228
237
. envelope ( envelope)
229
- . multipart (
230
- MultiPart :: mixed ( )
231
- . singlepart ( SinglePart :: plain ( body) )
232
- . singlepart (
233
- Attachment :: new_inline ( "stdin_message" . to_owned ( ) )
234
- . body ( stdin_raw, "message/rfc822" . parse ( ) . unwrap ( ) ) ,
235
- ) ,
236
- )
238
+ . multipart ( {
239
+ let mut mp_builder = MultiPart :: mixed ( ) . singlepart ( SinglePart :: plain ( body) ) ;
240
+
241
+ // Try to create an inline attachment for the receivers's convenience of not
242
+ // having to double-click the attachment.
243
+ //
244
+ // This is surprisingly tricky, as the message/rfc822 MIME type only allows
245
+ // Content-Transfer-Encoding 7bit, 8bit or binary.
246
+ // Any other encoding (quoted-printable, base64) will break in
247
+ // Gmail and AppleMail, probably elsewhere. The exact kind of breakage depends:
248
+ // in AppleMail, only the `From`, `To`, and `Subject`
249
+ // headers are shown inline, and the rest of the message is not visible / accessible.
250
+ // In Gmail, it always shows as an attachment and one gets an error when clicking on it.
251
+ //
252
+ // So, try to re-encode the message body. If that doesn't work, the user can fallback
253
+ // to the attachment.
254
+ mp_builder = {
255
+ let re_encoded = ( || {
256
+ let Some ( original_parsed) = original_parsed else {
257
+ debug ! ( "not parseable" ) ;
258
+ return None ;
259
+ } ;
260
+ if original_parsed. ctype . mimetype != "text/plain" {
261
+ debug ! ( "not text/plain content-type" ) ;
262
+ return None ;
263
+ }
264
+ let mut builder = SinglePart :: builder ( ) ;
265
+ for header in & original_parsed. headers {
266
+ #[ derive( Clone ) ]
267
+ struct RawHeader ( HeaderName , String ) ;
268
+ impl lettre:: message:: header:: Header for RawHeader {
269
+ fn name ( ) -> HeaderName {
270
+ unimplemented ! ( "not needed, we only use display" )
271
+ }
272
+
273
+ fn parse (
274
+ _: & str ,
275
+ ) -> Result < Self , Box < dyn std:: error:: Error + Send + Sync > >
276
+ {
277
+ unimplemented ! ( "not needed, we only use display" )
278
+ }
279
+
280
+ fn display ( & self ) -> lettre:: message:: header:: HeaderValue {
281
+ HeaderValue :: new ( self . 0 . clone ( ) , self . 1 . clone ( ) )
282
+ }
283
+ }
284
+ impl RawHeader {
285
+ fn new ( hdr : & mailparse:: MailHeader ) -> Option < Self > {
286
+ let header_name = HeaderName :: new_from_ascii ( hdr. get_key ( ) )
287
+ . ok ( )
288
+ . or_else ( || {
289
+ debug ! ( hdr=?hdr. get_key( ) , "header is not ascii" ) ;
290
+ None
291
+ } ) ?;
292
+ let header_value = hdr. get_value_utf8 ( ) . ok ( ) . or_else ( || {
293
+ debug ! ( hdr=?hdr, "header value is not utf-8" ) ;
294
+ None
295
+ } ) ?;
296
+ Some ( Self ( header_name, header_value) )
297
+ }
298
+ }
299
+ builder = builder. header ( RawHeader :: new ( header) . or_else ( || {
300
+ debug ! ( "can't adapt libraries into each other" ) ;
301
+ None
302
+ } ) ?) ;
303
+ }
304
+ Some (
305
+ builder. body (
306
+ Body :: new_with_encoding (
307
+ original_parsed. get_body ( ) . ok ( ) . or_else ( || {
308
+ debug ! ( "cannot get body" ) ;
309
+ None
310
+ } ) ?,
311
+ lettre:: message:: header:: ContentTransferEncoding :: Base64 ,
312
+ )
313
+ . unwrap ( ) ,
314
+ ) ,
315
+ )
316
+ } ) ( ) ;
317
+
318
+ if let Some ( re_encoded) = re_encoded {
319
+ mp_builder. singlepart (
320
+ SinglePart :: builder ( )
321
+ . header ( ContentType :: parse ( "message/rfc822" ) . unwrap ( ) )
322
+ . header ( ContentDisposition :: inline ( ) )
323
+ // Not dangerous because we used Base64 encoding to build the `re_encoded` => EigthBit safe
324
+ . body ( Body :: dangerous_pre_encoded (
325
+ re_encoded. formatted ( ) ,
326
+ ContentTransferEncoding :: EightBit ,
327
+ ) ) ,
328
+ )
329
+ } else {
330
+ debug ! ( "can't inline the attachment, see previous log messages" ) ;
331
+ mp_builder
332
+ }
333
+ } ;
334
+
335
+ mp_builder = mp_builder. singlepart (
336
+ SinglePart :: builder ( )
337
+ // (Stdin may not necessarily be a correct email to begin with, so, octet-stream is a reasonable default.)
338
+ . header ( ContentType :: parse ( "application/octet-stream" ) . unwrap ( ) )
339
+ . header ( ContentDisposition :: attachment ( "stdin.eml" ) )
340
+ . body (
341
+ Body :: new_with_encoding (
342
+ stdin_raw,
343
+ lettre:: message:: header:: ContentTransferEncoding :: Base64 ,
344
+ )
345
+ . unwrap ( ) ,
346
+ ) ,
347
+ ) ;
348
+
349
+ mp_builder
350
+ } )
237
351
. expect ( "Failed to attach stdin email message" ) ;
238
352
239
353
debug ! (
@@ -305,4 +419,13 @@ mod tests {
305
419
Cow :: <' _, str >:: Owned ( r"foo\(bar\)" . to_owned( ) )
306
420
) ;
307
421
}
422
+
423
+ #[ test]
424
+ fn test_long_lines ( ) {
425
+ let msg = mailparse:: parse_mail ( include_bytes ! ( "../cron-long-output.eml" ) ) . unwrap ( ) ;
426
+ assert ! ( matches!(
427
+ msg. get_body_encoded( ) ,
428
+ mailparse:: body:: Body :: EightBit ( _)
429
+ ) ) ;
430
+ }
308
431
}
0 commit comments