diff --git a/index.js b/index.js index 05f3ed6..77a20cb 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,7 @@ * @property {string} encoding Content-Transfer-Encoding * @property {string} Body Encoded content * @property {Array} Headers Encoded headers + * @property {MIME} MIME Mime property */ /** @@ -39,6 +40,7 @@ * @property {Date} date Mail Date header * @property {Date} deliveryDate Mail Delivery-Date header * @property {Array} attachments List of mail attachments + * @property {Attachment} Content The main mail content * @property {string} Created Mail Created property * @property {MIME} MIME Mail Mime property */ @@ -173,6 +175,25 @@ function decode(str, encoding, charset) { return require('iconv-lite').decode(buffer, charset) } +/** + * Recursively gathers the attachments of both the passed mail object + * and recursively of its attachment parts. + * + * @param {object} entry MailHog mail or Attachment object + * @returns {Array} List of mail attachments parts + */ +function getMimePartsRecursively(entry) { + if (!entry.MIME) { + return [] + } + let results = [] + for (const part of entry.MIME.Parts) { + results.push(part) + results = results.concat(getMimePartsRecursively(part)) + } + return results +} + /** * Returns the content part matching the given content-type regular expression. * @@ -182,7 +203,7 @@ function decode(str, encoding, charset) { */ function getContent(mail, typeRegExp) { let parts = [mail.Content] - if (mail.MIME) parts = parts.concat(mail.MIME.Parts) + parts = parts.concat(getMimePartsRecursively(mail)) for (const part of parts) { const type = (part.Headers['Content-Type'] || '').toString() if (typeRegExp.test(type)) { diff --git a/index.test.js b/index.test.js index fe7dd70..15917bd 100644 --- a/index.test.js +++ b/index.test.js @@ -219,6 +219,26 @@ describe('multipart', function () { }) }) +describe('multipart hierarchical', function () { + it('parses quoted-printable encoded text content', async function () { + const result = await mailhog.latestTo('multipart-hierarchical@test.com') + assert.strictEqual( + result.text, + 'Some Test Message Text', + 'Parses sub-level text part' + ) + }) + + it('parses quoted-printable encoded HTML content', async function () { + const result = await mailhog.latestTo('multipart-hierarchical@test.com') + assert.strictEqual( + result.html, + 'Some Test Message HTML', + 'Parses sub-level html part' + ) + }) +}) + describe('charset', function () { it('parses mail with utf8 charset', async function () { const result = await mailhog.latestTo('nihon@example.org') @@ -297,31 +317,36 @@ describe('headers', function () { describe('messages', function () { it('retrieve mails', async function () { const result = await mailhog.messages() - assert.strictEqual(result.count, 4, 'Returns all emails') + assert.strictEqual(result.count, 5, 'Returns all emails') assert.strictEqual( result.items[0].subject, + 'Mail with hierarchical mime content', + 'Returns the decoded mail Subject header for the fifth mail in the set' + ) + assert.strictEqual( + result.items[1].subject, 'Mail without charset', 'Returns the decoded mail Subject header for the first mail in the set' ) assert.strictEqual( - result.items[1].subject, + result.items[2].subject, 'ISO-8859-1', 'Returns the decoded mail Subject header for the second mail in the set' ) assert.strictEqual( - result.items[2].subject, + result.items[3].subject, '日本', 'Returns the decoded mail Subject header for the third mail in the set' ) assert.strictEqual( - result.items[3].subject, + result.items[4].subject, 'üäö', 'Returns the decoded mail Subject header for the fourth mail in the set' ) }) it('limit the messages range', async function () { - const result = await mailhog.messages(3, 1) + const result = await mailhog.messages(4, 1) assert.strictEqual(result.count, 1, 'Returns a set for the given range') assert.strictEqual( result.items[0].subject, @@ -469,7 +494,7 @@ describe('releaseMessage', function () { const listResult = await mailhog.messages() assert.strictEqual( listResult.count, - 5, + 6, 'Number of mails stored has increased by 1' ) }) @@ -496,7 +521,7 @@ describe('deleteMessage', function () { const listResult = await mailhog.messages() assert.strictEqual( listResult.count, - 3, + 4, 'Number of mails stored has decreased by 1' ) }) diff --git a/mail/05.eml b/mail/05.eml new file mode 100644 index 0000000..16f5084 --- /dev/null +++ b/mail/05.eml @@ -0,0 +1,33 @@ +Content-Type: multipart/mixed; boundary="===============7426108140616550474==" +Date: Tue, 17 May 2022 11:12:22 +0000 +From: from@test.com +MIME-Version: 1.0 +Message-ID: <165278594245.265.1843128869165443175@09814d7d4841> +Received: from [123.4.5.6] by mailhog.example (MailHog) + id MNnNbGMyvWxyJ7lmXH_0e4PSTgekCuo9dFCqyxeR0QA=@mailhog.example; Tue, 17 May 2022 11:12:22 +0000 +Return-Path: +Subject: Mail with hierarchical mime content +To: multipart-hierarchical@test.com + +--===============7426108140616550474== +Content-Type: multipart/alternative; + boundary="===============1607218409922029753==" +MIME-Version: 1.0 + +--===============1607218409922029753== +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit + +Some Test Message Text + +--===============1607218409922029753== +Content-Type: text/html; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit + +Some Test Message HTML + +--===============1607218409922029753==-- + +--===============7426108140616550474==--