import { describe, expect, it } from "vitest"; import { attachments, decodeHeaderValue, parseHeaders, parseMessage, walkParts, } from "../../src/core/mime"; const MULTIPART = `From: =?UTF-8?Q?Ada_=E2=9C=93?= \r Subject: folded\r value\r MIME-Version: 1.0\r Content-Type: multipart/mixed; boundary="b"\r \r --b\r Content-Type: text/plain; charset=utf-8\r Content-Transfer-Encoding: quoted-printable\r \r hello=20world\r --b\r Content-Type: application/octet-stream\r Content-Disposition: attachment; filename*=UTF-8''report%20one.txt\r Content-Transfer-Encoding: base64\r \r YWJj\r --b--`; describe("MIME parser", () => { it("unfolds fields and decodes RFC 2047 words", () => { const headers = parseHeaders( "Subject: =?UTF-8?Q?Hello_=E2=9C=93?=\r\n\tworld", ); expect(headers[0]?.value).toBe("Hello ✓ world"); expect(decodeHeaderValue("=?ISO-8859-1?Q?Andr=E9?=")).toBe("André"); expect(decodeHeaderValue("=?UTF-8?Q?joined?= =?UTF-8?Q?_words?=")).toBe( "joined words", ); }); it("builds nested parts and decodes transfer encodings", () => { const parsed = parseMessage(MULTIPART); expect(walkParts(parsed.root).map((part) => part.mediaType)).toEqual([ "multipart/mixed", "text/plain", "application/octet-stream", ]); expect(parsed.root.children[0]?.text).toBe("hello world"); expect([...parsed.root.children[1]!.bytes]).toEqual([97, 98, 99]); expect(attachments(parsed.root)[0]).toMatchObject({ filename: "report one.txt", size: 3, }); }); it("preserves arbitrary source octets and exact multipart byte ranges", () => { const prefix = new TextEncoder().encode( "From: a@example.test\r\nContent-Type: multipart/mixed; boundary=x\r\n\r\n--x\r\nContent-Type: application/octet-stream\r\n\r\n", ); const suffix = new TextEncoder().encode("\r\n--x--\r\n"); const bytes = new Uint8Array(prefix.length + 4 + suffix.length); bytes.set(prefix); bytes.set([0x80, 0x81, 0xfe, 0xff], prefix.length); bytes.set(suffix, prefix.length + 4); const parsed = parseMessage(bytes); expect([...parsed.root.children[0]!.bytes]).toEqual([ 0x80, 0x81, 0xfe, 0xff, ]); expect([...parsed.rawBytes]).toEqual([...bytes]); const range = parsed.root.children[0]!.sourceRange!; expect([...parsed.rawBytes.slice(range.bodyStart, range.end)]).toEqual([ 0x80, 0x81, 0xfe, 0xff, ]); }); it("assembles RFC 2231 parameter continuations", () => { const parsed = parseMessage( "From: a@example.test\r\nContent-Type: application/octet-stream\r\nContent-Disposition: attachment; filename*0*=UTF-8''long%20; filename*1*=name%E2%9C%93.bin\r\n\r\ndata", ); expect(attachments(parsed.root)[0]?.filename).toBe("long name✓.bin"); }); it("parses nested message/rfc822 entities", () => { const parsed = parseMessage( "From: a@example.test\nContent-Type: message/rfc822\n\nFrom: b@example.test\nContent-Type: text/plain\n\nnested", ); expect(parsed.root.children[0]?.text).toBe("nested"); }); it("decodes and exposes transferred message attachments", () => { const nested = btoa( "From: b@example.test\r\nContent-Type: text/plain\r\n\r\nnested", ); const parsed = parseMessage( `From: a@example.test\nContent-Type: message/rfc822; name="forwarded.eml"\nContent-Disposition: attachment; filename="forwarded.eml"\nContent-Transfer-Encoding: base64\n\n${nested}`, ); expect(parsed.root.children[0]?.text).toBe("nested"); expect(attachments(parsed.root)[0]).toMatchObject({ filename: "forwarded.eml", }); expect(attachments(parsed.root)[0]?.size).toBeGreaterThan(0); }); it("fails closed for malformed and excessive structures", () => { expect(() => parseMessage("Bad header\n\nbody")).toThrow( /malformed header/iu, ); expect(() => parseMessage("Content-Type: multipart/mixed; boundary=x\n\nnone"), ).toThrow(/boundary/iu); expect(() => parseMessage("From: a@example.test\n\n12345", { maxChars: 4, maxHeaders: 2, maxParts: 2, maxDepth: 2, maxDecodedBytes: 8, }), ).toThrow(/limit/iu); }); });