Skip to content

Commit 3538f28

Browse files
pae23pae-hexa
authored andcommitted
fix: address Gemini review feedback
- Extract shared header-building logic into `build_headers()` private method, eliminating duplication between `build()` and `build_with_attachments()`. - Add `escape_quoted_string()` helper to properly escape backslashes and double quotes in MIME filename parameters (RFC 2045/2822).
1 parent f43b0b5 commit 3538f28

1 file changed

Lines changed: 23 additions & 43 deletions

File tree

src/helpers/gmail/mod.rs

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
367375
impl 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\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8");
402+
headers.push_str(&format!("\r\nMIME-Version: 1.0\r\nContent-Type: {}", content_type_line));
392403

393404
if let Some(from) = self.from {
394405
headers.push_str(&format!("\r\nFrom: {}", sanitize_header_value(from)));
@@ -404,6 +415,12 @@ impl MessageBuilder<'_> {
404415
headers.push_str(&format!("\r\nBcc: {}", 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\nSubject: {}",
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\nIn-Reply-To: {}\r\nReferences: {}",
441-
sanitize_header_value(threading.in_reply_to),
442-
sanitize_header_value(threading.references),
443-
));
444-
}
445-
446-
headers.push_str(&format!(
447-
"\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary=\"{}\"",
448-
boundary
449-
));
450-
451-
if let Some(from) = self.from {
452-
headers.push_str(&format!("\r\nFrom: {}", sanitize_header_value(from)));
453-
}
454-
455-
if let Some(cc) = self.cc {
456-
headers.push_str(&format!("\r\nCc: {}", sanitize_header_value(cc)));
457-
}
458-
459-
if let Some(bcc) = self.bcc {
460-
headers.push_str(&format!("\r\nBcc: {}", 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

Comments
 (0)