Files
Maven/internal/email/charset.go
T
claude 4f96bbd6ec email: bound the MIME walk and drop two copies of every body (V-581)
The MIME tree walk had no depth limit, and the nesting comes off the wire.
A boundary line is a few bytes, so one message inside MaxMessageBytes can
declare tens of thousands of multipart levels and pick the recursion depth
of a daemon reading his mail. MaxMIMEDepth stops the walk at 12, well past
the three levels real mail uses, and the headers still come through.

ParseMessage converted the raw message to a string to read it, which copied
up to 2 MiB per mail on a box already holding the resident model. It reads
the bytes directly now. decodeCP1251 collected runes and then copied them
into a string, four bytes a character for the whole body, and writes into a
Builder instead.

No behaviour change to what is read: EXAMINE and BODY.PEEK are still the
only mailbox commands, and no credential reaches a log line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 03:23:36 +04:00

56 lines
2.4 KiB
Go

package email
import "strings"
// windows-1251 (and its ASCII-compatible low half) is decoded here rather than
// pulled in from x/text.
//
// The alternative was returning an error for the charset, which ParseMessage
// turns into a subject-only message. That is a live gap and not a small one:
// cp1251 is still what older Russian senders emit, and subject-only means those
// mails can never produce a task candidate. The whole of x/text/encoding is a
// large dependency for the most privacy-sensitive path in the tree, and
// windows-1251 is a 128-entry table.
//
// Only cp1251 is added. Guessing at an unknown charset stays forbidden: mojibake
// is worse than nothing, because the model extracts a task from it happily.
// cp1251High — the 0x80..0xFF half of windows-1251. The low half is ASCII.
var cp1251High = [128]rune{
0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021,
0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F,
0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0xFFFD, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F,
0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,
0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,
0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7,
0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457,
0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
}
// decodeCP1251 maps each byte through the table. Every byte has a defined
// meaning in this charset, so decoding cannot fail.
//
// It writes into a Builder rather than collecting runes: a []rune of the whole
// body is four bytes a character and was then copied again into the string, so
// a 1 MiB cp1251 mail allocated about 6 MiB to produce roughly 2.
func decodeCP1251(b []byte) string {
var out strings.Builder
out.Grow(len(b))
for _, c := range b {
if c < 0x80 {
out.WriteByte(c)
continue
}
out.WriteRune(cp1251High[c-0x80])
}
return out.String()
}