Skip to content

Commit

Permalink
fix(mime-parsing): ensure that text content for multipart nodes alway…
Browse files Browse the repository at this point in the history
…s ends with a newline. Fixes #571
  • Loading branch information
andris9 committed Dec 7, 2023
1 parent e553523 commit 6f4994d
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 0 deletions.
1 change: 1 addition & 0 deletions Gruntfile.js
Expand Up @@ -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']);
};
17 changes: 17 additions & 0 deletions imap-core/lib/indexer/parse-mime-tree.js
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions 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: <beep-boop@example-1.com>
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--
22 changes: 22 additions & 0 deletions 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: <beep-boop@example-1.com>
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--
28 changes: 28 additions & 0 deletions imap-core/test/parse-mime-tree-test.js
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
});
});
});
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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",
Expand Down

0 comments on commit 6f4994d

Please sign in to comment.