add 25 MB per-file attachment size guard to MessageFormatter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -71,10 +71,21 @@ public enum MessageFormatter: Sendable {
|
||||
}
|
||||
|
||||
/// Format a multipart/mixed message with attachments.
|
||||
/// Throws `MessageFormatterError.attachmentTooLarge` if any file exceeds 25 MB.
|
||||
public static func formatMultipart(
|
||||
_ message: OutgoingMessage,
|
||||
attachments: [(filename: String, mimeType: String, data: Data)]
|
||||
) -> String {
|
||||
) throws -> String {
|
||||
let maxSize = 25 * 1024 * 1024 // 25 MB
|
||||
for attachment in attachments {
|
||||
if attachment.data.count > maxSize {
|
||||
throw MessageFormatterError.attachmentTooLarge(
|
||||
filename: attachment.filename,
|
||||
size: attachment.data.count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let boundary = "=_MagnumOpus_\(UUID().uuidString)"
|
||||
|
||||
var headers: [(String, String)] = []
|
||||
@@ -184,3 +195,7 @@ public enum MessageFormatter: Sendable {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageFormatterError: Error {
|
||||
case attachmentTooLarge(filename: String, size: Int)
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ struct MessageFormatterTests {
|
||||
// MARK: - Multipart Formatting
|
||||
|
||||
@Test("format with attachments produces multipart/mixed")
|
||||
func multipartWithAttachment() {
|
||||
func multipartWithAttachment() throws {
|
||||
let message = OutgoingMessage(
|
||||
from: EmailAddress(address: "alice@example.com"),
|
||||
to: [EmailAddress(address: "bob@example.com")],
|
||||
@@ -189,7 +189,7 @@ struct MessageFormatterTests {
|
||||
(filename: "test.pdf", mimeType: "application/pdf", data: Data("PDF content".utf8)),
|
||||
]
|
||||
|
||||
let formatted = MessageFormatter.formatMultipart(message, attachments: attachments)
|
||||
let formatted = try MessageFormatter.formatMultipart(message, attachments: attachments)
|
||||
|
||||
#expect(formatted.contains("Content-Type: multipart/mixed; boundary="))
|
||||
#expect(formatted.contains("Content-Type: text/plain; charset=utf-8"))
|
||||
@@ -225,8 +225,28 @@ struct MessageFormatterTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test("formatMultipart throws for file exceeding 25 MB")
|
||||
func attachmentSizeGuard() {
|
||||
let message = OutgoingMessage(
|
||||
from: EmailAddress(address: "alice@example.com"),
|
||||
to: [EmailAddress(address: "bob@example.com")],
|
||||
subject: "Big file",
|
||||
bodyText: "See attached.",
|
||||
messageId: "test@example.com"
|
||||
)
|
||||
// 26 MB attachment
|
||||
let bigData = Data(repeating: 0xFF, count: 26 * 1024 * 1024)
|
||||
let attachments: [(filename: String, mimeType: String, data: Data)] = [
|
||||
(filename: "huge.bin", mimeType: "application/octet-stream", data: bigData),
|
||||
]
|
||||
|
||||
#expect(throws: MessageFormatterError.self) {
|
||||
try MessageFormatter.formatMultipart(message, attachments: attachments)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("multiple attachments produce correct number of boundary sections")
|
||||
func multipleAttachments() {
|
||||
func multipleAttachments() throws {
|
||||
let message = OutgoingMessage(
|
||||
from: EmailAddress(address: "alice@example.com"),
|
||||
to: [EmailAddress(address: "bob@example.com")],
|
||||
@@ -239,7 +259,7 @@ struct MessageFormatterTests {
|
||||
(filename: "b.jpg", mimeType: "image/jpeg", data: Data("B".utf8)),
|
||||
]
|
||||
|
||||
let formatted = MessageFormatter.formatMultipart(message, attachments: attachments)
|
||||
let formatted = try MessageFormatter.formatMultipart(message, attachments: attachments)
|
||||
|
||||
#expect(formatted.contains("a.pdf"))
|
||||
#expect(formatted.contains("b.jpg"))
|
||||
|
||||
Reference in New Issue
Block a user