mirror of https://github.com/eleith/emailjs.git
integrate eslint & prettier
This commit is contained in:
parent
a81f056d1f
commit
dc12f85b16
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"mocha": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"mocha"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"no-case-declarations": "off",
|
||||
"no-console": "off",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none",
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
],
|
||||
"mocha/handle-done-callback": "error",
|
||||
"mocha/no-exclusive-tests": "error",
|
||||
"mocha/no-global-tests": "error",
|
||||
"mocha/no-mocha-arrows": "error",
|
||||
"mocha/no-skipped-tests": "error"
|
||||
}
|
||||
}
|
2
email.js
2
email.js
|
@ -1,4 +1,4 @@
|
|||
exports.server = require('./smtp/client');
|
||||
exports.message = require('./smtp/message');
|
||||
exports.SMTP = require('./smtp/smtp');
|
||||
exports.error= require('./smtp/error');
|
||||
exports.error = require('./smtp/error');
|
||||
|
|
12
package.json
12
package.json
|
@ -24,8 +24,13 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"chai": "1.1.0",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-mocha": "^5.0.0",
|
||||
"eslint-plugin-prettier": "^2.6.0",
|
||||
"mailparser": "2.2.0",
|
||||
"mocha": "5.0.0",
|
||||
"prettier": "^1.12.1",
|
||||
"smtp-server": "^3.4.1"
|
||||
},
|
||||
"engine": [
|
||||
|
@ -35,5 +40,10 @@
|
|||
"scripts": {
|
||||
"test": "mocha -R spec"
|
||||
},
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"useTabs": true
|
||||
}
|
||||
}
|
||||
|
|
277
smtp/client.js
277
smtp/client.js
|
@ -1,186 +1,185 @@
|
|||
const smtp = require('./smtp');
|
||||
const smtpError = require('./error');
|
||||
const message = require('./message');
|
||||
const addressparser = require('addressparser');
|
||||
|
||||
class Client {
|
||||
constructor(server) {
|
||||
this.smtp = new smtp.SMTP(server);
|
||||
//this.smtp.debug(1);
|
||||
constructor(server) {
|
||||
this.smtp = new smtp.SMTP(server);
|
||||
//this.smtp.debug(1);
|
||||
|
||||
this.queue = [];
|
||||
this.timer = null;
|
||||
this.sending = false;
|
||||
this.ready = false;
|
||||
this.queue = [];
|
||||
this.timer = null;
|
||||
this.sending = false;
|
||||
this.ready = false;
|
||||
}
|
||||
|
||||
_poll() {
|
||||
clearTimeout(this.timer);
|
||||
_poll() {
|
||||
clearTimeout(this.timer);
|
||||
|
||||
if (this.queue.length) {
|
||||
if (this.smtp.state() == smtp.state.NOTCONNECTED) {
|
||||
if (this.queue.length) {
|
||||
if (this.smtp.state() == smtp.state.NOTCONNECTED) {
|
||||
this._connect(this.queue[0]);
|
||||
} else if (
|
||||
this.smtp.state() == smtp.state.CONNECTED &&
|
||||
!this.sending &&
|
||||
this.ready
|
||||
) {
|
||||
this._sendmail(this.queue.shift());
|
||||
this.smtp.state() == smtp.state.CONNECTED &&
|
||||
!this.sending &&
|
||||
this.ready
|
||||
) {
|
||||
this._sendmail(this.queue.shift());
|
||||
}
|
||||
}
|
||||
// wait around 1 seconds in case something does come in, otherwise close out SMTP connection if still open
|
||||
else if (this.smtp.state() == smtp.state.CONNECTED)
|
||||
this.timer = setTimeout(() => this.smtp.quit(), 1000);
|
||||
}
|
||||
}
|
||||
// wait around 1 seconds in case something does come in, otherwise close out SMTP connection if still open
|
||||
else if (this.smtp.state() == smtp.state.CONNECTED)
|
||||
this.timer = setTimeout(() => this.smtp.quit(), 1000);
|
||||
}
|
||||
|
||||
_connect(stack) {
|
||||
const connect = err => {
|
||||
if (!err) {
|
||||
const begin = err => {
|
||||
if (!err) {
|
||||
this.ready = true;
|
||||
this._poll();
|
||||
} else {
|
||||
stack.callback(err, stack.message);
|
||||
_connect(stack) {
|
||||
const connect = err => {
|
||||
if (!err) {
|
||||
const begin = err => {
|
||||
if (!err) {
|
||||
this.ready = true;
|
||||
this._poll();
|
||||
} else {
|
||||
stack.callback(err, stack.message);
|
||||
|
||||
// clear out the queue so all callbacks can be called with the same error message
|
||||
this.queue.shift();
|
||||
this._poll();
|
||||
}
|
||||
};
|
||||
// clear out the queue so all callbacks can be called with the same error message
|
||||
this.queue.shift();
|
||||
this._poll();
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.smtp.authorized()) this.smtp.login(begin);
|
||||
else this.smtp.ehlo_or_helo_if_needed(begin);
|
||||
} else {
|
||||
stack.callback(err, stack.message);
|
||||
if (!this.smtp.authorized()) this.smtp.login(begin);
|
||||
else this.smtp.ehlo_or_helo_if_needed(begin);
|
||||
} else {
|
||||
stack.callback(err, stack.message);
|
||||
|
||||
// clear out the queue so all callbacks can be called with the same error message
|
||||
this.queue.shift();
|
||||
this._poll();
|
||||
}
|
||||
};
|
||||
// clear out the queue so all callbacks can be called with the same error message
|
||||
this.queue.shift();
|
||||
this._poll();
|
||||
}
|
||||
};
|
||||
|
||||
this.ready = false;
|
||||
this.smtp.connect(connect);
|
||||
}
|
||||
this.ready = false;
|
||||
this.smtp.connect(connect);
|
||||
}
|
||||
|
||||
send(msg, callback) {
|
||||
if (
|
||||
!(msg instanceof message.Message) &&
|
||||
msg.from &&
|
||||
(msg.to || msg.cc || msg.bcc) &&
|
||||
(msg.text !== undefined || this._containsInlinedHtml(msg.attachment))
|
||||
)
|
||||
msg = message.create(msg);
|
||||
send(msg, callback) {
|
||||
if (
|
||||
!(msg instanceof message.Message) &&
|
||||
msg.from &&
|
||||
(msg.to || msg.cc || msg.bcc) &&
|
||||
(msg.text !== undefined || this._containsInlinedHtml(msg.attachment))
|
||||
)
|
||||
msg = message.create(msg);
|
||||
|
||||
if (msg instanceof message.Message) {
|
||||
msg.valid((valid, why) => {
|
||||
if (valid) {
|
||||
const stack = {
|
||||
message: msg,
|
||||
to: addressparser(msg.header.to),
|
||||
from: addressparser(msg.header.from)[0].address,
|
||||
callback: (callback || function() {}).bind(this),
|
||||
};
|
||||
if (msg instanceof message.Message) {
|
||||
msg.valid((valid, why) => {
|
||||
if (valid) {
|
||||
const stack = {
|
||||
message: msg,
|
||||
to: addressparser(msg.header.to),
|
||||
from: addressparser(msg.header.from)[0].address,
|
||||
callback: (callback || function() {}).bind(this),
|
||||
};
|
||||
|
||||
if (msg.header.cc)
|
||||
stack.to = stack.to.concat(addressparser(msg.header.cc));
|
||||
if (msg.header.cc)
|
||||
stack.to = stack.to.concat(addressparser(msg.header.cc));
|
||||
|
||||
if (msg.header.bcc)
|
||||
stack.to = stack.to.concat(addressparser(msg.header.bcc));
|
||||
if (msg.header.bcc)
|
||||
stack.to = stack.to.concat(addressparser(msg.header.bcc));
|
||||
|
||||
if (
|
||||
msg.header['return-path'] &&
|
||||
addressparser(msg.header['return-path']).length
|
||||
)
|
||||
stack.returnPath = addressparser(
|
||||
msg.header['return-path']
|
||||
)[0].address;
|
||||
if (
|
||||
msg.header['return-path'] &&
|
||||
addressparser(msg.header['return-path']).length
|
||||
)
|
||||
stack.returnPath = addressparser(
|
||||
msg.header['return-path']
|
||||
)[0].address;
|
||||
|
||||
this.queue.push(stack);
|
||||
this._poll();
|
||||
} else {
|
||||
this.queue.push(stack);
|
||||
this._poll();
|
||||
} else {
|
||||
callback(new Error(why), msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
});
|
||||
} else {
|
||||
callback(new Error('message is not a valid Message instance'), msg);
|
||||
}
|
||||
}
|
||||
|
||||
_containsInlinedHtml(attachment) {
|
||||
if (Array.isArray(attachment)) {
|
||||
return attachment.some(() => {
|
||||
return att => {
|
||||
return this._isAttachmentInlinedHtml(att);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return this._isAttachmentInlinedHtml(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
_isAttachmentInlinedHtml(attachment) {
|
||||
return (
|
||||
attachment &&
|
||||
(attachment.data || attachment.path) &&
|
||||
attachment.alternative === true
|
||||
);
|
||||
}
|
||||
|
||||
_sendsmtp(stack, next) {
|
||||
return err => {
|
||||
if (!err && next) {
|
||||
next.apply(this, [stack]);
|
||||
} else {
|
||||
// if we snag on SMTP commands, call done, passing the error
|
||||
// but first reset SMTP state so queue can continue polling
|
||||
this.smtp.rset(() => this._senddone(err, stack));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_containsInlinedHtml(attachment) {
|
||||
if (Array.isArray(attachment)) {
|
||||
return attachment.some(() => {
|
||||
return att => {
|
||||
return this._isAttachmentInlinedHtml(att);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return this._isAttachmentInlinedHtml(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
_isAttachmentInlinedHtml(attachment) {
|
||||
return (
|
||||
attachment &&
|
||||
(attachment.data || attachment.path) &&
|
||||
attachment.alternative === true
|
||||
);
|
||||
}
|
||||
|
||||
_sendsmtp(stack, next) {
|
||||
return err => {
|
||||
if (!err && next) {
|
||||
next.apply(this, [stack]);
|
||||
} else {
|
||||
// if we snag on SMTP commands, call done, passing the error
|
||||
// but first reset SMTP state so queue can continue polling
|
||||
this.smtp.rset(() => this._senddone(err, stack));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_sendmail(stack) {
|
||||
const from = stack.returnPath || stack.from;
|
||||
this.sending = true;
|
||||
this.smtp.mail(this._sendsmtp(stack, this._sendrcpt), '<' + from + '>');
|
||||
const from = stack.returnPath || stack.from;
|
||||
this.sending = true;
|
||||
this.smtp.mail(this._sendsmtp(stack, this._sendrcpt), '<' + from + '>');
|
||||
}
|
||||
|
||||
_sendrcpt(stack) {
|
||||
const to = stack.to.shift().address;
|
||||
this.smtp.rcpt(
|
||||
this._sendsmtp(stack, stack.to.length ? this._sendrcpt : this._senddata),
|
||||
'<' + to + '>'
|
||||
);
|
||||
const to = stack.to.shift().address;
|
||||
this.smtp.rcpt(
|
||||
this._sendsmtp(stack, stack.to.length ? this._sendrcpt : this._senddata),
|
||||
'<' + to + '>'
|
||||
);
|
||||
}
|
||||
|
||||
_senddata(stack) {
|
||||
this.smtp.data(this._sendsmtp(stack, this._sendmessage));
|
||||
}
|
||||
this.smtp.data(this._sendsmtp(stack, this._sendmessage));
|
||||
}
|
||||
|
||||
_sendmessage(stack) {
|
||||
const stream = stack.message.stream();
|
||||
_sendmessage(stack) {
|
||||
const stream = stack.message.stream();
|
||||
|
||||
stream.on('data', (data) => this.smtp.message(data));
|
||||
stream.on('end', () => {
|
||||
this.smtp.data_end(
|
||||
this._sendsmtp(stack, () => this._senddone(null, stack))
|
||||
);
|
||||
});
|
||||
stream.on('data', data => this.smtp.message(data));
|
||||
stream.on('end', () => {
|
||||
this.smtp.data_end(
|
||||
this._sendsmtp(stack, () => this._senddone(null, stack))
|
||||
);
|
||||
});
|
||||
|
||||
// there is no way to cancel a message while in the DATA portion,
|
||||
// so we have to close the socket to prevent a bad email from going out
|
||||
stream.on('error', (err) => {
|
||||
this.smtp.close();
|
||||
this._senddone(err, stack);
|
||||
});
|
||||
stream.on('error', err => {
|
||||
this.smtp.close();
|
||||
this._senddone(err, stack);
|
||||
});
|
||||
}
|
||||
|
||||
_senddone(err, stack) {
|
||||
this.sending = false;
|
||||
stack.callback(err, stack.message);
|
||||
this._poll();
|
||||
}
|
||||
this.sending = false;
|
||||
stack.callback(err, stack.message);
|
||||
this._poll();
|
||||
}
|
||||
}
|
||||
|
||||
exports.Client = Client;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
module.exports = function(message, code, error, smtp) {
|
||||
const err = new Error(
|
||||
error && error.message ? `${message} (${error.message})` : message
|
||||
);
|
||||
const err = new Error(
|
||||
error && error.message ? `${message} (${error.message})` : message
|
||||
);
|
||||
|
||||
err.code = code;
|
||||
err.smtp = smtp;
|
||||
err.smtp = smtp;
|
||||
|
||||
if (error) {
|
||||
err.previous = error;
|
||||
}
|
||||
|
||||
return err;
|
||||
return err;
|
||||
};
|
||||
|
||||
module.exports.COULDNOTCONNECT = 1;
|
||||
|
|
812
smtp/message.js
812
smtp/message.js
|
@ -7,7 +7,6 @@ const mimeWordEncode = require('emailjs-mime-codec').mimeWordEncode;
|
|||
const addressparser = require('addressparser');
|
||||
const CRLF = '\r\n';
|
||||
const MIMECHUNK = 76; // MIME standard wants 76 char chunks when sending out.
|
||||
const BASE64CHUNK = 24; // BASE64 bits needed before padding is used
|
||||
const MIME64CHUNK = MIMECHUNK * 6; // meets both base64 and mime divisibility
|
||||
const BUFFERSIZE = MIMECHUNK * 24 * 7; // size of the message stream buffer
|
||||
|
||||
|
@ -15,497 +14,528 @@ let counter = 0;
|
|||
|
||||
// support for nodejs without Buffer.concat native function
|
||||
if (!Buffer.concat) {
|
||||
require('bufferjs/concat');
|
||||
require('bufferjs/concat');
|
||||
}
|
||||
|
||||
function generate_boundary() {
|
||||
let text = '';
|
||||
const possible = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+_,-./:=?`;
|
||||
let text = '';
|
||||
const possible =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+_,-./:=?";
|
||||
|
||||
for (let i = 0; i < 69; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
for (let i = 0; i < 69; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
|
||||
return text;
|
||||
return text;
|
||||
}
|
||||
|
||||
function person2address(l) {
|
||||
return addressparser(l)
|
||||
.map(({ name, address }) => {
|
||||
return name
|
||||
? `${mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>`
|
||||
: address;
|
||||
})
|
||||
.join(', ');
|
||||
return addressparser(l)
|
||||
.map(({ name, address }) => {
|
||||
return name
|
||||
? `${mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>`
|
||||
: address;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
function fix_header_name_case(header_name) {
|
||||
return header_name
|
||||
.toLowerCase()
|
||||
.replace(/^(.)|-(.)/g, match => match.toUpperCase());
|
||||
return header_name
|
||||
.toLowerCase()
|
||||
.replace(/^(.)|-(.)/g, match => match.toUpperCase());
|
||||
}
|
||||
|
||||
class Message {
|
||||
constructor(headers) {
|
||||
this.attachments = [];
|
||||
this.alternative = null;
|
||||
this.header = {
|
||||
'message-id': `<${new Date().getTime()}.${counter++}.${process.pid}@${os.hostname()}>`,
|
||||
date: moment()
|
||||
.locale('en')
|
||||
.format('ddd, DD MMM YYYY HH:mm:ss ZZ')
|
||||
constructor(headers) {
|
||||
this.attachments = [];
|
||||
this.alternative = null;
|
||||
this.header = {
|
||||
'message-id': `<${new Date().getTime()}.${counter++}.${
|
||||
process.pid
|
||||
}@${os.hostname()}>`,
|
||||
date: moment()
|
||||
.locale('en')
|
||||
.format('ddd, DD MMM YYYY HH:mm:ss ZZ'),
|
||||
};
|
||||
|
||||
this.content = 'text/plain; charset=utf-8';
|
||||
for (let header in headers) {
|
||||
// allow user to override default content-type to override charset or send a single non-text message
|
||||
if (/^content-type$/i.test(header)) {
|
||||
this.content = headers[header];
|
||||
} else if (header === 'text') {
|
||||
this.text = headers[header];
|
||||
} else if (header === 'attachment' && typeof headers[header] === 'object') {
|
||||
if (Array.isArray(headers[header])) {
|
||||
for (let i = 0, l = headers[header].length; i < l; i++) {
|
||||
this.attach(headers[header][i]);
|
||||
}
|
||||
} else {
|
||||
this.attach(headers[header]);
|
||||
}
|
||||
} else if (header === 'subject') {
|
||||
this.header.subject = mimeWordEncode(headers.subject);
|
||||
} else if (/^(cc|bcc|to|from)/i.test(header)) {
|
||||
this.header[header.toLowerCase()] = person2address(headers[header]);
|
||||
} else {
|
||||
// allow any headers the user wants to set??
|
||||
// if(/cc|bcc|to|from|reply-to|sender|subject|date|message-id/i.test(header))
|
||||
this.header[header.toLowerCase()] = headers[header];
|
||||
}
|
||||
this.content = 'text/plain; charset=utf-8';
|
||||
for (let header in headers) {
|
||||
// allow user to override default content-type to override charset or send a single non-text message
|
||||
if (/^content-type$/i.test(header)) {
|
||||
this.content = headers[header];
|
||||
} else if (header === 'text') {
|
||||
this.text = headers[header];
|
||||
} else if (
|
||||
header === 'attachment' &&
|
||||
typeof headers[header] === 'object'
|
||||
) {
|
||||
if (Array.isArray(headers[header])) {
|
||||
for (let i = 0, l = headers[header].length; i < l; i++) {
|
||||
this.attach(headers[header][i]);
|
||||
}
|
||||
} else {
|
||||
this.attach(headers[header]);
|
||||
}
|
||||
} else if (header === 'subject') {
|
||||
this.header.subject = mimeWordEncode(headers.subject);
|
||||
} else if (/^(cc|bcc|to|from)/i.test(header)) {
|
||||
this.header[header.toLowerCase()] = person2address(headers[header]);
|
||||
} else {
|
||||
// allow any headers the user wants to set??
|
||||
// if(/cc|bcc|to|from|reply-to|sender|subject|date|message-id/i.test(header))
|
||||
this.header[header.toLowerCase()] = headers[header];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attach(options) {
|
||||
/*
|
||||
/*
|
||||
legacy support, will remove eventually...
|
||||
arguments -> (path, type, name, headers)
|
||||
*/
|
||||
if (arguments.length > 1) {
|
||||
options = { path: options, type: arguments[1], name: arguments[2] };
|
||||
if (arguments.length > 1) {
|
||||
options = { path: options, type: arguments[1], name: arguments[2] };
|
||||
}
|
||||
|
||||
// sender can specify an attachment as an alternative
|
||||
if (options.alternative) {
|
||||
this.alternative = options;
|
||||
this.alternative.charset = options.charset || 'utf-8';
|
||||
this.alternative.type = options.type || 'text/html';
|
||||
this.alternative.inline = true;
|
||||
} else {
|
||||
// sender can specify an attachment as an alternative
|
||||
if (options.alternative) {
|
||||
this.alternative = options;
|
||||
this.alternative.charset = options.charset || 'utf-8';
|
||||
this.alternative.type = options.type || 'text/html';
|
||||
this.alternative.inline = true;
|
||||
} else {
|
||||
this.attachments.push(options);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
legacy support, will remove eventually...
|
||||
should use Message.attach() instead
|
||||
*/
|
||||
attach_alternative(html, charset) {
|
||||
this.alternative = {
|
||||
data: html,
|
||||
charset: charset || 'utf-8',
|
||||
type: 'text/html',
|
||||
inline: true
|
||||
};
|
||||
attach_alternative(html, charset) {
|
||||
this.alternative = {
|
||||
data: html,
|
||||
charset: charset || 'utf-8',
|
||||
type: 'text/html',
|
||||
inline: true,
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
valid(callback) {
|
||||
if (!this.header.from) {
|
||||
callback(false, 'message does not have a valid sender');
|
||||
valid(callback) {
|
||||
if (!this.header.from) {
|
||||
callback(false, 'message does not have a valid sender');
|
||||
}
|
||||
|
||||
if (!(this.header.to || this.header.cc || this.header.bcc)) {
|
||||
callback(false, 'message does not have a valid recipient');
|
||||
} else if (this.attachments.length === 0) {
|
||||
callback(true);
|
||||
} else {
|
||||
const failed = [];
|
||||
if (!(this.header.to || this.header.cc || this.header.bcc)) {
|
||||
callback(false, 'message does not have a valid recipient');
|
||||
} else if (this.attachments.length === 0) {
|
||||
callback(true);
|
||||
} else {
|
||||
const failed = [];
|
||||
|
||||
this.attachments.forEach((attachment, index) => {
|
||||
if (attachment.path) {
|
||||
// migrating path->fs for existsSync)
|
||||
if (!(fs.existsSync || path.existsSync)(attachment.path))
|
||||
failed.push(`${attachment.path} does not exist`);
|
||||
} else if (attachment.stream) {
|
||||
if (!attachment.stream.readable)
|
||||
failed.push('attachment stream is not readable');
|
||||
} else if (!attachment.data) {
|
||||
failed.push('attachment has no data associated with it');
|
||||
}
|
||||
});
|
||||
this.attachments.forEach(attachment => {
|
||||
if (attachment.path) {
|
||||
// migrating path->fs for existsSync)
|
||||
if (!(fs.existsSync || path.existsSync)(attachment.path))
|
||||
failed.push(`${attachment.path} does not exist`);
|
||||
} else if (attachment.stream) {
|
||||
if (!attachment.stream.readable)
|
||||
failed.push('attachment stream is not readable');
|
||||
} else if (!attachment.data) {
|
||||
failed.push('attachment has no data associated with it');
|
||||
}
|
||||
});
|
||||
|
||||
callback(failed.length === 0, failed.join(', '));
|
||||
}
|
||||
}
|
||||
callback(failed.length === 0, failed.join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
stream() {
|
||||
return new MessageStream(this);
|
||||
}
|
||||
stream() {
|
||||
return new MessageStream(this);
|
||||
}
|
||||
|
||||
read(callback) {
|
||||
let buffer = '';
|
||||
const str = this.stream();
|
||||
str.on('data', (data) => buffer += data);
|
||||
str.on('end', (err) => callback(err, buffer));
|
||||
str.on('error', (err) => callback(err, buffer));
|
||||
}
|
||||
read(callback) {
|
||||
let buffer = '';
|
||||
const str = this.stream();
|
||||
str.on('data', data => (buffer += data));
|
||||
str.on('end', err => callback(err, buffer));
|
||||
str.on('error', err => callback(err, buffer));
|
||||
}
|
||||
}
|
||||
|
||||
class MessageStream extends Stream {
|
||||
constructor(message) {
|
||||
constructor(message) {
|
||||
super();
|
||||
|
||||
this.message = message;
|
||||
this.readable = true;
|
||||
this.paused = false;
|
||||
this.buffer = Buffer.alloc(MIMECHUNK * 24 * 7);
|
||||
this.bufferIndex = 0;
|
||||
this.message = message;
|
||||
this.readable = true;
|
||||
this.paused = false;
|
||||
this.buffer = Buffer.alloc(MIMECHUNK * 24 * 7);
|
||||
this.bufferIndex = 0;
|
||||
|
||||
const output_mixed = () => {
|
||||
const boundary = generate_boundary();
|
||||
output(
|
||||
`Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
);
|
||||
const output_mixed = () => {
|
||||
const boundary = generate_boundary();
|
||||
output(
|
||||
`Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
);
|
||||
|
||||
if (!this.message.alternative) {
|
||||
output_text(this.message);
|
||||
output_message(boundary, this.message.attachments, 0, close);
|
||||
} else {
|
||||
const cb = () => output_message(boundary, this.message.attachments, 0, close);
|
||||
output_alternative(this.message, cb);
|
||||
}
|
||||
};
|
||||
|
||||
const output_message = (boundary, list, index, callback) => {
|
||||
if (index < list.length) {
|
||||
output(`--${boundary}${CRLF}`);
|
||||
if (list[index].related) {
|
||||
output_related(list[index], () =>
|
||||
output_message(boundary, list, index + 1, callback)
|
||||
);
|
||||
} else {
|
||||
output_attachment(list[index], () =>
|
||||
output_message(boundary, list, index + 1, callback)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const output_attachment_headers = attachment => {
|
||||
let data = [];
|
||||
const headers = {
|
||||
'content-type':
|
||||
attachment.type +
|
||||
(attachment.charset ? `; charset=${attachment.charset}` : '') +
|
||||
(attachment.method ? `; method=${attachment.method}` : ''),
|
||||
'content-transfer-encoding': 'base64',
|
||||
'content-disposition': attachment.inline
|
||||
? 'inline'
|
||||
: `attachment; filename="${mimeWordEncode(attachment.name)}"`
|
||||
};
|
||||
|
||||
for (let header in attachment.headers || {}) {
|
||||
// allow sender to override default headers
|
||||
headers[header.toLowerCase()] = attachment.headers[header];
|
||||
}
|
||||
|
||||
for (let header in headers) {
|
||||
data = data.concat([
|
||||
fix_header_name_case(header),
|
||||
': ',
|
||||
headers[header],
|
||||
CRLF
|
||||
]);
|
||||
}
|
||||
|
||||
output(data.concat([CRLF]).join(''));
|
||||
};
|
||||
|
||||
const output_attachment = (attachment, callback) => {
|
||||
const build = attachment.path
|
||||
? output_file
|
||||
: attachment.stream
|
||||
? output_stream
|
||||
: output_data;
|
||||
output_attachment_headers(attachment);
|
||||
build(attachment, callback);
|
||||
if (!this.message.alternative) {
|
||||
output_text(this.message);
|
||||
output_message(boundary, this.message.attachments, 0, close);
|
||||
} else {
|
||||
const cb = () =>
|
||||
output_message(boundary, this.message.attachments, 0, close);
|
||||
output_alternative(this.message, cb);
|
||||
}
|
||||
};
|
||||
|
||||
const output_data = (attachment, callback) => {
|
||||
output_base64(
|
||||
attachment.encoded
|
||||
? attachment.data
|
||||
: Buffer.from(attachment.data).toString('base64'),
|
||||
callback
|
||||
);
|
||||
const output_message = (boundary, list, index, callback) => {
|
||||
if (index < list.length) {
|
||||
output(`--${boundary}${CRLF}`);
|
||||
if (list[index].related) {
|
||||
output_related(list[index], () =>
|
||||
output_message(boundary, list, index + 1, callback)
|
||||
);
|
||||
} else {
|
||||
output_attachment(list[index], () =>
|
||||
output_message(boundary, list, index + 1, callback)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const output_file = (attachment, next) => {
|
||||
const chunk = MIME64CHUNK * 16;
|
||||
const buffer = Buffer.alloc(chunk);
|
||||
const closed = (fd) => {
|
||||
if (fs.closeSync) {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
const output_attachment_headers = attachment => {
|
||||
let data = [];
|
||||
const headers = {
|
||||
'content-type':
|
||||
attachment.type +
|
||||
(attachment.charset ? `; charset=${attachment.charset}` : '') +
|
||||
(attachment.method ? `; method=${attachment.method}` : ''),
|
||||
'content-transfer-encoding': 'base64',
|
||||
'content-disposition': attachment.inline
|
||||
? 'inline'
|
||||
: `attachment; filename="${mimeWordEncode(attachment.name)}"`,
|
||||
};
|
||||
|
||||
const opened = (err, fd) => {
|
||||
if (!err) {
|
||||
const read = (err, bytes) => {
|
||||
if (!err && this.readable) {
|
||||
let encoding =
|
||||
attachment && attachment.headers
|
||||
? attachment.headers['content-transfer-encoding'] || 'base64'
|
||||
: 'base64';
|
||||
if (encoding === 'ascii' || encoding === '7bit') {
|
||||
encoding = 'ascii';
|
||||
} else if (encoding === 'binary' || encoding === '8bit') {
|
||||
encoding = 'binary';
|
||||
} else {
|
||||
encoding = 'base64';
|
||||
}
|
||||
// guaranteed to be encoded without padding unless it is our last read
|
||||
output_base64(buffer.toString(encoding, 0, bytes), () => {
|
||||
if (bytes == chunk) {
|
||||
// we read a full chunk, there might be more
|
||||
fs.read(fd, buffer, 0, chunk, null, read);
|
||||
} // that was the last chunk, we are done reading the file
|
||||
else {
|
||||
this.removeListener('error', closed);
|
||||
fs.close(fd, next);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.emit('error', err || { message: 'message stream was interrupted somehow!' });
|
||||
}
|
||||
for (let header in attachment.headers || {}) {
|
||||
// allow sender to override default headers
|
||||
headers[header.toLowerCase()] = attachment.headers[header];
|
||||
}
|
||||
|
||||
for (let header in headers) {
|
||||
data = data.concat([
|
||||
fix_header_name_case(header),
|
||||
': ',
|
||||
headers[header],
|
||||
CRLF,
|
||||
]);
|
||||
}
|
||||
|
||||
output(data.concat([CRLF]).join(''));
|
||||
};
|
||||
|
||||
const output_attachment = (attachment, callback) => {
|
||||
const build = attachment.path
|
||||
? output_file
|
||||
: attachment.stream
|
||||
? output_stream
|
||||
: output_data;
|
||||
output_attachment_headers(attachment);
|
||||
build(attachment, callback);
|
||||
};
|
||||
|
||||
const output_data = (attachment, callback) => {
|
||||
output_base64(
|
||||
attachment.encoded
|
||||
? attachment.data
|
||||
: Buffer.from(attachment.data).toString('base64'),
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
const output_file = (attachment, next) => {
|
||||
const chunk = MIME64CHUNK * 16;
|
||||
const buffer = Buffer.alloc(chunk);
|
||||
const closed = fd => {
|
||||
if (fs.closeSync) {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
};
|
||||
|
||||
const opened = (err, fd) => {
|
||||
if (!err) {
|
||||
const read = (err, bytes) => {
|
||||
if (!err && this.readable) {
|
||||
let encoding =
|
||||
attachment && attachment.headers
|
||||
? attachment.headers['content-transfer-encoding'] || 'base64'
|
||||
: 'base64';
|
||||
if (encoding === 'ascii' || encoding === '7bit') {
|
||||
encoding = 'ascii';
|
||||
} else if (encoding === 'binary' || encoding === '8bit') {
|
||||
encoding = 'binary';
|
||||
} else {
|
||||
encoding = 'base64';
|
||||
}
|
||||
// guaranteed to be encoded without padding unless it is our last read
|
||||
output_base64(buffer.toString(encoding, 0, bytes), () => {
|
||||
if (bytes == chunk) {
|
||||
// we read a full chunk, there might be more
|
||||
fs.read(fd, buffer, 0, chunk, null, read);
|
||||
} // that was the last chunk, we are done reading the file
|
||||
else {
|
||||
this.removeListener('error', closed);
|
||||
fs.close(fd, next);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.emit(
|
||||
'error',
|
||||
err || { message: 'message stream was interrupted somehow!' }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
fs.read(fd, buffer, 0, chunk, null, read);
|
||||
this.once('error', closed);
|
||||
} else {
|
||||
fs.read(fd, buffer, 0, chunk, null, read);
|
||||
this.once('error', closed);
|
||||
} else {
|
||||
this.emit('error', err);
|
||||
}
|
||||
};
|
||||
|
||||
fs.open(attachment.path, 'r', opened);
|
||||
fs.open(attachment.path, 'r', opened);
|
||||
};
|
||||
|
||||
const output_stream = (attachment, callback) => {
|
||||
if (attachment.stream.readable) {
|
||||
const output_stream = (attachment, callback) => {
|
||||
if (attachment.stream.readable) {
|
||||
let previous = null;
|
||||
|
||||
attachment.stream.resume();
|
||||
attachment.stream.resume();
|
||||
|
||||
attachment.stream.on('end', () => {
|
||||
output_base64(
|
||||
(previous || Buffer.from(0)).toString('base64'),
|
||||
callback
|
||||
);
|
||||
this.removeListener('pause', attachment.stream.pause);
|
||||
this.removeListener('resume', attachment.stream.resume);
|
||||
this.removeListener('error', attachment.stream.resume);
|
||||
output_base64(
|
||||
(previous || Buffer.from(0)).toString('base64'),
|
||||
callback
|
||||
);
|
||||
this.removeListener('pause', attachment.stream.pause);
|
||||
this.removeListener('resume', attachment.stream.resume);
|
||||
this.removeListener('error', attachment.stream.resume);
|
||||
});
|
||||
|
||||
attachment.stream.on('data', (buffer) => {
|
||||
// do we have bytes from a previous stream data event?
|
||||
if (previous) {
|
||||
const buffer2 = Buffer.concat([previous, buffer]);
|
||||
previous = null; // free up the buffer
|
||||
buffer = null; // free up the buffer
|
||||
buffer = buffer2;
|
||||
attachment.stream.on('data', buffer => {
|
||||
// do we have bytes from a previous stream data event?
|
||||
if (previous) {
|
||||
const buffer2 = Buffer.concat([previous, buffer]);
|
||||
previous = null; // free up the buffer
|
||||
buffer = null; // free up the buffer
|
||||
buffer = buffer2;
|
||||
}
|
||||
|
||||
const padded = buffer.length % MIME64CHUNK;
|
||||
// encode as much of the buffer to base64 without empty bytes
|
||||
if (padded) {
|
||||
previous = Buffer.alloc(padded);
|
||||
// copy dangling bytes into previous buffer
|
||||
buffer.copy(previous, 0, buffer.length - padded);
|
||||
}
|
||||
output_base64(buffer.toString('base64', 0, buffer.length - padded));
|
||||
const padded = buffer.length % MIME64CHUNK;
|
||||
// encode as much of the buffer to base64 without empty bytes
|
||||
if (padded) {
|
||||
previous = Buffer.alloc(padded);
|
||||
// copy dangling bytes into previous buffer
|
||||
buffer.copy(previous, 0, buffer.length - padded);
|
||||
}
|
||||
output_base64(buffer.toString('base64', 0, buffer.length - padded));
|
||||
});
|
||||
|
||||
this.on('pause', attachment.stream.pause);
|
||||
this.on('resume', attachment.stream.resume);
|
||||
this.on('error', attachment.stream.resume);
|
||||
} else {
|
||||
this.on('pause', attachment.stream.pause);
|
||||
this.on('resume', attachment.stream.resume);
|
||||
this.on('error', attachment.stream.resume);
|
||||
} else {
|
||||
this.emit('error', { message: 'stream not readable' });
|
||||
}
|
||||
};
|
||||
|
||||
const output_base64 = (data, callback) => {
|
||||
const loops = Math.ceil(data.length / MIMECHUNK);
|
||||
let loop = 0;
|
||||
while (loop < loops) {
|
||||
output(data.substring(MIMECHUNK * loop, MIMECHUNK * (loop + 1)) + CRLF);
|
||||
loop++;
|
||||
}
|
||||
if (callback) callback();
|
||||
const output_base64 = (data, callback) => {
|
||||
const loops = Math.ceil(data.length / MIMECHUNK);
|
||||
let loop = 0;
|
||||
while (loop < loops) {
|
||||
output(data.substring(MIMECHUNK * loop, MIMECHUNK * (loop + 1)) + CRLF);
|
||||
loop++;
|
||||
}
|
||||
if (callback) callback();
|
||||
};
|
||||
|
||||
const output_text = (message) => {
|
||||
let data = [];
|
||||
|
||||
data = data.concat(['Content-Type:', message.content, CRLF, 'Content-Transfer-Encoding: 7bit', CRLF]);
|
||||
data = data.concat(['Content-Disposition: inline', CRLF, CRLF]);
|
||||
data = data.concat([message.text || '', CRLF, CRLF]);
|
||||
|
||||
output(data.join(''));
|
||||
};
|
||||
|
||||
const output_alternative = (message, callback) => {
|
||||
const boundary = generate_boundary();
|
||||
output(`Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`);
|
||||
output_text(message);
|
||||
output(`--${boundary}${CRLF}`);
|
||||
|
||||
const finish = () => {
|
||||
output([CRLF, '--', boundary, '--', CRLF, CRLF].join(''));
|
||||
callback();
|
||||
};
|
||||
|
||||
if (message.alternative.related) {
|
||||
output_related(message.alternative, finish);
|
||||
} else {
|
||||
output_attachment(message.alternative, finish);
|
||||
}
|
||||
};
|
||||
|
||||
const output_related = (message, callback) => {
|
||||
const boundary = generate_boundary();
|
||||
output(`Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`);
|
||||
output_attachment(message, () => {
|
||||
output_message(boundary, message.related, 0, () => {
|
||||
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const output_header_data = () => {
|
||||
if (this.message.attachments.length || this.message.alternative) {
|
||||
output(`MIME-Version: 1.0${CRLF}`);
|
||||
output_mixed();
|
||||
} // you only have a text message!
|
||||
else {
|
||||
output_text(this.message);
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const output_header = () => {
|
||||
const output_text = message => {
|
||||
let data = [];
|
||||
|
||||
for (let header in this.message.header) {
|
||||
// do not output BCC in the headers (regex) nor custom Object.prototype functions...
|
||||
if (!/bcc/i.test(header) && this.message.header.hasOwnProperty(header)) {
|
||||
data = data.concat([fix_header_name_case(header), ': ', this.message.header[header], CRLF]);
|
||||
data = data.concat([
|
||||
'Content-Type:',
|
||||
message.content,
|
||||
CRLF,
|
||||
'Content-Transfer-Encoding: 7bit',
|
||||
CRLF,
|
||||
]);
|
||||
data = data.concat(['Content-Disposition: inline', CRLF, CRLF]);
|
||||
data = data.concat([message.text || '', CRLF, CRLF]);
|
||||
|
||||
output(data.join(''));
|
||||
};
|
||||
|
||||
const output_alternative = (message, callback) => {
|
||||
const boundary = generate_boundary();
|
||||
output(
|
||||
`Content-Type: multipart/alternative; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
);
|
||||
output_text(message);
|
||||
output(`--${boundary}${CRLF}`);
|
||||
|
||||
const finish = () => {
|
||||
output([CRLF, '--', boundary, '--', CRLF, CRLF].join(''));
|
||||
callback();
|
||||
};
|
||||
|
||||
if (message.alternative.related) {
|
||||
output_related(message.alternative, finish);
|
||||
} else {
|
||||
output_attachment(message.alternative, finish);
|
||||
}
|
||||
};
|
||||
|
||||
const output_related = (message, callback) => {
|
||||
const boundary = generate_boundary();
|
||||
output(
|
||||
`Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
|
||||
);
|
||||
output_attachment(message, () => {
|
||||
output_message(boundary, message.related, 0, () => {
|
||||
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const output_header_data = () => {
|
||||
if (this.message.attachments.length || this.message.alternative) {
|
||||
output(`MIME-Version: 1.0${CRLF}`);
|
||||
output_mixed();
|
||||
} // you only have a text message!
|
||||
else {
|
||||
output_text(this.message);
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const output_header = () => {
|
||||
let data = [];
|
||||
|
||||
for (let header in this.message.header) {
|
||||
// do not output BCC in the headers (regex) nor custom Object.prototype functions...
|
||||
if (
|
||||
!/bcc/i.test(header) &&
|
||||
this.message.header.hasOwnProperty(header)
|
||||
) {
|
||||
data = data.concat([
|
||||
fix_header_name_case(header),
|
||||
': ',
|
||||
this.message.header[header],
|
||||
CRLF,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
output(data.join(''));
|
||||
output_header_data();
|
||||
output(data.join(''));
|
||||
output_header_data();
|
||||
};
|
||||
|
||||
const output = (data, callback, args) => {
|
||||
const output = (data, callback, args) => {
|
||||
const bytes = Buffer.byteLength(data);
|
||||
|
||||
// can we buffer the data?
|
||||
if ((bytes + this.bufferIndex) < this.buffer.length) {
|
||||
this.buffer.write(data, this.bufferIndex);
|
||||
this.bufferIndex += bytes;
|
||||
if (callback) callback.apply(null, args);
|
||||
}
|
||||
// we can't buffer the data, so ship it out!
|
||||
else if (bytes > this.buffer.length) {
|
||||
if (this.bufferIndex) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.bufferIndex = 0;
|
||||
// can we buffer the data?
|
||||
if (bytes + this.bufferIndex < this.buffer.length) {
|
||||
this.buffer.write(data, this.bufferIndex);
|
||||
this.bufferIndex += bytes;
|
||||
if (callback) callback.apply(null, args);
|
||||
}
|
||||
// we can't buffer the data, so ship it out!
|
||||
else if (bytes > this.buffer.length) {
|
||||
if (this.bufferIndex) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.bufferIndex = 0;
|
||||
}
|
||||
|
||||
const loops = Math.ceil(data.length / this.buffer.length);
|
||||
let loop = 0;
|
||||
while (loop < loops) {
|
||||
this.emit(
|
||||
'data',
|
||||
data.substring(
|
||||
this.buffer.length * loop,
|
||||
this.buffer.length * (loop + 1)
|
||||
)
|
||||
);
|
||||
loop++;
|
||||
}
|
||||
} // we need to clean out the buffer, it is getting full
|
||||
else {
|
||||
if (!this.paused) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.buffer.write(data, 0);
|
||||
this.bufferIndex = bytes;
|
||||
// we could get paused after emitting data...
|
||||
if (this.paused) {
|
||||
this.once('resume', () => callback.apply(null, args));
|
||||
} else if (callback) {
|
||||
callback.apply(null, args);
|
||||
}
|
||||
} // we can't empty out the buffer, so let's wait till we resume before adding to it
|
||||
else {
|
||||
this.once('resume', () => output(data, callback, args));
|
||||
}
|
||||
}
|
||||
const loops = Math.ceil(data.length / this.buffer.length);
|
||||
let loop = 0;
|
||||
while (loop < loops) {
|
||||
this.emit(
|
||||
'data',
|
||||
data.substring(
|
||||
this.buffer.length * loop,
|
||||
this.buffer.length * (loop + 1)
|
||||
)
|
||||
);
|
||||
loop++;
|
||||
}
|
||||
} // we need to clean out the buffer, it is getting full
|
||||
else {
|
||||
if (!this.paused) {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.buffer.write(data, 0);
|
||||
this.bufferIndex = bytes;
|
||||
// we could get paused after emitting data...
|
||||
if (this.paused) {
|
||||
this.once('resume', () => callback.apply(null, args));
|
||||
} else if (callback) {
|
||||
callback.apply(null, args);
|
||||
}
|
||||
} // we can't empty out the buffer, so let's wait till we resume before adding to it
|
||||
else {
|
||||
this.once('resume', () => output(data, callback, args));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const close = (err) => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.emit('end');
|
||||
}
|
||||
this.buffer = null;
|
||||
this.bufferIndex = 0;
|
||||
this.readable = false;
|
||||
this.removeAllListeners('resume');
|
||||
this.removeAllListeners('pause');
|
||||
this.removeAllListeners('error');
|
||||
this.removeAllListeners('data');
|
||||
this.removeAllListeners('end');
|
||||
const close = err => {
|
||||
if (err) {
|
||||
this.emit('error', err);
|
||||
} else {
|
||||
this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
|
||||
this.emit('end');
|
||||
}
|
||||
this.buffer = null;
|
||||
this.bufferIndex = 0;
|
||||
this.readable = false;
|
||||
this.removeAllListeners('resume');
|
||||
this.removeAllListeners('pause');
|
||||
this.removeAllListeners('error');
|
||||
this.removeAllListeners('data');
|
||||
this.removeAllListeners('end');
|
||||
};
|
||||
|
||||
this.once('destroy', close);
|
||||
process.nextTick(output_header);
|
||||
this.once('destroy', close);
|
||||
process.nextTick(output_header);
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.paused = true;
|
||||
this.emit('pause');
|
||||
pause() {
|
||||
this.paused = true;
|
||||
this.emit('pause');
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.paused = false;
|
||||
this.emit('resume');
|
||||
resume() {
|
||||
this.paused = false;
|
||||
this.emit('resume');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.emit('destroy', this.bufferIndex > 0 ? { message: 'message stream destroyed' } : null);
|
||||
destroy() {
|
||||
this.emit(
|
||||
'destroy',
|
||||
this.bufferIndex > 0 ? { message: 'message stream destroyed' } : null
|
||||
);
|
||||
}
|
||||
|
||||
destroySoon() {
|
||||
this.emit('destroy');
|
||||
}
|
||||
destroySoon() {
|
||||
this.emit('destroy');
|
||||
}
|
||||
}
|
||||
|
||||
exports.Message = Message;
|
||||
|
|
|
@ -8,53 +8,75 @@ class SMTPResponse {
|
|||
if (buffer.length) {
|
||||
// parse buffer for response codes
|
||||
const line = buffer.replace('\r', '');
|
||||
if (!line.trim().split(/\n/).pop().match(/^(\d{3})\s/)) {
|
||||
if (
|
||||
!line
|
||||
.trim()
|
||||
.split(/\n/)
|
||||
.pop()
|
||||
.match(/^(\d{3})\s/)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = line ? line.match(/(\d+)\s?(.*)/) : null;
|
||||
const data = match !== null
|
||||
? { code: match[1], message: match[2], data: line }
|
||||
: { code: -1, data: line };
|
||||
const data =
|
||||
match !== null
|
||||
? { code: match[1], message: match[2], data: line }
|
||||
: { code: -1, data: line };
|
||||
|
||||
stream.emit('response', null, data);
|
||||
buffer = '';
|
||||
}
|
||||
};
|
||||
|
||||
const error = (err) => {
|
||||
stream.emit('response', SMTPError('connection encountered an error', SMTPError.ERROR, err));
|
||||
}
|
||||
const error = err => {
|
||||
stream.emit(
|
||||
'response',
|
||||
SMTPError('connection encountered an error', SMTPError.ERROR, err)
|
||||
);
|
||||
};
|
||||
|
||||
const timedout = (err) => {
|
||||
const timedout = err => {
|
||||
stream.end();
|
||||
stream.emit('response', SMTPError('timedout while connecting to smtp server', SMTPError.TIMEDOUT, err));
|
||||
}
|
||||
stream.emit(
|
||||
'response',
|
||||
SMTPError(
|
||||
'timedout while connecting to smtp server',
|
||||
SMTPError.TIMEDOUT,
|
||||
err
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const watch = (data) => {
|
||||
const watch = data => {
|
||||
if (data !== null) {
|
||||
buffer += data.toString();
|
||||
notify();
|
||||
}
|
||||
};
|
||||
|
||||
const close = (err) => {
|
||||
stream.emit('response', SMTPError('connection has closed', SMTPError.CONNECTIONCLOSED, err));
|
||||
const close = err => {
|
||||
stream.emit(
|
||||
'response',
|
||||
SMTPError('connection has closed', SMTPError.CONNECTIONCLOSED, err)
|
||||
);
|
||||
};
|
||||
|
||||
const end = (err) => {
|
||||
stream.emit('response', SMTPError('connection has ended', SMTPError.CONNECTIONENDED, err));
|
||||
const end = err => {
|
||||
stream.emit(
|
||||
'response',
|
||||
SMTPError('connection has ended', SMTPError.CONNECTIONENDED, err)
|
||||
);
|
||||
};
|
||||
|
||||
this.stop = (err) => {
|
||||
this.stop = err => {
|
||||
stream.removeAllListeners('response');
|
||||
stream.removeListener('data', watch);
|
||||
stream.removeListener('end', end);
|
||||
stream.removeListener('close', close);
|
||||
stream.removeListener('error', error);
|
||||
|
||||
if (err && typeof onerror === 'function')
|
||||
onerror(err);
|
||||
if (err && typeof onerror === 'function') onerror(err);
|
||||
};
|
||||
|
||||
stream.on('data', watch);
|
||||
|
@ -65,4 +87,5 @@ class SMTPResponse {
|
|||
}
|
||||
}
|
||||
|
||||
exports.monitor = (stream, timeout, onerror) => new SMTPResponse(stream, timeout, onerror);
|
||||
exports.monitor = (stream, timeout, onerror) =>
|
||||
new SMTPResponse(stream, timeout, onerror);
|
||||
|
|
664
smtp/smtp.js
664
smtp/smtp.js
|
@ -15,44 +15,36 @@ const SMTP_SSL_PORT = 465;
|
|||
const SMTP_TLS_PORT = 587;
|
||||
const CRLF = '\r\n';
|
||||
const AUTH_METHODS = {
|
||||
PLAIN: 'PLAIN',
|
||||
CRAM_MD5: 'CRAM-MD5',
|
||||
LOGIN: 'LOGIN',
|
||||
XOAUTH2: 'XOAUTH2'
|
||||
PLAIN: 'PLAIN',
|
||||
CRAM_MD5: 'CRAM-MD5',
|
||||
LOGIN: 'LOGIN',
|
||||
XOAUTH2: 'XOAUTH2',
|
||||
};
|
||||
|
||||
const TIMEOUT = 5000;
|
||||
let DEBUG = 0;
|
||||
|
||||
const log = function() {
|
||||
if (DEBUG) {
|
||||
Array.prototype.slice.call(arguments).forEach(function(d) {
|
||||
console.log(d);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const quotedata = function(data) {
|
||||
// Quote data for email.
|
||||
// Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
|
||||
// Internet CRLF end-of-line.
|
||||
|
||||
return data.replace(/(?:\r\n|\n|\r(?!\n))/g, CRLF).replace(/^\./gm, '..');
|
||||
if (DEBUG) {
|
||||
Array.prototype.slice.call(arguments).forEach(function(d) {
|
||||
console.log(d);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const caller = function(callback) {
|
||||
if (typeof(callback) == 'function') {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
args.shift();
|
||||
if (typeof callback == 'function') {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
args.shift();
|
||||
|
||||
callback.apply(null, args);
|
||||
}
|
||||
callback.apply(null, args);
|
||||
}
|
||||
};
|
||||
|
||||
const SMTPState = {
|
||||
NOTCONNECTED: 0,
|
||||
CONNECTING: 1,
|
||||
CONNECTED: 2
|
||||
NOTCONNECTED: 0,
|
||||
CONNECTING: 1,
|
||||
CONNECTED: 2,
|
||||
};
|
||||
|
||||
class SMTP extends EventEmitter {
|
||||
|
@ -69,19 +61,22 @@ class SMTP extends EventEmitter {
|
|||
ssl,
|
||||
tls,
|
||||
authentication,
|
||||
} = Object.assign({
|
||||
timeout: TIMEOUT,
|
||||
domain: os.hostname(),
|
||||
host: 'localhost',
|
||||
ssl: false,
|
||||
tls: false,
|
||||
authentication: [
|
||||
AUTH_METHODS.CRAM_MD5,
|
||||
AUTH_METHODS.LOGIN,
|
||||
AUTH_METHODS.PLAIN,
|
||||
AUTH_METHODS.XOAUTH2,
|
||||
],
|
||||
}, options);
|
||||
} = Object.assign(
|
||||
{
|
||||
timeout: TIMEOUT,
|
||||
domain: os.hostname(),
|
||||
host: 'localhost',
|
||||
ssl: false,
|
||||
tls: false,
|
||||
authentication: [
|
||||
AUTH_METHODS.CRAM_MD5,
|
||||
AUTH_METHODS.LOGIN,
|
||||
AUTH_METHODS.PLAIN,
|
||||
AUTH_METHODS.XOAUTH2,
|
||||
],
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
this._state = SMTPState.NOTCONNECTED;
|
||||
this._secure = false;
|
||||
|
@ -98,7 +93,7 @@ class SMTP extends EventEmitter {
|
|||
this.tls = tls;
|
||||
|
||||
this.port = port || (ssl ? SMTP_SSL_PORT : tls ? SMTP_TLS_PORT : SMTP_PORT);
|
||||
this.loggedin = (user && password) ? false : true;
|
||||
this.loggedin = user && password ? false : true;
|
||||
|
||||
// keep these strings hidden when quicky debugging/logging
|
||||
this.user = () => options.user;
|
||||
|
@ -106,132 +101,154 @@ class SMTP extends EventEmitter {
|
|||
}
|
||||
|
||||
debug(level) {
|
||||
DEBUG = level;
|
||||
}
|
||||
DEBUG = level;
|
||||
}
|
||||
|
||||
state() {
|
||||
return this._state;
|
||||
}
|
||||
state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
authorized() {
|
||||
return this.loggedin;
|
||||
}
|
||||
authorized() {
|
||||
return this.loggedin;
|
||||
}
|
||||
|
||||
connect(callback, port, host, options) {
|
||||
options = options || {};
|
||||
connect(callback, port, host, options) {
|
||||
options = options || {};
|
||||
|
||||
this.host = host || this.host;
|
||||
this.port = port || this.port;
|
||||
this.ssl = options.ssl || this.ssl;
|
||||
this.host = host || this.host;
|
||||
this.port = port || this.port;
|
||||
this.ssl = options.ssl || this.ssl;
|
||||
|
||||
if (this._state != SMTPState.NOTCONNECTED) {
|
||||
this.quit(() => this.connect(callback, port, host, options));
|
||||
return;
|
||||
}
|
||||
if (this._state != SMTPState.NOTCONNECTED) {
|
||||
this.quit(() => this.connect(callback, port, host, options));
|
||||
return;
|
||||
}
|
||||
|
||||
const connected = (err) => {
|
||||
if (!err) {
|
||||
log(`connected: ${this.host}:${this.port}`);
|
||||
const connected = err => {
|
||||
if (!err) {
|
||||
log(`connected: ${this.host}:${this.port}`);
|
||||
|
||||
if (this.ssl && !this.tls) {
|
||||
// if key/ca/cert was passed in, check if connection is authorized
|
||||
if (typeof(this.ssl) != 'boolean' && !this.sock.authorized) {
|
||||
if (this.ssl && !this.tls) {
|
||||
// if key/ca/cert was passed in, check if connection is authorized
|
||||
if (typeof this.ssl != 'boolean' && !this.sock.authorized) {
|
||||
this.close(true);
|
||||
const msg = 'could not establish an ssl connection';
|
||||
caller(callback, SMTPError(msg, SMTPError.CONNECTIONAUTH, err));
|
||||
} else {
|
||||
caller(callback, SMTPError(msg, SMTPError.CONNECTIONAUTH, err));
|
||||
} else {
|
||||
this._secure = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.close(true);
|
||||
caller(callback, SMTPError('could not connect', SMTPError.COULDNOTCONNECT, err));
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.close(true);
|
||||
caller(
|
||||
callback,
|
||||
SMTPError('could not connect', SMTPError.COULDNOTCONNECT, err)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const response = (err, msg) => {
|
||||
if (err) {
|
||||
if (this._state === SMTPState.NOTCONNECTED && !this.sock) {
|
||||
return;
|
||||
}
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
} else if (msg.code == '220') {
|
||||
log(msg.data);
|
||||
const response = (err, msg) => {
|
||||
if (err) {
|
||||
if (this._state === SMTPState.NOTCONNECTED && !this.sock) {
|
||||
return;
|
||||
}
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
} else if (msg.code == '220') {
|
||||
log(msg.data);
|
||||
|
||||
// might happen first, so no need to wait on connected()
|
||||
this._state = SMTPState.CONNECTED;
|
||||
caller(callback, null, msg.data);
|
||||
} else {
|
||||
log(`response (data): ${msg.data}`);
|
||||
this.quit(() => {
|
||||
const err = SMTPError('bad response on connection', SMTPError.BADRESPONSE, err, msg.data);
|
||||
caller(callback, err);
|
||||
});
|
||||
}
|
||||
};
|
||||
// might happen first, so no need to wait on connected()
|
||||
this._state = SMTPState.CONNECTED;
|
||||
caller(callback, null, msg.data);
|
||||
} else {
|
||||
log(`response (data): ${msg.data}`);
|
||||
this.quit(() => {
|
||||
const err = SMTPError(
|
||||
'bad response on connection',
|
||||
SMTPError.BADRESPONSE,
|
||||
err,
|
||||
msg.data
|
||||
);
|
||||
caller(callback, err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this._state = SMTPState.CONNECTING;
|
||||
log(`connecting: ${this.host}:${this.port}`);
|
||||
this._state = SMTPState.CONNECTING;
|
||||
log(`connecting: ${this.host}:${this.port}`);
|
||||
|
||||
if (this.ssl) {
|
||||
this.sock = tls.connect(this.port, this.host, this.ssl, connected);
|
||||
} else {
|
||||
this.sock = new net.Socket();
|
||||
this.sock.connect(this.port, this.host, connected);
|
||||
}
|
||||
if (this.ssl) {
|
||||
this.sock = tls.connect(this.port, this.host, this.ssl, connected);
|
||||
} else {
|
||||
this.sock = new net.Socket();
|
||||
this.sock.connect(this.port, this.host, connected);
|
||||
}
|
||||
|
||||
this.monitor = SMTPResponse.monitor(this.sock, this.timeout, () => this.close(true));
|
||||
this.sock.once('response', response);
|
||||
this.sock.once('error', response); // the socket could reset or throw, so let's handle it and let the user know
|
||||
}
|
||||
this.monitor = SMTPResponse.monitor(this.sock, this.timeout, () =>
|
||||
this.close(true)
|
||||
);
|
||||
this.sock.once('response', response);
|
||||
this.sock.once('error', response); // the socket could reset or throw, so let's handle it and let the user know
|
||||
}
|
||||
|
||||
send(str, callback) {
|
||||
if (this.sock && this._state == SMTPState.CONNECTED) {
|
||||
log(str);
|
||||
send(str, callback) {
|
||||
if (this.sock && this._state == SMTPState.CONNECTED) {
|
||||
log(str);
|
||||
|
||||
this.sock.once('response', (err, msg) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
log(msg.data);
|
||||
caller(callback, null, msg);
|
||||
}
|
||||
});
|
||||
this.sock.write(str);
|
||||
} else {
|
||||
this.close(true);
|
||||
caller(callback, SMTPError('no connection has been established', SMTPError.NOCONNECTION));
|
||||
}
|
||||
}
|
||||
this.sock.once('response', (err, msg) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
log(msg.data);
|
||||
caller(callback, null, msg);
|
||||
}
|
||||
});
|
||||
this.sock.write(str);
|
||||
} else {
|
||||
this.close(true);
|
||||
caller(
|
||||
callback,
|
||||
SMTPError('no connection has been established', SMTPError.NOCONNECTION)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
command(cmd, callback, codes, failed) {
|
||||
codes = Array.isArray(codes) ? codes : typeof(codes) == 'number' ? [codes] : [250];
|
||||
command(cmd, callback, codes, failed) {
|
||||
codes = Array.isArray(codes)
|
||||
? codes
|
||||
: typeof codes == 'number'
|
||||
? [codes]
|
||||
: [250];
|
||||
|
||||
const response = (err, msg) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
if (codes.indexOf(Number(msg.code)) != -1) {
|
||||
caller(callback, err, msg.data, msg.message);
|
||||
} else {
|
||||
const response = (err, msg) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
if (codes.indexOf(Number(msg.code)) != -1) {
|
||||
caller(callback, err, msg.data, msg.message);
|
||||
} else {
|
||||
const suffix = msg.message ? `: ${msg.message}` : '';
|
||||
const errorMessage = `bad response on command '${cmd.split(' ')[0]}'${suffix}`;
|
||||
caller(callback, SMTPError(errorMessage, SMTPError.BADRESPONSE, null, msg.data));
|
||||
}
|
||||
}
|
||||
};
|
||||
const errorMessage = `bad response on command '${
|
||||
cmd.split(' ')[0]
|
||||
}'${suffix}`;
|
||||
caller(
|
||||
callback,
|
||||
SMTPError(errorMessage, SMTPError.BADRESPONSE, null, msg.data)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.send(cmd + CRLF, response);
|
||||
}
|
||||
this.send(cmd + CRLF, response);
|
||||
}
|
||||
|
||||
helo(callback, domain) {
|
||||
/*
|
||||
helo(callback, domain) {
|
||||
/*
|
||||
* SMTP 'helo' command.
|
||||
* Hostname to send for self command defaults to the FQDN of the local
|
||||
* host.
|
||||
*/
|
||||
this.command(`helo ${domain || this.domain}`, (err, data) => {
|
||||
this.command(`helo ${domain || this.domain}`, (err, data) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
|
@ -239,92 +256,102 @@ class SMTP extends EventEmitter {
|
|||
caller(callback, err, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
starttls(callback) {
|
||||
const response = (err, msg) => {
|
||||
if (err) {
|
||||
err.message += ' while establishing a starttls session';
|
||||
caller(callback, err);
|
||||
} else {
|
||||
// support new API
|
||||
if (tls.TLSSocket) {
|
||||
const secured_socket = new tls.TLSSocket(this.sock, {
|
||||
secureContext: tls.createSecureContext ? tls.createSecureContext(this.tls) : crypto.createCredentials(this.tls),
|
||||
isServer: false // older versions of node (0.12), do not default to false properly...
|
||||
});
|
||||
starttls(callback) {
|
||||
const response = (err, msg) => {
|
||||
if (err) {
|
||||
err.message += ' while establishing a starttls session';
|
||||
caller(callback, err);
|
||||
} else {
|
||||
// support new API
|
||||
if (tls.TLSSocket) {
|
||||
const secured_socket = new tls.TLSSocket(this.sock, {
|
||||
secureContext: tls.createSecureContext
|
||||
? tls.createSecureContext(this.tls)
|
||||
: crypto.createCredentials(this.tls),
|
||||
isServer: false, // older versions of node (0.12), do not default to false properly...
|
||||
});
|
||||
|
||||
secured_socket.on('error', (err) => {
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
});
|
||||
secured_socket.on('error', err => {
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
});
|
||||
|
||||
this._secure = true;
|
||||
this.sock = secured_socket;
|
||||
this._secure = true;
|
||||
this.sock = secured_socket;
|
||||
|
||||
SMTPResponse.monitor(this.sock, this.timeout, () => this.close(true));
|
||||
caller(callback, msg.data);
|
||||
} else {
|
||||
const secured_socket = null;
|
||||
const secured = () => {
|
||||
this._secure = true;
|
||||
this.sock = secured_socket;
|
||||
SMTPResponse.monitor(this.sock, this.timeout, () => this.close(true));
|
||||
caller(callback, msg.data);
|
||||
} else {
|
||||
let secured_socket = null;
|
||||
const secured = () => {
|
||||
this._secure = true;
|
||||
this.sock = secured_socket;
|
||||
|
||||
SMTPResponse.monitor(this.sock, this.timeout, () => this.close(true));
|
||||
caller(callback, msg.data);
|
||||
};
|
||||
SMTPResponse.monitor(this.sock, this.timeout, () =>
|
||||
this.close(true)
|
||||
);
|
||||
caller(callback, msg.data);
|
||||
};
|
||||
|
||||
const starttls = require('starttls');
|
||||
secured_socket = starttls({
|
||||
socket: this.sock,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
pair: tls.createSecurePair(
|
||||
tls.createSecureContext ? tls.createSecureContext(this.tls) : crypto.createCredentials(this.tls),
|
||||
false)
|
||||
}, secured).cleartext;
|
||||
const starttls = require('starttls');
|
||||
secured_socket = starttls(
|
||||
{
|
||||
socket: this.sock,
|
||||
host: this.host,
|
||||
port: this.port,
|
||||
pair: tls.createSecurePair(
|
||||
tls.createSecureContext
|
||||
? tls.createSecureContext(this.tls)
|
||||
: crypto.createCredentials(this.tls),
|
||||
false
|
||||
),
|
||||
},
|
||||
secured
|
||||
).cleartext;
|
||||
|
||||
secured_socket.on('error', (err) => {
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
secured_socket.on('error', err => {
|
||||
this.close(true);
|
||||
caller(callback, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.command('starttls', response, [220]);
|
||||
}
|
||||
this.command('starttls', response, [220]);
|
||||
}
|
||||
|
||||
parse_smtp_features(data) {
|
||||
// According to RFC1869 some (badly written)
|
||||
// MTA's will disconnect on an ehlo. Toss an exception if
|
||||
// that happens -ddm
|
||||
parse_smtp_features(data) {
|
||||
// According to RFC1869 some (badly written)
|
||||
// MTA's will disconnect on an ehlo. Toss an exception if
|
||||
// that happens -ddm
|
||||
|
||||
data.split('\n').forEach((ext) => {
|
||||
const parse = ext.match(/^(?:\d+[\-=]?)\s*?([^\s]+)(?:\s+(.*)\s*?)?$/);
|
||||
data.split('\n').forEach(ext => {
|
||||
const parse = ext.match(/^(?:\d+[-=]?)\s*?([^\s]+)(?:\s+(.*)\s*?)?$/);
|
||||
|
||||
// To be able to communicate with as many SMTP servers as possible,
|
||||
// we have to take the old-style auth advertisement into account,
|
||||
// because:
|
||||
// 1) Else our SMTP feature parser gets confused.
|
||||
// 2) There are some servers that only advertise the auth methods we
|
||||
// support using the old style.
|
||||
// To be able to communicate with as many SMTP servers as possible,
|
||||
// we have to take the old-style auth advertisement into account,
|
||||
// because:
|
||||
// 1) Else our SMTP feature parser gets confused.
|
||||
// 2) There are some servers that only advertise the auth methods we
|
||||
// support using the old style.
|
||||
|
||||
if (parse) {
|
||||
// RFC 1869 requires a space between ehlo keyword and parameters.
|
||||
// It's actually stricter, in that only spaces are allowed between
|
||||
// parameters, but were not going to check for that here. Note
|
||||
// that the space isn't present if there are no parameters.
|
||||
this.features[parse[1].toLowerCase()] = parse[2] || true;
|
||||
}
|
||||
});
|
||||
if (parse) {
|
||||
// RFC 1869 requires a space between ehlo keyword and parameters.
|
||||
// It's actually stricter, in that only spaces are allowed between
|
||||
// parameters, but were not going to check for that here. Note
|
||||
// that the space isn't present if there are no parameters.
|
||||
this.features[parse[1].toLowerCase()] = parse[2] || true;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ehlo(callback, domain) {
|
||||
this.features = {};
|
||||
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
|
||||
ehlo(callback, domain) {
|
||||
this.features = {};
|
||||
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
|
||||
if (err) {
|
||||
caller(callback, err);
|
||||
} else {
|
||||
|
@ -337,74 +364,74 @@ class SMTP extends EventEmitter {
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
has_extn(opt) {
|
||||
return this.features[opt.toLowerCase()] === undefined;
|
||||
}
|
||||
has_extn(opt) {
|
||||
return this.features[opt.toLowerCase()] === undefined;
|
||||
}
|
||||
|
||||
help(callback, args) {
|
||||
// SMTP 'help' command, returns text from the server
|
||||
this.command(args ? `help ${args}` : 'help', callback, [211, 214]);
|
||||
}
|
||||
help(callback, args) {
|
||||
// SMTP 'help' command, returns text from the server
|
||||
this.command(args ? `help ${args}` : 'help', callback, [211, 214]);
|
||||
}
|
||||
|
||||
rset(callback) {
|
||||
this.command('rset', callback);
|
||||
}
|
||||
rset(callback) {
|
||||
this.command('rset', callback);
|
||||
}
|
||||
|
||||
noop(callback) {
|
||||
this.send('noop', callback);
|
||||
}
|
||||
noop(callback) {
|
||||
this.send('noop', callback);
|
||||
}
|
||||
|
||||
mail(callback, from) {
|
||||
this.command(`mail FROM:${from}`, callback);
|
||||
}
|
||||
mail(callback, from) {
|
||||
this.command(`mail FROM:${from}`, callback);
|
||||
}
|
||||
|
||||
rcpt(callback, to) {
|
||||
this.command(`RCPT TO:${to}`, callback, [250, 251]);
|
||||
}
|
||||
rcpt(callback, to) {
|
||||
this.command(`RCPT TO:${to}`, callback, [250, 251]);
|
||||
}
|
||||
|
||||
data(callback) {
|
||||
this.command('data', callback, [354]);
|
||||
}
|
||||
data(callback) {
|
||||
this.command('data', callback, [354]);
|
||||
}
|
||||
|
||||
data_end(callback) {
|
||||
this.command(`${CRLF}.`, callback);
|
||||
}
|
||||
data_end(callback) {
|
||||
this.command(`${CRLF}.`, callback);
|
||||
}
|
||||
|
||||
message(data) {
|
||||
log(data);
|
||||
this.sock.write(data);
|
||||
}
|
||||
message(data) {
|
||||
log(data);
|
||||
this.sock.write(data);
|
||||
}
|
||||
|
||||
verify(address, callback) {
|
||||
// SMTP 'verify' command -- checks for address validity.
|
||||
this.command(`vrfy ${address}`, callback, [250, 251, 252]);
|
||||
}
|
||||
verify(address, callback) {
|
||||
// SMTP 'verify' command -- checks for address validity.
|
||||
this.command(`vrfy ${address}`, callback, [250, 251, 252]);
|
||||
}
|
||||
|
||||
expn(address, callback) {
|
||||
// SMTP 'expn' command -- expands a mailing list.
|
||||
this.command(`expn ${address}`, callback);
|
||||
}
|
||||
expn(address, callback) {
|
||||
// SMTP 'expn' command -- expands a mailing list.
|
||||
this.command(`expn ${address}`, callback);
|
||||
}
|
||||
|
||||
ehlo_or_helo_if_needed(callback, domain) {
|
||||
// Call this.ehlo() and/or this.helo() if needed.
|
||||
// If there has been no previous EHLO or HELO command self session, self
|
||||
// method tries ESMTP EHLO first.
|
||||
if (!this.features) {
|
||||
const response = (err, data) => caller(callback, err, data);
|
||||
this.ehlo((err, data) => {
|
||||
if (err) {
|
||||
ehlo_or_helo_if_needed(callback, domain) {
|
||||
// Call this.ehlo() and/or this.helo() if needed.
|
||||
// If there has been no previous EHLO or HELO command self session, self
|
||||
// method tries ESMTP EHLO first.
|
||||
if (!this.features) {
|
||||
const response = (err, data) => caller(callback, err, data);
|
||||
this.ehlo((err, data) => {
|
||||
if (err) {
|
||||
this.helo(response, domain);
|
||||
} else {
|
||||
caller(callback, err, data);
|
||||
}
|
||||
}, domain);
|
||||
}
|
||||
}
|
||||
}, domain);
|
||||
}
|
||||
}
|
||||
|
||||
login(callback, user, password, options) {
|
||||
const login = {
|
||||
login(callback, user, password, options) {
|
||||
const login = {
|
||||
user: user ? () => user : this.user,
|
||||
password: password ? () => password : this.password,
|
||||
method: options && options.method ? options.method.toUpperCase() : '',
|
||||
|
@ -433,22 +460,24 @@ class SMTP extends EventEmitter {
|
|||
|
||||
let method = null;
|
||||
|
||||
const encode_cram_md5 = (challenge) => {
|
||||
const encode_cram_md5 = challenge => {
|
||||
const hmac = crypto.createHmac('md5', login.password());
|
||||
hmac.update(Buffer.from(challenge, 'base64').toString('ascii'));
|
||||
return Buffer.from(`${login.user()} ${hmac.digest('hex')}`).toString('base64');
|
||||
return Buffer.from(`${login.user()} ${hmac.digest('hex')}`).toString(
|
||||
'base64'
|
||||
);
|
||||
};
|
||||
|
||||
const encode_plain = () =>
|
||||
Buffer
|
||||
.from(`\u0000${login.user()}\u0000${login.password()}`)
|
||||
.toString('base64');
|
||||
Buffer.from(`\u0000${login.user()}\u0000${login.password()}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
// see: https://developers.google.com/gmail/xoauth2_protocol
|
||||
const encode_xoauth2 = () =>
|
||||
Buffer
|
||||
.from(`user=${login.user()}\u0001auth=Bearer ${login.password()}\u0001\u0001`)
|
||||
.toString('base64');
|
||||
Buffer.from(
|
||||
`user=${login.user()}\u0001auth=Bearer ${login.password()}\u0001\u0001`
|
||||
).toString('base64');
|
||||
|
||||
// List of authentication methods we support: from preferred to
|
||||
// less preferred methods.
|
||||
|
@ -457,7 +486,7 @@ class SMTP extends EventEmitter {
|
|||
let auth = '';
|
||||
|
||||
if (this.features && this.features.auth) {
|
||||
if (typeof(this.features.auth) === 'string') {
|
||||
if (typeof this.features.auth === 'string') {
|
||||
auth = this.features.auth;
|
||||
}
|
||||
}
|
||||
|
@ -474,7 +503,10 @@ class SMTP extends EventEmitter {
|
|||
const failed = (err, data) => {
|
||||
this.loggedin = false;
|
||||
this.close(); // if auth is bad, close the connection, it won't get better by itself
|
||||
caller(callback, SMTPError('authorization.failed', SMTPError.AUTHFAILED, err, data));
|
||||
caller(
|
||||
callback,
|
||||
SMTPError('authorization.failed', SMTPError.AUTHFAILED, err, data)
|
||||
);
|
||||
};
|
||||
|
||||
const response = (err, data) => {
|
||||
|
@ -493,7 +525,11 @@ class SMTP extends EventEmitter {
|
|||
if (method == AUTH_METHODS.CRAM_MD5) {
|
||||
this.command(encode_cram_md5(msg), response, [235, 503]);
|
||||
} else if (method == AUTH_METHODS.LOGIN) {
|
||||
this.command(Buffer.from(login.password()).toString('base64'), response, [235, 503]);
|
||||
this.command(
|
||||
Buffer.from(login.password()).toString('base64'),
|
||||
response,
|
||||
[235, 503]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -503,7 +539,11 @@ class SMTP extends EventEmitter {
|
|||
failed(err, data);
|
||||
} else {
|
||||
if (method == AUTH_METHODS.LOGIN) {
|
||||
this.command(Buffer.from(login.user()).toString('base64'), attempt, [334]);
|
||||
this.command(
|
||||
Buffer.from(login.user()).toString('base64'),
|
||||
attempt,
|
||||
[334]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -516,10 +556,24 @@ class SMTP extends EventEmitter {
|
|||
this.command(`AUTH ${AUTH_METHODS.LOGIN}`, attempt_user, [334]);
|
||||
break;
|
||||
case AUTH_METHODS.PLAIN:
|
||||
this.command(`AUTH ${AUTH_METHODS.PLAIN} ${encode_plain(login.user(), login.password())}`, response, [235, 503]);
|
||||
this.command(
|
||||
`AUTH ${AUTH_METHODS.PLAIN} ${encode_plain(
|
||||
login.user(),
|
||||
login.password()
|
||||
)}`,
|
||||
response,
|
||||
[235, 503]
|
||||
);
|
||||
break;
|
||||
case AUTH_METHODS.XOAUTH2:
|
||||
this.command(`AUTH ${AUTH_METHODS.XOAUTH2} ${encode_xoauth2(login.user(), login.password())}`, response, [235, 503]);
|
||||
this.command(
|
||||
`AUTH ${AUTH_METHODS.XOAUTH2} ${encode_xoauth2(
|
||||
login.user(),
|
||||
login.password()
|
||||
)}`,
|
||||
response,
|
||||
[235, 503]
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const msg = 'no form of authorization supported';
|
||||
|
@ -529,38 +583,42 @@ class SMTP extends EventEmitter {
|
|||
}
|
||||
};
|
||||
|
||||
this.ehlo_or_helo_if_needed(initiate, domain);
|
||||
}
|
||||
this.ehlo_or_helo_if_needed(initiate, domain);
|
||||
}
|
||||
|
||||
close(force) {
|
||||
if (this.sock) {
|
||||
if (force) {
|
||||
log('smtp connection destroyed!');
|
||||
this.sock.destroy();
|
||||
} else {
|
||||
log('smtp connection closed.');
|
||||
this.sock.end();
|
||||
}
|
||||
}
|
||||
close(force) {
|
||||
if (this.sock) {
|
||||
if (force) {
|
||||
log('smtp connection destroyed!');
|
||||
this.sock.destroy();
|
||||
} else {
|
||||
log('smtp connection closed.');
|
||||
this.sock.end();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.monitor) {
|
||||
this.monitor.stop();
|
||||
this.monitor = null;
|
||||
}
|
||||
if (this.monitor) {
|
||||
this.monitor.stop();
|
||||
this.monitor = null;
|
||||
}
|
||||
|
||||
this._state = SMTPState.NOTCONNECTED;
|
||||
this._secure = false;
|
||||
this.sock = null;
|
||||
this.features = null;
|
||||
this.loggedin = !(this.user() && this.password());
|
||||
}
|
||||
this._state = SMTPState.NOTCONNECTED;
|
||||
this._secure = false;
|
||||
this.sock = null;
|
||||
this.features = null;
|
||||
this.loggedin = !(this.user() && this.password());
|
||||
}
|
||||
|
||||
quit(callback) {
|
||||
this.command('quit', (err, data) => {
|
||||
caller(callback, err, data);
|
||||
this.close();
|
||||
}, [221, 250]);
|
||||
}
|
||||
quit(callback) {
|
||||
this.command(
|
||||
'quit',
|
||||
(err, data) => {
|
||||
caller(callback, err, data);
|
||||
this.close();
|
||||
},
|
||||
[221, 250]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.SMTP = SMTP;
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
describe('authorize plain', function() {
|
||||
const { simpleParser: parser } = require('mailparser');
|
||||
const { SMTPServer: smtpServer } = require('smtp-server');
|
||||
const { expect } = require('chai');
|
||||
const email = require('../email');
|
||||
const { simpleParser: parser } = require('mailparser');
|
||||
const { SMTPServer: smtpServer } = require('smtp-server');
|
||||
const { expect } = require('chai');
|
||||
const email = require('../email');
|
||||
const port = 2526;
|
||||
|
||||
let server = null;
|
||||
let smtp = null;
|
||||
let server = null;
|
||||
let smtp = null;
|
||||
|
||||
const send = function(message, verify, done) {
|
||||
smtp.onData = function(stream, session, callback) {
|
||||
parser(stream)
|
||||
.then(verify)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
stream.on('end', callback);
|
||||
};
|
||||
|
||||
server.send(message, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
|
||||
before(function(done) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
|
||||
|
||||
smtp = new smtpServer({ secure: true, authMethods: ['LOGIN'] });
|
||||
smtp.listen(port, function() {
|
||||
smtp.onAuth = function(auth, session, callback) {
|
||||
if (auth.username == 'pooh' && auth.password == 'honey') {
|
||||
callback(null, { user: 'pooh' });
|
||||
} else {
|
||||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
};
|
||||
|
||||
server = email.server.connect({
|
||||
port: port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
smtp.close(done);
|
||||
});
|
||||
|
||||
it('login', function(done) {
|
||||
const message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'piglet@gmail.com',
|
||||
to: 'pooh@gmail.com',
|
||||
text: 'It is hard to be brave when you\'re only a Very Small Animal.'
|
||||
const send = function(message, verify, done) {
|
||||
smtp.onData = function(stream, session, callback) {
|
||||
parser(stream)
|
||||
.then(verify)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
stream.on('end', callback);
|
||||
};
|
||||
|
||||
const created = email.message.create(message);
|
||||
server.send(message, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
|
||||
before(function(done) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
|
||||
|
||||
smtp = new smtpServer({ secure: true, authMethods: ['LOGIN'] });
|
||||
smtp.listen(port, function() {
|
||||
smtp.onAuth = function(auth, session, callback) {
|
||||
if (auth.username == 'pooh' && auth.password == 'honey') {
|
||||
callback(null, { user: 'pooh' });
|
||||
} else {
|
||||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
};
|
||||
|
||||
server = email.server.connect({
|
||||
port: port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true,
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
smtp.close(done);
|
||||
});
|
||||
|
||||
it('login', function(done) {
|
||||
const message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'piglet@gmail.com',
|
||||
to: 'pooh@gmail.com',
|
||||
text: "It is hard to be brave when you're only a Very Small Animal.",
|
||||
};
|
||||
|
||||
const created = email.message.create(message);
|
||||
|
||||
const callback = function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
|
@ -67,6 +67,6 @@ describe('authorize plain', function() {
|
|||
expect(mail.to.text).to.equal(message.to);
|
||||
};
|
||||
|
||||
send(created, callback, done);
|
||||
});
|
||||
send(created, callback, done);
|
||||
});
|
||||
});
|
||||
|
|
118
test/authssl.js
118
test/authssl.js
|
@ -1,71 +1,71 @@
|
|||
describe('authorize ssl', function() {
|
||||
const { simpleParser: parser } = require('mailparser');
|
||||
const { SMTPServer: smtpServer } = require('smtp-server');
|
||||
const { expect } = require('chai');
|
||||
const email = require('../email');
|
||||
const { simpleParser: parser } = require('mailparser');
|
||||
const { SMTPServer: smtpServer } = require('smtp-server');
|
||||
const { expect } = require('chai');
|
||||
const email = require('../email');
|
||||
const port = 2526;
|
||||
|
||||
let server = null;
|
||||
let smtp = null;
|
||||
let server = null;
|
||||
let smtp = null;
|
||||
|
||||
const send = function(message, verify, done) {
|
||||
smtp.onData = function(stream, session, callback) {
|
||||
parser(stream)
|
||||
.then(verify)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
stream.on('end', callback);
|
||||
};
|
||||
const send = function(message, verify, done) {
|
||||
smtp.onData = function(stream, session, callback) {
|
||||
parser(stream)
|
||||
.then(verify)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
stream.on('end', callback);
|
||||
};
|
||||
|
||||
server.send(message, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
server.send(message, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
|
||||
before(function(done) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
|
||||
before(function(done) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
|
||||
|
||||
smtp = new smtpServer({ secure: true, authMethods: ['LOGIN'] });
|
||||
smtp.listen(port, function() {
|
||||
smtp.onAuth = function(auth, session, callback) {
|
||||
if (auth.username == 'pooh' && auth.password == 'honey') {
|
||||
callback(null, { user: 'pooh' });
|
||||
} else {
|
||||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
};
|
||||
smtp = new smtpServer({ secure: true, authMethods: ['LOGIN'] });
|
||||
smtp.listen(port, function() {
|
||||
smtp.onAuth = function(auth, session, callback) {
|
||||
if (auth.username == 'pooh' && auth.password == 'honey') {
|
||||
callback(null, { user: 'pooh' });
|
||||
} else {
|
||||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
};
|
||||
|
||||
server = email.server.connect({
|
||||
port: port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
server = email.server.connect({
|
||||
port: port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true,
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
smtp.close(done);
|
||||
});
|
||||
after(function(done) {
|
||||
smtp.close(done);
|
||||
});
|
||||
|
||||
it('login', function(done) {
|
||||
const message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'pooh@gmail.com',
|
||||
to: 'rabbit@gmail.com',
|
||||
text: 'hello friend, i hope this message finds you well.'
|
||||
};
|
||||
it('login', function(done) {
|
||||
const message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'pooh@gmail.com',
|
||||
to: 'rabbit@gmail.com',
|
||||
text: 'hello friend, i hope this message finds you well.',
|
||||
};
|
||||
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
838
test/message.js
838
test/message.js
|
@ -1,455 +1,455 @@
|
|||
describe('messages', function() {
|
||||
const { simpleParser: parser } = require('mailparser');
|
||||
const { SMTPServer: smtpServer } = require('smtp-server');
|
||||
const { expect } = require('chai');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const email = require('../email');
|
||||
const port = 2526;
|
||||
const { simpleParser: parser } = require('mailparser');
|
||||
const { SMTPServer: smtpServer } = require('smtp-server');
|
||||
const { expect } = require('chai');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const email = require('../email');
|
||||
const port = 2526;
|
||||
|
||||
let server = null;
|
||||
let smtp = null;
|
||||
let server = null;
|
||||
let smtp = null;
|
||||
|
||||
const send = function(message, verify, done) {
|
||||
smtp.onData = function(stream, session, callback) {
|
||||
//stream.pipe(process.stdout);
|
||||
parser(stream)
|
||||
.then(verify)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
stream.on('end', callback);
|
||||
};
|
||||
const send = function(message, verify, done) {
|
||||
smtp.onData = function(stream, session, callback) {
|
||||
//stream.pipe(process.stdout);
|
||||
parser(stream)
|
||||
.then(verify)
|
||||
.then(done)
|
||||
.catch(done);
|
||||
stream.on('end', callback);
|
||||
};
|
||||
|
||||
server.send(message, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
server.send(message, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
|
||||
before(function(done) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
|
||||
before(function(done) {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
|
||||
|
||||
smtp = new smtpServer({ secure: true, authMethods: ['LOGIN'] });
|
||||
smtp.listen(port, function() {
|
||||
smtp.onAuth = function(auth, session, callback) {
|
||||
if (auth.username == 'pooh' && auth.password == 'honey') {
|
||||
callback(null, { user: 'pooh' });
|
||||
} else {
|
||||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
};
|
||||
smtp = new smtpServer({ secure: true, authMethods: ['LOGIN'] });
|
||||
smtp.listen(port, function() {
|
||||
smtp.onAuth = function(auth, session, callback) {
|
||||
if (auth.username == 'pooh' && auth.password == 'honey') {
|
||||
callback(null, { user: 'pooh' });
|
||||
} else {
|
||||
return callback(new Error('invalid user / pass'));
|
||||
}
|
||||
};
|
||||
|
||||
server = email.server.connect({
|
||||
port: port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
server = email.server.connect({
|
||||
port: port,
|
||||
user: 'pooh',
|
||||
password: 'honey',
|
||||
ssl: true,
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
smtp.close(done);
|
||||
});
|
||||
after(function(done) {
|
||||
smtp.close(done);
|
||||
});
|
||||
|
||||
it('simple text message', function(done) {
|
||||
var message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: 'hello friend, i hope this message finds you well.',
|
||||
'message-id': 'this is a special id'
|
||||
};
|
||||
it('simple text message', function(done) {
|
||||
var message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: 'hello friend, i hope this message finds you well.',
|
||||
'message-id': 'this is a special id',
|
||||
};
|
||||
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
expect(mail.messageId).to.equal('<' + message['message-id'] + '>');
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
expect(mail.messageId).to.equal('<' + message['message-id'] + '>');
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('null text', function(done) {
|
||||
send(
|
||||
{
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: null,
|
||||
'message-id': 'this is a special id'
|
||||
},
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal('\n\n\n');
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
it('null text', function(done) {
|
||||
send(
|
||||
{
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: null,
|
||||
'message-id': 'this is a special id',
|
||||
},
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal('\n\n\n');
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('empty text', function(done) {
|
||||
send(
|
||||
{
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: '',
|
||||
'message-id': 'this is a special id'
|
||||
},
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal('\n\n\n');
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
it('empty text', function(done) {
|
||||
send(
|
||||
{
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'zelda@gmail.com',
|
||||
to: 'gannon@gmail.com',
|
||||
text: '',
|
||||
'message-id': 'this is a special id',
|
||||
},
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal('\n\n\n');
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('simple unicode text message', function(done) {
|
||||
var message = {
|
||||
subject: 'this ✓ is a test ✓ TEXT message from emailjs',
|
||||
from: 'zelda✓ <zelda@gmail.com>',
|
||||
to: 'gannon✓ <gannon@gmail.com>',
|
||||
text: 'hello ✓ friend, i hope this message finds you well.'
|
||||
};
|
||||
it('simple unicode text message', function(done) {
|
||||
var message = {
|
||||
subject: 'this ✓ is a test ✓ TEXT message from emailjs',
|
||||
from: 'zelda✓ <zelda@gmail.com>',
|
||||
to: 'gannon✓ <gannon@gmail.com>',
|
||||
text: 'hello ✓ friend, i hope this message finds you well.',
|
||||
};
|
||||
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('very large text message', function(done) {
|
||||
this.timeout(20000);
|
||||
// thanks to jart+loberstech for this one!
|
||||
var message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'ninjas@gmail.com',
|
||||
to: 'pirates@gmail.com',
|
||||
text: fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.txt'),
|
||||
'utf-8'
|
||||
)
|
||||
};
|
||||
it('very large text message', function(done) {
|
||||
this.timeout(20000);
|
||||
// thanks to jart+loberstech for this one!
|
||||
var message = {
|
||||
subject: 'this is a test TEXT message from emailjs',
|
||||
from: 'ninjas@gmail.com',
|
||||
to: 'pirates@gmail.com',
|
||||
text: fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.txt'),
|
||||
'utf-8'
|
||||
),
|
||||
};
|
||||
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text.replace(/\r/g, '') + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
email.message.create(message),
|
||||
function(mail) {
|
||||
expect(mail.text).to.equal(message.text.replace(/\r/g, '') + '\n\n\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('very large text data', function(done) {
|
||||
this.timeout(10000);
|
||||
var text =
|
||||
'<html><body><pre>' +
|
||||
fs.readFileSync(path.join(__dirname, 'attachments/smtp.txt'), 'utf-8') +
|
||||
'</pre></body></html>';
|
||||
var message = {
|
||||
subject: 'this is a test TEXT+DATA message from emailjs',
|
||||
from: 'lobsters@gmail.com',
|
||||
to: 'lizards@gmail.com',
|
||||
text:
|
||||
'hello friend if you are seeing this, you can not view html emails. it is attached inline.',
|
||||
attachment: { data: text, alternative: true }
|
||||
};
|
||||
it('very large text data', function(done) {
|
||||
this.timeout(10000);
|
||||
var text =
|
||||
'<html><body><pre>' +
|
||||
fs.readFileSync(path.join(__dirname, 'attachments/smtp.txt'), 'utf-8') +
|
||||
'</pre></body></html>';
|
||||
var message = {
|
||||
subject: 'this is a test TEXT+DATA message from emailjs',
|
||||
from: 'lobsters@gmail.com',
|
||||
to: 'lizards@gmail.com',
|
||||
text:
|
||||
'hello friend if you are seeing this, you can not view html emails. it is attached inline.',
|
||||
attachment: { data: text, alternative: true },
|
||||
};
|
||||
|
||||
send(
|
||||
message,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(text.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal(message.text + '\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
message,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(text.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal(message.text + '\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('html data', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var message = {
|
||||
subject: 'this is a test TEXT+HTML+DATA message from emailjs',
|
||||
from: 'obama@gmail.com',
|
||||
to: 'mitt@gmail.com',
|
||||
attachment: { data: html, alternative: true }
|
||||
};
|
||||
it('html data', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var message = {
|
||||
subject: 'this is a test TEXT+HTML+DATA message from emailjs',
|
||||
from: 'obama@gmail.com',
|
||||
to: 'mitt@gmail.com',
|
||||
attachment: { data: html, alternative: true },
|
||||
};
|
||||
|
||||
send(
|
||||
message,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
message,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(message.subject);
|
||||
expect(mail.from.text).to.equal(message.from);
|
||||
expect(mail.to.text).to.equal(message.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('html file', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+HTML+FILE message from emailjs',
|
||||
from: 'thomas@gmail.com',
|
||||
to: 'nikolas@gmail.com',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp.html'),
|
||||
alternative: true
|
||||
}
|
||||
};
|
||||
it('html file', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+HTML+FILE message from emailjs',
|
||||
from: 'thomas@gmail.com',
|
||||
to: 'nikolas@gmail.com',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp.html'),
|
||||
alternative: true,
|
||||
},
|
||||
};
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('html with image embed', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp2.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var image = fs.readFileSync(path.join(__dirname, 'attachments/smtp.gif'));
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+HTML+IMAGE message from emailjs',
|
||||
from: 'ninja@gmail.com',
|
||||
to: 'pirate@gmail.com',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp2.html'),
|
||||
alternative: true,
|
||||
related: [
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/smtp.gif'),
|
||||
type: 'image/gif',
|
||||
name: 'smtp-diagram.gif',
|
||||
headers: { 'Content-ID': '<smtp-diagram@local>' }
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
it('html with image embed', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp2.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var image = fs.readFileSync(path.join(__dirname, 'attachments/smtp.gif'));
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+HTML+IMAGE message from emailjs',
|
||||
from: 'ninja@gmail.com',
|
||||
to: 'pirate@gmail.com',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp2.html'),
|
||||
alternative: true,
|
||||
related: [
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/smtp.gif'),
|
||||
type: 'image/gif',
|
||||
name: 'smtp-diagram.gif',
|
||||
headers: { 'Content-ID': '<smtp-diagram@local>' },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
image.toString('base64')
|
||||
);
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
image.toString('base64')
|
||||
);
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('html data and attachment', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+HTML+FILE message from emailjs',
|
||||
from: 'thomas@gmail.com',
|
||||
to: 'nikolas@gmail.com',
|
||||
attachment: [
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/smtp.html'),
|
||||
alternative: true
|
||||
},
|
||||
{ path: path.join(__dirname, 'attachments/smtp.gif') }
|
||||
]
|
||||
};
|
||||
it('html data and attachment', function(done) {
|
||||
var html = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/smtp.html'),
|
||||
'utf-8'
|
||||
);
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+HTML+FILE message from emailjs',
|
||||
from: 'thomas@gmail.com',
|
||||
to: 'nikolas@gmail.com',
|
||||
attachment: [
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/smtp.html'),
|
||||
alternative: true,
|
||||
},
|
||||
{ path: path.join(__dirname, 'attachments/smtp.gif') },
|
||||
],
|
||||
};
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.html).to.equal(html.replace(/\r/g, ''));
|
||||
expect(mail.text).to.equal('\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('attachment', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+ATTACHMENT message from emailjs',
|
||||
from: 'washing@gmail.com',
|
||||
to: 'lincoln@gmail.com',
|
||||
text: 'hello friend, i hope this message and pdf finds you well.',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp.pdf'),
|
||||
type: 'application/pdf',
|
||||
name: 'smtp-info.pdf'
|
||||
}
|
||||
};
|
||||
it('attachment', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+ATTACHMENT message from emailjs',
|
||||
from: 'washing@gmail.com',
|
||||
to: 'lincoln@gmail.com',
|
||||
text: 'hello friend, i hope this message and pdf finds you well.',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp.pdf'),
|
||||
type: 'application/pdf',
|
||||
name: 'smtp-info.pdf',
|
||||
},
|
||||
};
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('attachment sent with unicode filename', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+ATTACHMENT message from emailjs',
|
||||
from: 'washing@gmail.com',
|
||||
to: 'lincoln@gmail.com',
|
||||
text: 'hello friend, i hope this message and pdf finds you well.',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp.pdf'),
|
||||
type: 'application/pdf',
|
||||
name: 'smtp-✓-info.pdf'
|
||||
}
|
||||
};
|
||||
it('attachment sent with unicode filename', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+ATTACHMENT message from emailjs',
|
||||
from: 'washing@gmail.com',
|
||||
to: 'lincoln@gmail.com',
|
||||
text: 'hello friend, i hope this message and pdf finds you well.',
|
||||
attachment: {
|
||||
path: path.join(__dirname, 'attachments/smtp.pdf'),
|
||||
type: 'application/pdf',
|
||||
name: 'smtp-✓-info.pdf',
|
||||
},
|
||||
};
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.attachments[0].filename).to.equal('smtp-✓-info.pdf');
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.attachments[0].filename).to.equal('smtp-✓-info.pdf');
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('attachments', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var tar = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz')
|
||||
);
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+2+ATTACHMENTS message from emailjs',
|
||||
from: 'sergey@gmail.com',
|
||||
to: 'jobs@gmail.com',
|
||||
text: 'hello friend, i hope this message and attachments finds you well.',
|
||||
attachment: [
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/smtp.pdf'),
|
||||
type: 'application/pdf',
|
||||
name: 'smtp-info.pdf'
|
||||
},
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz'),
|
||||
type: 'application/tar-gz',
|
||||
name: 'postfix.source.2.8.7.tar.gz'
|
||||
}
|
||||
]
|
||||
};
|
||||
it('attachments', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var tar = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz')
|
||||
);
|
||||
var headers = {
|
||||
subject: 'this is a test TEXT+2+ATTACHMENTS message from emailjs',
|
||||
from: 'sergey@gmail.com',
|
||||
to: 'jobs@gmail.com',
|
||||
text: 'hello friend, i hope this message and attachments finds you well.',
|
||||
attachment: [
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/smtp.pdf'),
|
||||
type: 'application/pdf',
|
||||
name: 'smtp-info.pdf',
|
||||
},
|
||||
{
|
||||
path: path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz'),
|
||||
type: 'application/tar-gz',
|
||||
name: 'postfix.source.2.8.7.tar.gz',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.attachments[1].content.toString('base64')).to.equal(
|
||||
tar.toString('base64')
|
||||
);
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.attachments[1].content.toString('base64')).to.equal(
|
||||
tar.toString('base64')
|
||||
);
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
|
||||
it('streams', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var tar = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz')
|
||||
);
|
||||
var stream = fs.createReadStream(
|
||||
path.join(__dirname, 'attachments/smtp.pdf')
|
||||
);
|
||||
var stream2 = fs.createReadStream(
|
||||
path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz')
|
||||
);
|
||||
var headers = {
|
||||
subject:
|
||||
'this is a test TEXT+2+STREAMED+ATTACHMENTS message from emailjs',
|
||||
from: 'stanford@gmail.com',
|
||||
to: 'mit@gmail.com',
|
||||
text:
|
||||
'hello friend, i hope this message and streamed attachments finds you well.',
|
||||
attachment: [
|
||||
{ stream: stream, type: 'application/pdf', name: 'smtp-info.pdf' },
|
||||
{
|
||||
stream: stream2,
|
||||
type: 'application/x-gzip',
|
||||
name: 'postfix.source.2.8.7.tar.gz'
|
||||
}
|
||||
]
|
||||
};
|
||||
it('streams', function(done) {
|
||||
var pdf = fs.readFileSync(path.join(__dirname, 'attachments/smtp.pdf'));
|
||||
var tar = fs.readFileSync(
|
||||
path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz')
|
||||
);
|
||||
var stream = fs.createReadStream(
|
||||
path.join(__dirname, 'attachments/smtp.pdf')
|
||||
);
|
||||
var stream2 = fs.createReadStream(
|
||||
path.join(__dirname, 'attachments/postfix-2.8.7.tar.gz')
|
||||
);
|
||||
var headers = {
|
||||
subject:
|
||||
'this is a test TEXT+2+STREAMED+ATTACHMENTS message from emailjs',
|
||||
from: 'stanford@gmail.com',
|
||||
to: 'mit@gmail.com',
|
||||
text:
|
||||
'hello friend, i hope this message and streamed attachments finds you well.',
|
||||
attachment: [
|
||||
{ stream: stream, type: 'application/pdf', name: 'smtp-info.pdf' },
|
||||
{
|
||||
stream: stream2,
|
||||
type: 'application/x-gzip',
|
||||
name: 'postfix.source.2.8.7.tar.gz',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
stream.pause();
|
||||
stream2.pause();
|
||||
stream.pause();
|
||||
stream2.pause();
|
||||
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.attachments[1].content.toString('base64')).to.equal(
|
||||
tar.toString('base64')
|
||||
);
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
send(
|
||||
headers,
|
||||
function(mail) {
|
||||
expect(mail.attachments[0].content.toString('base64')).to.equal(
|
||||
pdf.toString('base64')
|
||||
);
|
||||
expect(mail.attachments[1].content.toString('base64')).to.equal(
|
||||
tar.toString('base64')
|
||||
);
|
||||
expect(mail.text).to.equal(headers.text + '\n');
|
||||
expect(mail.subject).to.equal(headers.subject);
|
||||
expect(mail.from.text).to.equal(headers.from);
|
||||
expect(mail.to.text).to.equal(headers.to);
|
||||
},
|
||||
done
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,16 +15,17 @@ describe('Connect to wrong email server', function() {
|
|||
it('Should not call callback multiple times with wrong server configuration', function(done) {
|
||||
this.timeout(5000);
|
||||
const server = email.server.connect({ host: 'bar.baz' });
|
||||
server.send({
|
||||
from: 'foo@bar.baz',
|
||||
to: 'foo@bar.baz',
|
||||
subject: 'hello world',
|
||||
text: 'hello world',
|
||||
}, function(err) {
|
||||
assert.notEqual(err, null);
|
||||
done();
|
||||
});
|
||||
server.send(
|
||||
{
|
||||
from: 'foo@bar.baz',
|
||||
to: 'foo@bar.baz',
|
||||
subject: 'hello world',
|
||||
text: 'hello world',
|
||||
},
|
||||
function(err) {
|
||||
assert.notEqual(err, null);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue