@@ -364,9 +364,20 @@ pub(super) struct MessageBuilder<'a> {
364364 pub threading : Option < ThreadingHeaders < ' a > > ,
365365}
366366
367+ /// Escape a filename for use in a MIME quoted-string parameter.
368+ /// Backslashes and double quotes are escaped per RFC 2045/2822.
369+ fn escape_quoted_string ( s : & str ) -> String {
370+ sanitize_header_value ( s)
371+ . replace ( '\\' , "\\ \\ " )
372+ . replace ( '"' , "\\ \" " )
373+ }
374+
367375impl MessageBuilder < ' _ > {
368- /// Build the complete RFC 2822 message (headers + blank line + body).
369- pub fn build ( & self , body : & str ) -> String {
376+ /// Build the common RFC 2822 headers shared by both plain and multipart
377+ /// messages. The `content_type_line` parameter supplies the Content-Type
378+ /// header value (e.g. "text/plain; charset=utf-8" or
379+ /// "multipart/mixed; boundary=\"...\"").
380+ fn build_headers ( & self , content_type_line : & str ) -> String {
370381 debug_assert ! (
371382 !self . to. is_empty( ) ,
372383 "MessageBuilder: `to` must not be empty"
@@ -388,7 +399,7 @@ impl MessageBuilder<'_> {
388399 ) ) ;
389400 }
390401
391- headers. push_str ( "\r \n MIME-Version: 1.0\r \n Content-Type: text/plain; charset=utf-8" ) ;
402+ headers. push_str ( & format ! ( "\r \n MIME-Version: 1.0\r \n Content-Type: {}" , content_type_line ) ) ;
392403
393404 if let Some ( from) = self . from {
394405 headers. push_str ( & format ! ( "\r \n From: {}" , sanitize_header_value( from) ) ) ;
@@ -404,6 +415,12 @@ impl MessageBuilder<'_> {
404415 headers. push_str ( & format ! ( "\r \n Bcc: {}" , sanitize_header_value( bcc) ) ) ;
405416 }
406417
418+ headers
419+ }
420+
421+ /// Build the complete RFC 2822 message (headers + blank line + body).
422+ pub fn build ( & self , body : & str ) -> String {
423+ let headers = self . build_headers ( "text/plain; charset=utf-8" ) ;
407424 format ! ( "{}\r \n \r \n {}" , headers, body)
408425 }
409426
@@ -424,41 +441,7 @@ impl MessageBuilder<'_> {
424441 let mut rng = rand:: thread_rng ( ) ;
425442 let boundary = format ! ( "{:016x}{:016x}" , rng. gen :: <u64 >( ) , rng. gen :: <u64 >( ) ) ;
426443
427- debug_assert ! (
428- !self . to. is_empty( ) ,
429- "MessageBuilder: `to` must not be empty"
430- ) ;
431-
432- let mut headers = format ! (
433- "To: {}\r \n Subject: {}" ,
434- sanitize_header_value( self . to) ,
435- encode_header_value( & sanitize_header_value( self . subject) ) ,
436- ) ;
437-
438- if let Some ( ref threading) = self . threading {
439- headers. push_str ( & format ! (
440- "\r \n In-Reply-To: {}\r \n References: {}" ,
441- sanitize_header_value( threading. in_reply_to) ,
442- sanitize_header_value( threading. references) ,
443- ) ) ;
444- }
445-
446- headers. push_str ( & format ! (
447- "\r \n MIME-Version: 1.0\r \n Content-Type: multipart/mixed; boundary=\" {}\" " ,
448- boundary
449- ) ) ;
450-
451- if let Some ( from) = self . from {
452- headers. push_str ( & format ! ( "\r \n From: {}" , sanitize_header_value( from) ) ) ;
453- }
454-
455- if let Some ( cc) = self . cc {
456- headers. push_str ( & format ! ( "\r \n Cc: {}" , sanitize_header_value( cc) ) ) ;
457- }
458-
459- if let Some ( bcc) = self . bcc {
460- headers. push_str ( & format ! ( "\r \n Bcc: {}" , sanitize_header_value( bcc) ) ) ;
461- }
444+ let headers = self . build_headers ( & format ! ( "multipart/mixed; boundary=\" {}\" " , boundary) ) ;
462445
463446 // Start the multipart body.
464447 let mut message = format ! ( "{}\r \n \r \n " , headers) ;
@@ -480,18 +463,15 @@ impl MessageBuilder<'_> {
480463 . collect :: < Vec < _ > > ( )
481464 . join ( "\r \n " ) ;
482465
466+ let safe_filename = escape_quoted_string ( & att. filename ) ;
483467 message. push_str ( & format ! (
484468 "--{}\r \n \
485469 Content-Type: {}; name=\" {}\" \r \n \
486470 Content-Disposition: attachment; filename=\" {}\" \r \n \
487471 Content-Transfer-Encoding: base64\r \n \
488472 \r \n \
489473 {}\r \n ",
490- boundary,
491- att. content_type,
492- sanitize_header_value( & att. filename) ,
493- sanitize_header_value( & att. filename) ,
494- folded,
474+ boundary, att. content_type, safe_filename, safe_filename, folded,
495475 ) ) ;
496476 }
497477
0 commit comments