Files
2025-11-02 22:52:08 +01:00

1397 lines
49 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint no-unused-expressions:0 */
/* globals afterEach, beforeEach, describe, it */
'use strict';
var chai = require('chai');
var sinon = require('sinon');
var Buildmail = require('../lib/buildmail');
var http = require('http');
var stream = require('stream');
var Transform = stream.Transform;
var PassThrough = stream.PassThrough;
var expect = chai.expect;
chai.config.includeStack = true;
describe('Buildmail', function () {
it('should create Buildmail object', function () {
expect(new Buildmail()).to.exist;
});
describe('#createChild', function () {
it('should create child', function () {
var mb = new Buildmail('multipart/mixed');
var child = mb.createChild('multipart/mixed');
expect(child.parentNode).to.equal(mb);
expect(child.rootNode).to.equal(mb);
var subchild1 = child.createChild('text/html');
expect(subchild1.parentNode).to.equal(child);
expect(subchild1.rootNode).to.equal(mb);
var subchild2 = child.createChild('text/html');
expect(subchild2.parentNode).to.equal(child);
expect(subchild2.rootNode).to.equal(mb);
});
});
describe('#appendChild', function () {
it('should append child node', function () {
var mb = new Buildmail('multipart/mixed');
var child = new Buildmail('text/plain');
mb.appendChild(child);
expect(child.parentNode).to.equal(mb);
expect(child.rootNode).to.equal(mb);
expect(mb.childNodes.length).to.equal(1);
expect(mb.childNodes[0]).to.equal(child);
});
});
describe('#replace', function () {
it('should replace node', function () {
var mb = new Buildmail(),
child = mb.createChild('text/plain'),
replacement = new Buildmail('image/png');
child.replace(replacement);
expect(mb.childNodes.length).to.equal(1);
expect(mb.childNodes[0]).to.equal(replacement);
});
});
describe('#remove', function () {
it('should remove node', function () {
var mb = new Buildmail(),
child = mb.createChild('text/plain');
child.remove();
expect(mb.childNodes.length).to.equal(0);
expect(child.parenNode).to.not.exist;
});
});
describe('#setHeader', function () {
it('should set header', function () {
var mb = new Buildmail();
mb.setHeader('key', 'value');
mb.setHeader('key', 'value1');
expect(mb.getHeader('Key')).to.equal('value1');
mb.setHeader([{
key: 'key',
value: 'value2'
}, {
key: 'key2',
value: 'value3'
}]);
expect(mb._headers).to.deep.equal([{
key: 'Key',
value: 'value2'
}, {
key: 'Key2',
value: 'value3'
}]);
mb.setHeader({
key: 'value4',
key2: 'value5'
});
expect(mb._headers).to.deep.equal([{
key: 'Key',
value: 'value4'
}, {
key: 'Key2',
value: 'value5'
}]);
});
it('should set multiple headers with the same key', function () {
var mb = new Buildmail();
mb.setHeader('key', ['value1', 'value2', 'value3']);
expect(mb._headers).to.deep.equal([{
key: 'Key',
value: ['value1', 'value2', 'value3']
}]);
});
});
describe('#addHeader', function () {
it('should add header', function () {
var mb = new Buildmail();
mb.addHeader('key', 'value1');
mb.addHeader('key', 'value2');
mb.addHeader([{
key: 'key',
value: 'value2'
}, {
key: 'key2',
value: 'value3'
}]);
mb.addHeader({
key: 'value4',
key2: 'value5'
});
expect(mb._headers).to.deep.equal([{
key: 'Key',
value: 'value1'
}, {
key: 'Key',
value: 'value2'
}, {
key: 'Key',
value: 'value2'
}, {
key: 'Key2',
value: 'value3'
}, {
key: 'Key',
value: 'value4'
}, {
key: 'Key2',
value: 'value5'
}]);
});
it('should set multiple headers with the same key', function () {
var mb = new Buildmail();
mb.addHeader('key', ['value1', 'value2', 'value3']);
expect(mb._headers).to.deep.equal([{
key: 'Key',
value: 'value1'
}, {
key: 'Key',
value: 'value2'
}, {
key: 'Key',
value: 'value3'
}]);
});
});
describe('#getHeader', function () {
it('should return first matching header value', function () {
var mb = new Buildmail();
mb._headers = [{
key: 'Key',
value: 'value4'
}, {
key: 'Key2',
value: 'value5'
}];
expect(mb.getHeader('KEY')).to.equal('value4');
});
});
describe('#setContent', function () {
it('should set the contents for a node', function () {
var mb = new Buildmail();
mb.setContent('abc');
expect(mb.content).to.equal('abc');
});
});
describe('#build', function () {
it('should build root node', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
date: '12345',
'message-id': '67890'
}).
setContent('Hello world!'),
expected = 'Content-Type: text/plain\r\n' +
'Date: 12345\r\n' +
'Message-ID: <67890>\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'Hello world!';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should build child node', function (done) {
var mb = new Buildmail('multipart/mixed'),
childNode = mb.createChild('text/plain').
setContent('Hello world!'),
expected = 'Content-Type: text/plain\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
'Hello world!';
childNode.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should build multipart node', function (done) {
var mb = new Buildmail('multipart/mixed', {
baseBoundary: 'test'
}).setHeader({
date: '12345',
'message-id': '67890'
}),
expected = 'Content-Type: multipart/mixed; boundary="----sinikael-?=_1-test"\r\n' +
'Date: 12345\r\n' +
'Message-ID: <67890>\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'------sinikael-?=_1-test\r\n' +
'Content-Type: text/plain\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
'Hello world!\r\n' +
'------sinikael-?=_1-test--\r\n';
mb.createChild('text/plain').setContent('Hello world!');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should build root with generated headers', function (done) {
var mb = new Buildmail('text/plain');
mb.hostname = 'abc';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Date:\s/m.test(msg)).to.be.true;
expect(/^Message\-ID:\s/m.test(msg)).to.be.true;
expect(/^MIME-Version: 1\.0$/m.test(msg)).to.be.true;
done();
});
});
it('should not include bcc missing in output, but in envelope', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
from: 'sender@example.com',
to: 'receiver@example.com',
bcc: 'bcc@example.com'
});
var envelope = mb.getEnvelope();
expect(envelope).to.deep.equal({
from: 'sender@example.com',
to: ['receiver@example.com', 'bcc@example.com']
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^From: sender@example.com$/m.test(msg)).to.be.true;
expect(/^To: receiver@example.com$/m.test(msg)).to.be.true;
expect(!/^Bcc:/m.test(msg)).to.be.true;
done();
});
});
it('should include bcc missing in output and in envelope', function (done) {
var mb = new Buildmail(
'text/plain', {
keepBcc: true
}).
setHeader({
from: 'sender@example.com',
to: 'receiver@example.com',
bcc: 'bcc@example.com'
});
var envelope = mb.getEnvelope();
expect(envelope).to.deep.equal({
from: 'sender@example.com',
to: ['receiver@example.com', 'bcc@example.com']
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^From: sender@example.com$/m.test(msg)).to.be.true;
expect(/^To: receiver@example.com$/m.test(msg)).to.be.true;
expect(/^Bcc: bcc@example.com$/m.test(msg)).to.be.true;
done();
});
});
it('should use set envelope', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
from: 'sender@example.com',
to: 'receiver@example.com',
bcc: 'bcc@example.com'
}).setEnvelope({
from: 'U Name, A Name <a@a.a>',
to: 'B Name <b@b.b>, c@c.c',
bcc: 'P P P, <u@u.u>'
});
var envelope = mb.getEnvelope();
expect(envelope).to.deep.equal({
from: 'a@a.a',
to: ['b@b.b', 'c@c.c', 'u@u.u']
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^From: sender@example.com$/m.test(msg)).to.be.true;
expect(/^To: receiver@example.com$/m.test(msg)).to.be.true;
expect(!/^Bcc:/m.test(msg)).to.be.true;
done();
});
});
it('should have unicode subject', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
subject: 'jõgeval istus kägu metsas'
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Subject: =\?UTF-8\?Q\?j=C3=B5geval\?= istus =\?UTF-8\?Q\?k=C3=A4gu\?= metsas$/m.test(msg)).to.be.true;
done();
});
});
it('should have unicode subject with strange characters', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
subject: 'ˆ¸ÁÌÓıÏˇÁÛ^¸\\ÁıˆÌÁÛØ^\\˜Û˝™ˇıÓ¸^\\˜fi^\\·\\˜Ø^£˜#fi^\\£fi^\\£fi^\\'
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg.match(/\bSubject: [^\r]*\r\n( [^\r]*\r\n)*/)[0]).to.equal('Subject: =?UTF-8?B?y4bCuMOBw4zDk8Sxw4/Lh8OBw5tewrhcw4HEscuG?=\r\n =?UTF-8?B?w4zDgcObw5heXMucw5vLneKEosuHxLHDk8K4Xlw=?=\r\n =?UTF-8?B?y5zvrIFeXMK3XMucw5hewqPLnCPvrIFeXMKj76yB?=\r\n =?UTF-8?B?XlzCo++sgV5c?=\r\n');
done();
});
});
it('should keep 7bit text as is', function (done) {
var mb = new Buildmail('text/plain').
setContent('tere tere');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/\r\n\r\ntere tere$/.test(msg)).to.be.true;
expect(/^Content-Type: text\/plain$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: 7bit$/m.test(msg)).to.be.true;
done();
});
});
it('should prefer base64', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
subject: 'õõõõ'
}).
setContent('õõõõõõõõ');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/plain; charset=utf-8$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: base64$/m.test(msg)).to.be.true;
expect(/^Subject: =\?UTF-8\?B\?w7XDtcO1w7U=\?=$/m.test(msg)).to.be.true;
msg = msg.split('\r\n\r\n');
msg.shift();
msg = msg.join('\r\n\r\n');
expect(msg).to.equal('w7XDtcO1w7XDtcO1w7XDtQ==');
done();
});
});
it('should force quoted-printable', function (done) {
var mb = new Buildmail('text/plain', {
textEncoding: 'quoted-printable'
}).setHeader({
subject: 'õõõõ'
}).
setContent('õõõõõõõõ');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/plain; charset=utf-8$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true;
expect(/^Subject: =\?UTF-8\?Q\?=C3=B5=C3=B5=C3=B5=C3=B5\?=$/m.test(msg)).to.be.true;
msg = msg.split('\r\n\r\n');
msg.shift();
msg = msg.join('\r\n\r\n');
expect(msg).to.equal('=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5=C3=B5');
done();
});
});
it('should prefer quoted-printable', function (done) {
var mb = new Buildmail('text/plain').
setContent('ooooooooõ');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/plain; charset=utf-8$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true;
msg = msg.split('\r\n\r\n');
msg.shift();
msg = msg.join('\r\n\r\n');
expect(msg).to.equal('oooooooo=C3=B5');
done();
});
});
it('should not flow text', function (done) {
var mb = new Buildmail('text/plain').
setContent('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/plain$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true;
msg = msg.split('\r\n\r\n');
msg.shift();
msg = msg.join('\r\n\r\n');
expect(msg).to.equal('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d=\r\n e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 =\r\n0');
done();
});
});
it('should not flow html', function (done) {
var mb = new Buildmail('text/html').
setContent('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/html$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true;
msg = msg.split('\r\n\r\n');
msg.shift();
msg = msg.join('\r\n\r\n');
expect(msg).to.equal('a b c d e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 0 a b c d=\r\n e f g h i j k l m o p q r s t u w x y z 1 2 3 4 5 6 7 8 9 =\r\n0');
done();
});
});
it('should use 7bit for html', function (done) {
var mb = new Buildmail('text/html').
setContent('a b c d e f g h i j k l m o p\r\nq r s t u w x y z 1 2 3 4 5 6\r\n7 8 9 0 a b c d e f g h i j k\r\nl m o p q r s t u w x y z\r\n1 2 3 4 5 6 7 8 9 0');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/html$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: 7bit$/m.test(msg)).to.be.true;
msg = msg.split('\r\n\r\n');
msg.shift();
msg = msg.join('\r\n\r\n');
expect(msg).to.equal('a b c d e f g h i j k l m o p\r\nq r s t u w x y z 1 2 3 4 5 6\r\n7 8 9 0 a b c d e f g h i j k\r\nl m o p q r s t u w x y z\r\n1 2 3 4 5 6 7 8 9 0');
done();
});
});
it('should fetch ascii filename', function (done) {
var mb = new Buildmail('text/plain', {
filename: 'jogeva.txt'
}).
setContent('jogeva');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/\r\n\r\njogeva$/.test(msg)).to.be.true;
expect(/^Content-Type: text\/plain; name=jogeva.txt$/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: 7bit$/m.test(msg)).to.be.true;
expect(/^Content-Disposition: attachment; filename=jogeva.txt$/m.test(msg)).to.be.true;
done();
});
});
it('should set unicode filename', function (done) {
var mb = new Buildmail('text/plain', {
filename: 'jõgeva.txt'
}).
setContent('jõgeva');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/plain; charset=utf-8;/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true;
expect(/^Content-Disposition: attachment; filename\*0\*=utf-8''j%C3%B5geva.txt$/m.test(msg)).to.be.true;
done();
});
});
it('should set dashed filename', function (done) {
var mb = new Buildmail('text/plain', {
filename: 'Ɣ------Ɣ------Ɣ------Ɣ------Ɣ------Ɣ------Ɣ------.pdf'
}).
setContent('jõgeva');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg.indexOf('Content-Disposition: attachment;\r\n' +
' filename*0*=utf-8\'\'%C6%94------%C6%94------%C6%94------%C6%94;\r\n' +
' filename*1*=------%C6%94------%C6%94------%C6%94------.pdf')).to.be.gte(0);
done();
});
});
it('should encode filename with a space', function (done) {
var mb = new Buildmail('text/plain', {
filename: 'document a.test.pdf'
}).
setContent('jõgeva');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: text\/plain; charset=utf-8;/m.test(msg)).to.be.true;
expect(/^Content-Transfer-Encoding: quoted-printable$/m.test(msg)).to.be.true;
expect(/^Content-Disposition: attachment; filename="document a.test.pdf"$/m.test(msg)).to.be.true;
done();
});
});
it('should detect content type from filename', function (done) {
var mb = new Buildmail(false, {
filename: 'jogeva.zip'
}).
setContent('jogeva');
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Content-Type: application\/zip;/m.test(msg)).to.be.true;
done();
});
});
it('should convert address objects', function (done) {
var mb = new Buildmail(false).
setHeader({
from: [{
name: 'the safewithme testuser',
address: 'safewithme.testuser@jõgeva.com'
}],
cc: [{
name: 'the safewithme testuser',
address: 'safewithme.testuser@jõgeva.com'
}]
});
expect(mb.getEnvelope()).to.deep.equal({
from: 'safewithme.testuser@xn--jgeva-dua.com',
to: [
'safewithme.testuser@xn--jgeva-dua.com'
]
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^From: the safewithme testuser <safewithme.testuser@xn\-\-jgeva-dua.com>$/m.test(msg)).to.be.true;
expect(/^Cc: the safewithme testuser <safewithme.testuser@xn\-\-jgeva-dua.com>$/m.test(msg)).to.be.true;
done();
});
});
it('should skip empty header', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
a: 'b',
cc: '',
dd: [],
o: false,
date: 'zzz',
'message-id': '67890'
}).
setContent('Hello world!'),
expected = 'Content-Type: text/plain\r\n' +
'A: b\r\n' +
'Date: zzz\r\n' +
'Message-ID: <67890>\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'Hello world!';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should not process prepared headers', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
unprepared: {
value: new Array(100).join('a b')
},
prepared: {
value: new Array(100).join('a b'),
prepared: true
},
unicode: {
value: 'õäöü',
prepared: true
},
date: 'zzz',
'message-id': '67890'
}).
setContent('Hello world!'),
expected = 'Content-Type: text/plain\r\n' +
// long folded value
'Unprepared: a ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' +
' ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' +
' ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' +
' ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba\r\n' +
' ba ba ba b\r\n' +
// long unfolded value
'Prepared: a ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba ba b\r\n' +
// non-ascii value
'Unicode: õäöü\r\n' +
'Date: zzz\r\n' +
'Message-ID: <67890>\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'Hello world!';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should set default transfer encoding for application content', function (done) {
var mb = new Buildmail('application/x-my-stuff').
setHeader({
date: '12345',
'message-id': '67890'
}).
setContent('Hello world!'),
expected = 'Content-Type: application/x-my-stuff\r\n' +
'Date: 12345\r\n' +
'Message-ID: <67890>\r\n' +
'Content-Transfer-Encoding: base64\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'SGVsbG8gd29ybGQh';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should not set transfer encoding for multipart content', function (done) {
var mb = new Buildmail('multipart/global').
setHeader({
date: '12345',
'message-id': '67890'
}).
setContent('Hello world!'),
expected = 'Content-Type: multipart/global; boundary=abc\r\n' +
'Date: 12345\r\n' +
'Message-ID: <67890>\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'Hello world!\r\n' +
'--abc--' +
'\r\n';
mb.boundary = 'abc';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should not set transfer encoding for message/ content', function (done) {
var mb = new Buildmail('message/rfc822').
setHeader({
date: '12345',
'message-id': '67890'
}).
setContent('Hello world!'),
expected = 'Content-Type: message/rfc822\r\n' +
'Date: 12345\r\n' +
'Message-ID: <67890>\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'Hello world!';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should use from domain for message-id', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
from: 'test@example.com'
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Message-ID: <[0-9a-f\-]+@example\.com>$/m.test(msg)).to.be.true;
done();
});
});
it('should fallback to hostname for message-id', function (done) {
var mb = new Buildmail('text/plain');
mb.hostname = 'abc';
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^Message-ID: <[0-9a-f\-]+@abc>$/m.test(msg)).to.be.true;
done();
});
});
});
describe('#getEnvelope', function () {
it('should get envelope', function () {
expect(new Buildmail().addHeader({
from: 'From <from@example.com>',
sender: 'Sender <sender@example.com>',
to: 'receiver1@example.com'
}).addHeader({
to: 'receiver2@example.com',
cc: 'receiver1@example.com, receiver3@example.com',
bcc: 'receiver4@example.com, Rec5 <receiver5@example.com>'
}).getEnvelope()).to.deep.equal({
from: 'from@example.com',
to: ['receiver1@example.com', 'receiver2@example.com', 'receiver3@example.com', 'receiver4@example.com', 'receiver5@example.com']
});
expect(new Buildmail().addHeader({
sender: 'Sender <sender@example.com>',
to: 'receiver1@example.com'
}).addHeader({
to: 'receiver2@example.com',
cc: 'receiver1@example.com, receiver3@example.com',
bcc: 'receiver4@example.com, Rec5 <receiver5@example.com>'
}).getEnvelope()).to.deep.equal({
from: 'sender@example.com',
to: ['receiver1@example.com', 'receiver2@example.com', 'receiver3@example.com', 'receiver4@example.com', 'receiver5@example.com']
});
});
});
describe('#messageId', function () {
it('should create and return message-Id', function () {
var mail = new Buildmail().addHeader({
from: 'From <from@example.com>'
});
var messageId = mail.messageId();
expect(/^<[\w\-]+@example\.com>$/.test(messageId)).to.be.true;
expect(messageId).to.equal(mail.messageId());
});
});
describe('#getAddresses', function () {
it('should get address object', function () {
expect(new Buildmail().addHeader({
from: 'From <from@example.com>',
sender: 'Sender <sender@example.com>',
to: 'receiver1@example.com'
}).addHeader({
to: 'receiver2@example.com',
cc: 'receiver1@example.com, receiver3@example.com',
bcc: 'receiver4@example.com, Rec5 <receiver5@example.com>'
}).getAddresses()).to.deep.equal({
from: [{
address: 'from@example.com',
name: 'From'
}],
sender: [{
address: 'sender@example.com',
name: 'Sender'
}],
to: [{
address: 'receiver1@example.com',
name: ''
}, {
address: 'receiver2@example.com',
name: ''
}],
cc: [{
address: 'receiver1@example.com',
name: ''
}, {
address: 'receiver3@example.com',
name: ''
}],
bcc: [{
address: 'receiver4@example.com',
name: ''
}, {
address: 'receiver5@example.com',
name: 'Rec5'
}]
});
expect(new Buildmail().addHeader({
sender: 'Sender <sender@example.com>',
to: 'receiver1@example.com'
}).addHeader({
to: 'receiver2@example.com',
cc: 'receiver1@example.com, receiver1@example.com',
bcc: 'receiver4@example.com, Rec5 <receiver5@example.com>'
}).getAddresses()).to.deep.equal({
sender: [{
address: 'sender@example.com',
name: 'Sender'
}],
to: [{
address: 'receiver1@example.com',
name: ''
}, {
address: 'receiver2@example.com',
name: ''
}],
cc: [{
address: 'receiver1@example.com',
name: ''
}],
bcc: [{
address: 'receiver4@example.com',
name: ''
}, {
address: 'receiver5@example.com',
name: 'Rec5'
}]
});
});
});
describe('#_parseAddresses', function () {
it('should normalize header key', function () {
var mb = new Buildmail();
expect(mb._parseAddresses('test address@example.com')).to.deep.equal([{
address: 'address@example.com',
name: 'test'
}]);
expect(mb._parseAddresses(['test address@example.com'])).to.deep.equal([{
address: 'address@example.com',
name: 'test'
}]);
expect(mb._parseAddresses([
['test address@example.com']
])).to.deep.equal([{
address: 'address@example.com',
name: 'test'
}]);
expect(mb._parseAddresses([{
address: 'address@example.com',
name: 'test'
}])).to.deep.equal([{
address: 'address@example.com',
name: 'test'
}]);
});
});
describe('#_normalizeHeaderKey', function () {
it('should normalize header key', function () {
var mb = new Buildmail();
expect(mb._normalizeHeaderKey('key')).to.equal('Key');
expect(mb._normalizeHeaderKey('mime-vERSION')).to.equal('MIME-Version');
expect(mb._normalizeHeaderKey('-a-long-name')).to.equal('-A-Long-Name');
expect(mb._normalizeHeaderKey('some-spf')).to.equal('Some-SPF');
expect(mb._normalizeHeaderKey('dkim-some')).to.equal('DKIM-Some');
expect(mb._normalizeHeaderKey('x-smtpapi')).to.equal('X-SMTPAPI');
expect(mb._normalizeHeaderKey('message-id')).to.equal('Message-ID');
expect(mb._normalizeHeaderKey('CONTENT-FEATUres')).to.equal('Content-features');
});
});
describe('#_handleContentType', function () {
it('should do nothing on non multipart', function () {
var mb = new Buildmail();
expect(mb.boundary).to.not.exist;
mb._handleContentType({
value: 'text/plain'
});
expect(mb.boundary).to.be.false;
expect(mb.multipart).to.be.false;
});
it('should use provided boundary', function () {
var mb = new Buildmail();
expect(mb.boundary).to.not.exist;
mb._handleContentType({
value: 'multipart/mixed',
params: {
boundary: 'abc'
}
});
expect(mb.boundary).to.equal('abc');
expect(mb.multipart).to.equal('mixed');
});
it('should generate boundary', function () {
var mb = new Buildmail();
sinon.stub(mb, '_generateBoundary').returns('def');
expect(mb.boundary).to.not.exist;
mb._handleContentType({
value: 'multipart/mixed',
params: {}
});
expect(mb.boundary).to.equal('def');
expect(mb.multipart).to.equal('mixed');
mb._generateBoundary.restore();
});
});
describe('#_generateBoundary ', function () {
it('should genereate boundary string', function () {
var mb = new Buildmail();
mb._nodeId = 'abc';
mb.rootNode.baseBoundary = 'def';
expect(mb._generateBoundary()).to.equal('----sinikael-?=_abc-def');
});
});
describe('#_encodeHeaderValue', function () {
it('should do noting if possible', function () {
var mb = new Buildmail();
expect(mb._encodeHeaderValue('x-my', 'test value')).to.equal('test value');
});
it('should encode non ascii characters', function () {
var mb = new Buildmail();
expect(mb._encodeHeaderValue('x-my', 'test jõgeva value')).to.equal('test =?UTF-8?Q?j=C3=B5geva?= value');
});
it('should format references', function () {
var mb = new Buildmail();
expect(mb._encodeHeaderValue('references', 'abc def')).to.equal('<abc> <def>');
expect(mb._encodeHeaderValue('references', ['abc', 'def'])).to.equal('<abc> <def>');
});
it('should format message-id', function () {
var mb = new Buildmail();
expect(mb._encodeHeaderValue('message-id', 'abc')).to.equal('<abc>');
});
it('should format addresses', function () {
var mb = new Buildmail();
expect(mb._encodeHeaderValue('from', {
name: 'the safewithme testuser',
address: 'safewithme.testuser@jõgeva.com'
})).to.equal('the safewithme testuser <safewithme.testuser@xn--jgeva-dua.com>');
});
});
describe('#_convertAddresses', function () {
it('should convert address object to a string', function () {
var mb = new Buildmail();
expect(mb._convertAddresses([{
name: 'Jõgeva Ants',
address: 'ants@jõgeva.ee'
}, {
name: 'Composers',
group: [{
address: 'sebu@example.com',
name: 'Bach, Sebastian'
}, {
address: 'mozart@example.com',
name: 'Mozzie'
}]
}])).to.equal('=?UTF-8?Q?J=C3=B5geva_Ants?= <ants@xn--jgeva-dua.ee>, Composers:"Bach, Sebastian" <sebu@example.com>, Mozzie <mozart@example.com>;');
});
it('should keep ascii name as is', function () {
var mb = new Buildmail();
expect(mb._convertAddresses([{
name: 'O\'Vigala Sass',
address: 'a@b.c'
}])).to.equal('O\'Vigala Sass <a@b.c>');
});
it('should include name in quotes for special symbols', function () {
var mb = new Buildmail();
expect(mb._convertAddresses([{
name: 'Sass, Vigala',
address: 'a@b.c'
}])).to.equal('"Sass, Vigala" <a@b.c>');
});
it('should escape quotes', function () {
var mb = new Buildmail();
expect(mb._convertAddresses([{
name: '"Vigala Sass"',
address: 'a@b.c'
}])).to.equal('"\\"Vigala Sass\\"" <a@b.c>');
});
it('should mime encode unicode names', function () {
var mb = new Buildmail();
expect(mb._convertAddresses([{
name: '"Jõgeva Sass"',
address: 'a@b.c'
}])).to.equal('=?UTF-8?Q?=22J=C3=B5geva_Sass=22?= <a@b.c>');
});
});
describe('#_generateMessageId', function(){
it('should generate uuid-looking message-id', function(){
var mb = new Buildmail();
var mid = mb._generateMessageId();
expect(/^<[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}@.*>/.test(mid)).to.be.true;
});
});
describe('Attachment streaming', function () {
var port = 10337;
var server;
beforeEach(function (done) {
server = http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
var data = new Buffer(new Array(1024 + 1).join('ä'), 'utf-8');
var i = 0;
var sendByte = function () {
if (i >= data.length) {
return res.end();
}
res.write(new Buffer([data[i++]]));
setImmediate(sendByte);
};
sendByte();
});
server.listen(port, done);
});
afterEach(function (done) {
server.close(done);
});
it('should pipe URL as an attachment', function (done) {
var mb = new Buildmail('text/plain').
setContent({
href: 'http://localhost:' + port
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^=C3=A4/m.test(msg)).to.be.true;
done();
});
});
it('should reject URL attachment', function (done) {
var mb = new Buildmail('text/plain', {disableUrlAccess: true}).
setContent({
href: 'http://localhost:' + port
});
mb.build(function (err, msg) {
expect(err).to.exist;
expect(msg).to.not.exist;
done();
});
});
it('should return an error on invalid url', function (done) {
var mb = new Buildmail('text/plain').
setContent({
href: 'http://__should_not_exist:58888'
});
mb.build(function (err) {
expect(err).to.exist;
done();
});
});
it('should pipe file as an attachment', function (done) {
var mb = new Buildmail('application/octet-stream').
setContent({
path: __dirname + '/fixtures/attachment.bin'
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(/^w7VrdmEK$/m.test(msg)).to.be.true;
done();
});
});
it('should reject file as an attachment', function (done) {
var mb = new Buildmail('application/octet-stream', {disableFileAccess: true}).
setContent({
path: __dirname + '/fixtures/attachment.bin'
});
mb.build(function (err, msg) {
expect(err).to.exist;
expect(msg).to.not.exist;
done();
});
});
it('should return an error on invalid file path', function (done) {
var mb = new Buildmail('text/plain').
setContent({
href: '/ASfsdfsdf/Sdgsgdfg/SDFgdfgdfg'
});
mb.build(function (err) {
expect(err).to.exist;
done();
});
});
it('should return a error for an errored stream', function (done) {
var s = new PassThrough();
var mb = new Buildmail('text/plain').
setContent(s);
s.write('abc');
s.emit('error', new Error('Stream error'));
setTimeout(function () {
mb.build(function (err) {
expect(err).to.exist;
done();
});
}, 100);
});
it('should return a stream error', function (done) {
var s = new PassThrough();
var mb = new Buildmail('text/plain').
setContent(s);
mb.build(function (err) {
expect(err).to.exist;
done();
});
s.write('abc');
setTimeout(function () {
s.emit('error', new Error('Stream error'));
}, 100);
});
});
describe('#transform', function () {
it('should pipe through provided stream', function (done) {
var mb = new Buildmail('text/plain').
setHeader({
date: '12345',
'message-id': '67890'
}).
setContent('Hello world!');
var expected = 'Content-Type:\ttext/plain\r\n' +
'Date:\t12345\r\n' +
'Message-ID:\t<67890>\r\n' +
'Content-Transfer-Encoding:\t7bit\r\n' +
'MIME-Version:\t1.0\r\n' +
'\r\n' +
'Hello\tworld!';
// Transform stream that replaces all spaces with tabs
var transform = new Transform();
transform._transform = function (chunk, encoding, done) {
if (encoding !== 'buffer') {
chunk = new Buffer(chunk, encoding);
}
for (var i = 0, len = chunk.length; i < len; i++) {
if (chunk[i] === 0x20) {
chunk[i] = 0x09;
}
}
this.push(chunk);
done();
};
mb.transform(transform);
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
});
describe('Raw content', function () {
it('should return pregenerated content', function (done) {
var expected = new Array(100).join('Test\n');
var mb = new Buildmail('text/plain').setRaw(expected);
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should return pregenerated content for a child node', function (done) {
var expected = new Array(100).join('Test\n');
var mb = new Buildmail('multipart/mixed', {
baseBoundary: 'test'
}).setHeader({
date: '12345',
'message-id': '67890'
});
var child = mb.createChild();
child.setRaw(expected);
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal('Content-Type: multipart/mixed; boundary="----sinikael-?=_1-test"\r\n' +
'Date: 12345\r\n' +
'Message-ID: <67890>\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'------sinikael-?=_1-test\r\n' +
expected +
'\r\n' +
'------sinikael-?=_1-test--\r\n');
done();
});
});
it('should return pregenerated content from a stream', function (done) {
var expected = new Array(100).join('Test\n');
var raw = new PassThrough();
var mb = new Buildmail('text/plain').setRaw(raw);
setImmediate(function () {
raw.end(expected);
});
mb.build(function (err, msg) {
expect(err).to.not.exist;
msg = msg.toString();
expect(msg).to.equal(expected);
done();
});
});
it('should catch error from a raw stream 1', function (done) {
var raw = new PassThrough();
var mb = new Buildmail('text/plain').setRaw(raw);
raw.emit('error', new Error('Stream error'));
mb.build(function (err) {
expect(err).to.exist;
done();
});
});
it('should catch error from a raw stream 2', function (done) {
var raw = new PassThrough();
var mb = new Buildmail('text/plain').setRaw(raw);
mb.build(function (err) {
expect(err).to.exist;
done();
});
setImmediate(function () {
raw.emit('error', new Error('Stream error'));
});
});
});
});