From 62f3bde8fc1c2b65420779a4052830686db1c0ca Mon Sep 17 00:00:00 2001 From: Daniel Nylander Date: Wed, 25 Mar 2026 08:55:04 +0100 Subject: [PATCH] fix: use 8bit Content-Transfer-Encoding for non-ASCII text bodies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the message body contains non-ASCII characters (e.g. UTF-8 text in Swedish, German, etc.), the MIME Content-Transfer-Encoding was incorrectly set to '7bit'. Per RFC 2045 §2.7, 7bit encoding must not contain octets with the high bit set. This could cause mail servers to mangle the message body or, in some cases, strip attachments from multipart/mixed messages. Fix: introduce transferEncodingForText() that returns '8bit' when the text contains non-ASCII bytes, and '7bit' otherwise. Applied to all text body parts: standalone, multipart/alternative, and inline parts within multipart/mixed (with attachments). --- internal/cmd/gmail_mime.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/internal/cmd/gmail_mime.go b/internal/cmd/gmail_mime.go index 73c6ef81..4542b581 100644 --- a/internal/cmd/gmail_mime.go +++ b/internal/cmd/gmail_mime.go @@ -136,13 +136,13 @@ func buildRFC822(opts mailOptions, cfg *rfc822Config) ([]byte, error) { return b.Bytes(), nil case hasHTML && !hasPlain: writeHeader(&b, "Content-Type", "text/html; charset=\"utf-8\"") - writeHeader(&b, "Content-Transfer-Encoding", "7bit") + writeHeader(&b, "Content-Transfer-Encoding", transferEncodingForText(htmlBody)) b.WriteString("\r\n") writeBodyWithTrailingCRLF(&b, htmlBody) return b.Bytes(), nil default: writeHeader(&b, "Content-Type", "text/plain; charset=\"utf-8\"") - writeHeader(&b, "Content-Transfer-Encoding", "7bit") + writeHeader(&b, "Content-Transfer-Encoding", transferEncodingForText(plainBody)) b.WriteString("\r\n") writeBodyWithTrailingCRLF(&b, plainBody) return b.Bytes(), nil @@ -171,11 +171,11 @@ func buildRFC822(opts mailOptions, cfg *rfc822Config) ([]byte, error) { fmt.Fprintf(&b, "--%s--\r\n", altBoundary) case hasHTML && !hasPlain: b.WriteString("Content-Type: text/html; charset=\"utf-8\"\r\n") - b.WriteString("Content-Transfer-Encoding: 7bit\r\n\r\n") + fmt.Fprintf(&b, "Content-Transfer-Encoding: %s\r\n\r\n", transferEncodingForText(htmlBody)) writeBodyWithTrailingCRLF(&b, htmlBody) default: b.WriteString("Content-Type: text/plain; charset=\"utf-8\"\r\n") - b.WriteString("Content-Transfer-Encoding: 7bit\r\n\r\n") + fmt.Fprintf(&b, "Content-Transfer-Encoding: %s\r\n\r\n", transferEncodingForText(plainBody)) writeBodyWithTrailingCRLF(&b, plainBody) } @@ -292,10 +292,21 @@ func writeBodyWithTrailingCRLF(b *bytes.Buffer, body string) { func writeTextPart(b *bytes.Buffer, boundary string, contentType string, body string) { _, _ = fmt.Fprintf(b, "--%s\r\n", boundary) _, _ = fmt.Fprintf(b, "Content-Type: %s\r\n", contentType) - b.WriteString("Content-Transfer-Encoding: 7bit\r\n\r\n") + _, _ = fmt.Fprintf(b, "Content-Transfer-Encoding: %s\r\n\r\n", transferEncodingForText(body)) writeBodyWithTrailingCRLF(b, body) } +// transferEncodingForText returns "7bit" for pure ASCII text and "8bit" for +// text containing non-ASCII bytes (e.g. UTF-8). Using "7bit" for UTF-8 content +// violates RFC 2045 §2.7 and can cause mail servers to mangle the message or +// strip attachments. +func transferEncodingForText(s string) string { + if isASCII(s) { + return "7bit" + } + return "8bit" +} + func randomBoundary() (string, error) { var b [18]byte if _, err := rand.Read(b[:]); err != nil {