diff --git a/Gruntfile.js b/Gruntfile.js index 98616bad..d1e0c351 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -58,4 +58,5 @@ module.exports = function (grunt) { // Tasks grunt.registerTask('default', ['eslint', 'shell:server', 'wait:server', 'mochaTest', 'shell:server:kill']); grunt.registerTask('testonly', ['shell:server', 'wait:server', 'mochaTest', 'shell:server:kill']); + grunt.registerTask('proto', ['mochaTest:imap']); }; diff --git a/imap-core/lib/indexer/parse-mime-tree.js b/imap-core/lib/indexer/parse-mime-tree.js index baf35ce6..7d9a9f41 100644 --- a/imap-core/lib/indexer/parse-mime-tree.js +++ b/imap-core/lib/indexer/parse-mime-tree.js @@ -125,6 +125,23 @@ class MIMEParser { node.message = parse(node.body.join('')); } + if (node.body && node.body.length && node.multipart) { + // find last character from an array of strings + let lastChar; + let lastIndex = 0; + for (let i = node.body.length - 1; i >= 0; i--) { + if (typeof node.body[i] === 'string' && node.body[i].length) { + lastChar = node.body[i].at(-1); + lastIndex = i; + break; + } + } + if (lastChar && lastChar !== '\n') { + // ensure that non-multipart body text always ends with a newline + node.body[lastIndex] += '\n'; + } + } + node.lineCount = node.body.length ? node.body.length - 1 : 0; node.body = Buffer.from( node.body diff --git a/imap-core/test/fixtures/no_empty_line_between_text_boundary.eml b/imap-core/test/fixtures/no_empty_line_between_text_boundary.eml new file mode 100644 index 00000000..cd18c673 --- /dev/null +++ b/imap-core/test/fixtures/no_empty_line_between_text_boundary.eml @@ -0,0 +1,22 @@ +Content-Type: multipart/mixed; boundary="------------cWFvDSey27tFG0hVYLqp9hs9" +MIME-Version: 1.0 +Message-ID: +To: foo-1@example-1.com +From: foo-1@example-1.com +Subject: test + +This is a multi-part message in MIME format. +--------------cWFvDSey27tFG0hVYLqp9hs9 +Content-Type: text/plain; charset=UTF-8; format=flowed +Content-Transfer-Encoding: 7bit + +test + +--------------cWFvDSey27tFG0hVYLqp9hs9 +Content-Type: text/plain; charset=UTF-8; name="example.txt" +Content-Disposition: attachment; filename="example.txt" +Content-Transfer-Encoding: base64 + +ZXhhbXBsZQo= + +--------------cWFvDSey27tFG0hVYLqp9hs9-- \ No newline at end of file diff --git a/imap-core/test/fixtures/no_empty_line_between_text_boundary.eml.2 b/imap-core/test/fixtures/no_empty_line_between_text_boundary.eml.2 new file mode 100644 index 00000000..7428f180 --- /dev/null +++ b/imap-core/test/fixtures/no_empty_line_between_text_boundary.eml.2 @@ -0,0 +1,22 @@ +Content-Type: multipart/mixed; boundary="------------cWFvDSey27tFG0hVYLqp9hs9" +MIME-Version: 1.0 +Message-ID: +To: foo-1@example-1.com +From: foo-1@example-1.com +Subject: test + +This is a multi-part message in MIME format. +--------------cWFvDSey27tFG0hVYLqp9hs9 +Content-Type: text/plain; charset=UTF-8; format=flowed +Content-Transfer-Encoding: 7bit + +test + +--------------cWFvDSey27tFG0hVYLqp9hs9 +Content-Type: text/plain; charset=UTF-8; name="example.txt" +Content-Disposition: attachment; filename="example.txt" +Content-Transfer-Encoding: base64 + +ZXhhbXBsZQo= + +--------------cWFvDSey27tFG0hVYLqp9hs9-- diff --git a/imap-core/test/parse-mime-tree-test.js b/imap-core/test/parse-mime-tree-test.js index f4821a28..6d541bad 100644 --- a/imap-core/test/parse-mime-tree-test.js +++ b/imap-core/test/parse-mime-tree-test.js @@ -3,11 +3,20 @@ 'use strict'; const MIMEParser = require('../lib/indexer/parse-mime-tree').MIMEParser; +const Indexer = require('../lib/indexer/indexer'); +const indexer = new Indexer(); +const fs = require('fs'); const chai = require('chai'); const expect = chai.expect; chai.config.includeStack = true; +const fixtures = { + no_empty_line_between_text_boundary: { + eml: fs.readFileSync(__dirname + '/fixtures/no_empty_line_between_text_boundary.eml') + } +}; + describe('#parseValueParams', function () { it('should return continuation value as mime-word', function () { let parser = new MIMEParser(); @@ -44,4 +53,23 @@ describe('#parseValueParams', function () { hasParams: true }); }); + + it('should parse a file with no empty line between text and boundary', function (done) { + // parse a file and then make sure that boundary is correct + + let source = Buffer.concat([fixtures.no_empty_line_between_text_boundary.eml]); + + let parser = new MIMEParser(source); + + parser.parse(); + parser.finalizeTree(); + + let parsed = parser.tree.childNodes[0]; + + indexer.bodyQuery(parsed, '', (err, data) => { + expect(err).to.not.exist; + expect(data.toString().indexOf('This is a multi-part message in MIME format.\r\n--------------cWFvDSey27tFG0hVYLqp9hs9')).to.gt(0); + done(); + }); + }); }); diff --git a/package.json b/package.json index 7e4071d1..8ee35baf 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "server.js", "scripts": { "test": "mongo --eval 'db.dropDatabase()' wildduck-test && redis-cli -n 13 flushdb && npm run runtest", + "test:proto": "NODE_ENV=test grunt proto", "printconf": "NODE_CONFIG_ONLY=true npm start", "runtest": "NODE_ENV=test grunt", "runci": "npm run printconf && npm run runtest",