What happens
encodeHeaderValue() turns a non-ASCII header value into a single RFC 2047 encoded-word:
return `=?utf-8?B?${stringToBase64(value)}?=`
RFC 2047 §2 caps an encoded-word at 75 characters, and RFC 5322 §2.1.1 caps a line at 998 octets. Neither is enforced. foldHeader() cannot help: it folds on spaces, and base64 has none — so the whole thing is emitted as one unbroken line, however long the subject is.
Reproduction
import { buildMime } from "../src/drivers/_mime.ts"
const mime = (subject: string) => buildMime({
from: { email: "hi@acme.com", name: "Acme" }, to: [{ email: "a@x.com" }],
cc: [], bcc: [], replyTo: [], subject, text: "hello",
messageId: "<1@acme.com>", date: new Date(0),
})
const a = mime("Willkommen bei Acme — Ihre Bestellung über Straßenbahnfahrscheine ist unterwegs")
const line = a.body.split("\r\n").find((l) => l.startsWith("Subject:"))!
console.log("subject header line length:", line.length)
console.log(line)
console.log("encoded-word length:", /=\?utf-8\?B\?[^?]*\?=/.exec(line)![0].length, "(RFC 2047 max 75)")
const b = mime("Zusammenfassung Ihrer Bestellung — ".repeat(40))
console.log("longest header line:", Math.max(...b.body.split("\r\n").map((l) => l.length)), "(RFC 5322 hard cap 998)")
Observed output:
subject header line length: 133
Subject: =?utf-8?B?V2lsbGtvbW1lbiBiZWkgQWNtZSDigJQgSWhyZSBCZXN0ZWxsdW5nIMO8YmVyIFN0cmHDn2VuYmFobmZhaHJzY2hlaW5lIGlzdCB1bnRlcndlZ3M=?=
encoded-word length: 124 (RFC 2047 max 75)
longest header line: 1997 (RFC 5322 hard cap 998)
Why it matters
This is the SMTP and SES path (_mime.buildMime feeds both smtp.send and ses.toPayload's Content.Raw), and a non-ASCII subject is the normal case for any non-English sender.
A 124-character encoded-word is over the RFC 2047 limit but is tolerated by most clients. A 1997-octet header line is over RFC 5322's hard limit and is not: an MTA is entitled to reject the message or to fold it itself at an arbitrary point, and a fold inserted in the middle of a base64 encoded-word makes the subject undecodable — the recipient sees the raw =?utf-8?B?… text. Because it happens after DKIM signing, a header rewritten in transit also invalidates the signature.
The same function encodes attachment filenames (_mime.ts:185), so a long non-ASCII filename has the same problem.
Where
src/drivers/_mime.ts:226-229 — encodeHeaderValue()
src/drivers/_mime.ts:208-222 — foldHeader(), which cannot split a space-free value
- callers:
src/drivers/_mime.ts:81 (Subject), :185 (attachment filename)
Suggested fix
Encode in chunks: split the input into runs whose base64 form keeps each encoded-word at or under 75 characters (cut on whole UTF-8 code points, never mid-sequence), emit them as separate encoded-words separated by CRLF + a single space, which RFC 2047 defines as the way to continue. foldHeader then has the whitespace it needs. Give foldHeader a hard 998-octet backstop for any value it still cannot split.
What happens
encodeHeaderValue()turns a non-ASCII header value into a single RFC 2047 encoded-word:RFC 2047 §2 caps an encoded-word at 75 characters, and RFC 5322 §2.1.1 caps a line at 998 octets. Neither is enforced.
foldHeader()cannot help: it folds on spaces, and base64 has none — so the whole thing is emitted as one unbroken line, however long the subject is.Reproduction
Observed output:
Why it matters
This is the SMTP and SES path (
_mime.buildMimefeeds bothsmtp.sendandses.toPayload'sContent.Raw), and a non-ASCII subject is the normal case for any non-English sender.A 124-character encoded-word is over the RFC 2047 limit but is tolerated by most clients. A 1997-octet header line is over RFC 5322's hard limit and is not: an MTA is entitled to reject the message or to fold it itself at an arbitrary point, and a fold inserted in the middle of a base64 encoded-word makes the subject undecodable — the recipient sees the raw
=?utf-8?B?…text. Because it happens after DKIM signing, a header rewritten in transit also invalidates the signature.The same function encodes attachment filenames (
_mime.ts:185), so a long non-ASCII filename has the same problem.Where
src/drivers/_mime.ts:226-229—encodeHeaderValue()src/drivers/_mime.ts:208-222—foldHeader(), which cannot split a space-free valuesrc/drivers/_mime.ts:81(Subject),:185(attachment filename)Suggested fix
Encode in chunks: split the input into runs whose base64 form keeps each encoded-word at or under 75 characters (cut on whole UTF-8 code points, never mid-sequence), emit them as separate encoded-words separated by CRLF + a single space, which RFC 2047 defines as the way to continue.
foldHeaderthen has the whitespace it needs. GivefoldHeadera hard 998-octet backstop for any value it still cannot split.