4f96bbd6ec
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>
56 lines
2.4 KiB
Go
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()
|
|
}
|