refactor code to use modern language features & remove `new Buffer` calls

This commit is contained in:
Zack Schuster 2018-05-14 16:02:16 -07:00
parent affddaec47
commit 99c8f5262a
11 changed files with 1559 additions and 1574 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@
*.swo
*~
yarn.lock
package-lock.json

View File

@ -23,11 +23,10 @@
"bufferjs": "1.1.0"
},
"devDependencies": {
"mocha": "5.0.0",
"chai": "1.1.0",
"smtp-server": "^3.4.1",
"mailparser": "2.2.0",
"iconv": "2.2.1"
"mocha": "5.0.0",
"smtp-server": "^3.4.1"
},
"engine": [
"node >= 6"

View File

@ -1,209 +1,187 @@
var smtp = require('./smtp');
var smtpError = require('./error');
var message = require('./message');
var addressparser= require('addressparser');
const smtp = require('./smtp');
const smtpError = require('./error');
const message = require('./message');
const addressparser = require('addressparser');
var Client = function(server)
{
this.smtp = new smtp.SMTP(server);
//this.smtp.debug(1);
class Client {
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;
}
Client.prototype =
{
_poll: function()
{
var self = this;
_poll() {
clearTimeout(this.timer);
clearTimeout(self.timer);
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());
}
}
// 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);
}
if(self.queue.length)
{
if(self.smtp.state() == smtp.state.NOTCONNECTED)
self._connect(self.queue[0]);
else if(self.smtp.state() == smtp.state.CONNECTED && !self.sending && self.ready)
self._sendmail(self.queue.shift());
}
// wait around 1 seconds in case something does come in, otherwise close out SMTP connection if still open
else if(self.smtp.state() == smtp.state.CONNECTED)
self.timer = setTimeout(function() { self.smtp.quit(); }, 1000);
},
_connect: function(stack)
{
var self = this,
connect = function(err)
{
if(!err)
{
var begin = function(err)
{
if(!err)
{
self.ready = true;
self._poll();
}
else {
stack.callback(err, stack.message);
// clear out the queue so all callbacks can be called with the same error message
self.queue.shift();
self._poll();
}
};
if(!self.smtp.authorized())
self.smtp.login(begin);
else
self.smtp.ehlo_or_helo_if_needed(begin);
}
else {
_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
self.queue.shift();
self._poll();
}
};
this.queue.shift();
this._poll();
}
};
self.ready = false;
self.smtp.connect(connect);
},
if (!this.smtp.authorized()) this.smtp.login(begin);
else this.smtp.ehlo_or_helo_if_needed(begin);
} else {
stack.callback(err, stack.message);
send: function(msg, callback)
{
var self = this;
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(function(valid, why)
{
if(valid)
{
var stack =
{
message: msg,
to: addressparser(msg.header.to),
from: addressparser(msg.header.from)[0].address,
callback: callback || function() {}
};
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['return-path'] && addressparser(msg.header['return-path']).length)
stack.returnPath = addressparser(msg.header['return-path'])[0].address;
self.queue.push(stack);
self._poll();
}
else
callback(new Error(why), msg);
});
// clear out the queue so all callbacks can be called with the same error message
this.queue.shift();
this._poll();
}
else
callback(new Error("message is not a valid Message instance"), msg);
},
};
_containsInlinedHtml: function(attachment) {
if (Array.isArray(attachment)) {
return attachment.some((function(ctx) {
return function(att) {
return ctx._isAttachmentInlinedHtml(att);
};
})(this));
} else {
return this._isAttachmentInlinedHtml(attachment);
}
},
this.ready = false;
this.smtp.connect(connect);
}
_isAttachmentInlinedHtml: function(attachment) {
return attachment &&
(attachment.data || attachment.path) &&
attachment.alternative === true;
},
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);
_sendsmtp: function(stack, next)
{
var self = this;
var check= function(err)
{
if(!err && next)
{
next.apply(self, [stack]);
}
else
{
// if we snag on SMTP commands, call done, passing the error
// but first reset SMTP state so queue can continue polling
self.smtp.rset(function() { self._senddone(err, stack); });
}
};
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),
};
return check;
},
if (msg.header.cc)
stack.to = stack.to.concat(addressparser(msg.header.cc));
_sendmail: function(stack)
{
var self = this;
var from = stack.returnPath || stack.from;
self.sending = true;
self.smtp.mail(self._sendsmtp(stack, self._sendrcpt), '<' + from + '>');
},
if (msg.header.bcc)
stack.to = stack.to.concat(addressparser(msg.header.bcc));
_sendrcpt: function(stack)
{
var self = this, to = stack.to.shift().address;
self.smtp.rcpt(self._sendsmtp(stack, stack.to.length ? self._sendrcpt : self._senddata), '<'+ to +'>');
},
if (
msg.header['return-path'] &&
addressparser(msg.header['return-path']).length
)
stack.returnPath = addressparser(
msg.header['return-path']
)[0].address;
_senddata: function(stack)
{
var self = this;
self.smtp.data(self._sendsmtp(stack, self._sendmessage));
},
this.queue.push(stack);
this._poll();
} else {
callback(new Error(why), msg);
}
});
} else {
callback(new Error('message is not a valid Message instance'), msg);
};
}
_sendmessage: function(stack)
{
var self = this, stream = stack.message.stream();
_containsInlinedHtml(attachment) {
if (Array.isArray(attachment)) {
return attachment.some(() => {
return att => {
return this._isAttachmentInlinedHtml(att);
};
});
} else {
return this._isAttachmentInlinedHtml(attachment);
}
}
stream.on('data', function(data) { self.smtp.message(data); });
stream.on('end', function() { self.smtp.data_end(self._sendsmtp(stack, function() { self._senddone(null, stack) })); });
_isAttachmentInlinedHtml(attachment) {
return (
attachment &&
(attachment.data || attachment.path) &&
attachment.alternative === true
);
}
// 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', function(err) { self.smtp.close(); self._senddone(err, stack); });
},
_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));
}
};
}
_senddone: function(err, stack)
{
var self = this;
self.sending = false;
stack.callback(err, stack.message);
self._poll();
}
};
_sendmail(stack) {
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 + '>'
);
}
_senddata(stack) {
this.smtp.data(this._sendsmtp(stack, this._sendmessage));
}
_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))
);
});
// 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);
});
}
_senddone(err, stack) {
this.sending = false;
stack.callback(err, stack.message);
this._poll();
}
}
exports.Client = Client;
exports.connect = function(server)
{
return new Client(server);
};
exports.connect = server => new Client(server);

View File

@ -1,14 +1,19 @@
module.exports = function(message, code, error, smtp) {
var err = new Error((error && error.message) ? message + ' (' + error.message + ')' : message);
err.code = code;
if(error)
err.previous = error;
const err = new Error(
error && error.message ? `${message} (${error.message})` : message
);
err.code = code;
err.smtp = smtp;
if (error) {
err.previous = error;
}
return err;
};
module.exports.COULDNOTCONNECT = 1;
module.exports.COULDNOTCONNECT = 1;
module.exports.BADRESPONSE = 2;
module.exports.AUTHFAILED = 3;
module.exports.TIMEDOUT = 4;

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +1,68 @@
var SMTPError = require('./error');
const SMTPError = require('./error');
var SMTPResponse = function(stream, timeout, onerror)
{
var buffer = '',
class SMTPResponse {
constructor(stream, timeout, onerror) {
let buffer = '';
notify = function()
{
if(buffer.length)
{
// parse buffer for response codes
var line = buffer.replace("\r", '');
if(!line.trim().split(/\n/).pop().match(/^(\d{3})\s/))
return;
var match = line ? line.match(/(\d+)\s?(.*)/) : null;
const notify = () => {
if (buffer.length) {
// parse buffer for response codes
const line = buffer.replace('\r', '');
if (!line.trim().split(/\n/).pop().match(/^(\d{3})\s/)) {
return;
}
stream.emit('response', null, match ? {code:match[1], message:match[2], data:line} : {code:-1, data:line});
buffer = '';
}
},
const match = line ? line.match(/(\d+)\s?(.*)/) : null;
const data = match !== null
? { code: match[1], message: match[2], data: line }
: { code: -1, data: line };
error = function(err)
{
stream.emit('response', SMTPError('connection encountered an error', SMTPError.ERROR, err));
},
stream.emit('response', null, data);
buffer = '';
}
};
timedout = function(err)
{
stream.end();
stream.emit('response', SMTPError('timedout while connecting to smtp server', SMTPError.TIMEDOUT, err));
},
const error = (err) => {
stream.emit('response', SMTPError('connection encountered an error', SMTPError.ERROR, err));
}
watch = function(data)
{
//var data = stream.read();
if (data !== null) {
var decoded = data.toString();
var emit = false;
var code = 0;
const timedout = (err) => {
stream.end();
stream.emit('response', SMTPError('timedout while connecting to smtp server', SMTPError.TIMEDOUT, err));
}
buffer += decoded;
notify();
}
},
const watch = (data) => {
if (data !== null) {
buffer += data.toString();
notify();
}
};
close = function(err)
{
stream.emit('response', SMTPError('connection has closed', SMTPError.CONNECTIONCLOSED, err));
},
const close = (err) => {
stream.emit('response', SMTPError('connection has closed', SMTPError.CONNECTIONCLOSED, err));
};
end = function(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 = function(err) {
stream.removeAllListeners('response');
//stream.removeListener('readable', watch);
stream.removeListener('data', watch);
stream.removeListener('end', end);
stream.removeListener('close', close);
stream.removeListener('error', error);
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('readable', watch);
stream.on('data', watch);
stream.on('end', end);
stream.on('close', close);
stream.on('error', error);
stream.setTimeout(timeout, timedout);
};
stream.on('data', watch);
stream.on('end', end);
stream.on('close', close);
stream.on('error', error);
stream.setTimeout(timeout, timedout);
}
}
exports.monitor = function(stream, timeout, onerror)
{
return new SMTPResponse(stream, timeout, onerror);
};
exports.monitor = (stream, timeout, onerror) => new SMTPResponse(stream, timeout, onerror);

View File

@ -1,30 +1,30 @@
/*
* SMTP class written using python's (2.7) smtplib.py as a base
*/
var net = require('net');
var crypto = require('crypto');
var os = require('os');
var tls = require('tls');
var util = require('util');
var events = require('events');
const net = require('net');
const crypto = require('crypto');
const os = require('os');
const tls = require('tls');
const { EventEmitter } = require('events');
var SMTPResponse = require('./response');
var SMTPError = require('./error');
const SMTPResponse = require('./response');
const SMTPError = require('./error');
var SMTP_PORT = 25;
var SMTP_SSL_PORT = 465;
var SMTP_TLS_PORT = 587;
var CRLF = "\r\n";
var AUTH_METHODS = {
const SMTP_PORT = 25;
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'
};
var TIMEOUT = 5000;
var DEBUG = 0;
var log = function() {
const TIMEOUT = 5000;
let DEBUG = 0;
const log = function() {
if (DEBUG) {
Array.prototype.slice.call(arguments).forEach(function(d) {
console.log(d);
@ -32,7 +32,7 @@ var log = function() {
}
};
var quotedata = function(data) {
const quotedata = function(data) {
// Quote data for email.
// Double leading '.', and change Unix newline '\\n', or Mac '\\r' into
// Internet CRLF end-of-line.
@ -40,274 +40,268 @@ var quotedata = function(data) {
return data.replace(/(?:\r\n|\n|\r(?!\n))/g, CRLF).replace(/^\./gm, '..');
};
var caller = function(callback) {
const caller = function(callback) {
if (typeof(callback) == 'function') {
var args = Array.prototype.slice.call(arguments);
const args = Array.prototype.slice.call(arguments);
args.shift();
callback.apply(null, args);
}
};
var SMTPState = {
const SMTPState = {
NOTCONNECTED: 0,
CONNECTING: 1,
CONNECTED: 2
};
var SMTP = function(options) {
events.EventEmitter.call(this);
class SMTP extends EventEmitter {
constructor(options = {}) {
super();
options = options || {};
const {
timeout,
user,
password,
domain,
host,
port,
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);
this.sock = null;
this.timeout = options.timeout || TIMEOUT;
this.features = null;
this._state = SMTPState.NOTCONNECTED;
this._secure = false;
this.loggedin = (options.user && options.password) ? false : true;
this.domain = options.domain || os.hostname();
this.host = options.host || 'localhost';
this.port = options.port || (options.ssl ? SMTP_SSL_PORT : options.tls ? SMTP_TLS_PORT : SMTP_PORT);
this.ssl = options.ssl || false;
this.tls = options.tls || false;
this.monitor = null;
this.authentication = options.authentication || [AUTH_METHODS.CRAM_MD5, AUTH_METHODS.LOGIN, AUTH_METHODS.PLAIN, AUTH_METHODS.XOAUTH2];
this._state = SMTPState.NOTCONNECTED;
this._secure = false;
// keep these strings hidden when quicky debugging/logging
this.user = function() {
return options.user;
};
this.password = function() {
return options.password;
};
};
this.sock = null;
this.features = null;
this.monitor = null;
SMTP.prototype = {
debug: function(level) {
DEBUG = level;
},
this.authentication = authentication;
this.timeout = timeout;
this.domain = domain;
this.host = host;
this.ssl = ssl;
this.tls = tls;
state: function() {
this.port = port || (ssl ? SMTP_SSL_PORT : tls ? SMTP_TLS_PORT : SMTP_PORT);
this.loggedin = (user && password) ? false : true;
// keep these strings hidden when quicky debugging/logging
this.user = () => options.user;
this.password = () => options.password;
}
debug(level) {
DEBUG = level;
}
state() {
return this._state;
},
}
authorized: function() {
authorized() {
return this.loggedin;
},
}
connect: function(callback, port, host, options) {
connect(callback, port, host, options) {
options = options || {};
var self = this;
this.host = host || this.host;
this.port = port || this.port;
this.ssl = options.ssl || this.ssl;
self.host = host || self.host;
self.port = port || self.port;
self.ssl = options.ssl || self.ssl;
if (self._state != SMTPState.NOTCONNECTED) {
self.quit(function() {
self.connect(callback, port, host, options);
});
if (this._state != SMTPState.NOTCONNECTED) {
this.quit(() => this.connect(callback, port, host, options));
return;
}
var connected = function(err) {
const connected = (err) => {
if (!err) {
log("connected: " + self.host + ":" + self.port);
log(`connected: ${this.host}:${this.port}`);
if (self.ssl && !self.tls) {
if (this.ssl && !this.tls) {
// if key/ca/cert was passed in, check if connection is authorized
if (typeof(self.ssl) != 'boolean' && !self.sock.authorized) {
self.close(true);
caller(callback, SMTPError('could not establish an ssl connection', SMTPError.CONNECTIONAUTH, err));
} else self._secure = true;
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 {
this._secure = true;
}
}
} else {
self.close(true);
caller(callback, SMTPError("could not connect", SMTPError.COULDNOTCONNECT, err));
this.close(true);
caller(callback, SMTPError('could not connect', SMTPError.COULDNOTCONNECT, err));
}
};
var response = function(err, msg) {
const response = (err, msg) => {
if (err) {
if (self._state === SMTPState.NOTCONNECTED && !self.sock) {
if (this._state === SMTPState.NOTCONNECTED && !this.sock) {
return;
}
self.close(true);
this.close(true);
caller(callback, err);
} else if (msg.code == '220') {
log(msg.data);
// might happen first, so no need to wait on connected()
self._state = SMTPState.CONNECTED;
this._state = SMTPState.CONNECTED;
caller(callback, null, msg.data);
} else {
log("response (data): " + msg.data);
self.quit(function() {
caller(callback, SMTPError("bad response on connection", SMTPError.BADRESPONSE, err, msg.data));
log(`response (data): ${msg.data}`);
this.quit(() => {
const err = SMTPError('bad response on connection', SMTPError.BADRESPONSE, err, msg.data);
caller(callback, err);
});
}
};
self._state = SMTPState.CONNECTING;
log("connecting: " + self.host + ":" + self.port);
this._state = SMTPState.CONNECTING;
log(`connecting: ${this.host}:${this.port}`);
if (self.ssl) {
self.sock = tls.connect(self.port, self.host, self.ssl, connected);
if (this.ssl) {
this.sock = tls.connect(this.port, this.host, this.ssl, connected);
} else {
self.sock = new net.Socket();
self.sock.connect(self.port, self.host, connected);
this.sock = new net.Socket();
this.sock.connect(this.port, this.host, connected);
}
self.monitor = SMTPResponse.monitor(self.sock, self.timeout, function() {
self.close(true);
});
self.sock.once('response', response);
self.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: function(str, callback) {
var self = this;
if (self.sock && self._state == SMTPState.CONNECTED) {
send(str, callback) {
if (this.sock && this._state == SMTPState.CONNECTED) {
log(str);
var response = function(err, msg) {
this.sock.once('response', (err, msg) => {
if (err) {
caller(callback, err);
} else {
log(msg.data);
caller(callback, null, msg);
}
};
self.sock.once('response', response);
self.sock.write(str);
});
this.sock.write(str);
} else {
self.close(true);
this.close(true);
caller(callback, SMTPError('no connection has been established', SMTPError.NOCONNECTION));
}
},
}
command: function(cmd, callback, codes, failed) {
command(cmd, callback, codes, failed) {
codes = Array.isArray(codes) ? codes : typeof(codes) == 'number' ? [codes] : [250];
var response = function(err, msg) {
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 {
var errorMessage = "bad response on command '" + cmd.split(' ')[0] + "'";
if (msg.message) {
errorMessage += ': ' + msg.message;
}
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));
}
}
};
this.send(cmd + CRLF, response);
},
}
helo: function(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) => {
if (err) {
caller(callback, err);
} else {
this.parse_smtp_features(data);
caller(callback, err, data);
}
});
}
var self = this,
response = function(err, data) {
starttls(callback) {
const response = (err, msg) => {
if (err) {
caller(callback, err);
} else {
self.parse_smtp_features(data);
caller(callback, err, data);
}
};
this.command("helo " + (domain || this.domain), response);
},
starttls: function(callback) {
var self = this,
response = function(err, msg) {
if (err) {
err.message += " while establishing a starttls session";
err.message += ' while establishing a starttls session';
caller(callback, err);
} else {
// support new API
if (tls.TLSSocket) {
var secured_socket = new tls.TLSSocket(self.sock, {
secureContext: tls.createSecureContext ? tls.createSecureContext(self.tls) : crypto.createCredentials(self.tls),
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', function(err) {
self.close(true);
secured_socket.on('error', (err) => {
this.close(true);
caller(callback, err);
});
self._secure = true;
self.sock = secured_socket;
this._secure = true;
this.sock = secured_socket;
SMTPResponse.monitor(self.sock, self.timeout, function() {
self.close(true);
});
SMTPResponse.monitor(this.sock, this.timeout, () => this.close(true));
caller(callback, msg.data);
} else {
var secured_socket = null;
var secured = function() {
self._secure = true;
self.sock = secured_socket;
const secured_socket = null;
const secured = () => {
this._secure = true;
this.sock = secured_socket;
var error = function(err) {
self.close(true);
caller(callback, err);
};
SMTPResponse.monitor(self.sock, self.timeout, function() {
self.close(true);
});
SMTPResponse.monitor(this.sock, this.timeout, () => this.close(true));
caller(callback, msg.data);
};
//secured_socket = starttls.secure(self.sock, self.tls, secured);
var starttls = require('starttls');
const starttls = require('starttls');
secured_socket = starttls({
socket: self.sock,
host: self.host,
port: self.port,
socket: this.sock,
host: this.host,
port: this.port,
pair: tls.createSecurePair(
tls.createSecureContext ? tls.createSecureContext(self.tls) : crypto.createCredentials(self.tls),
tls.createSecureContext ? tls.createSecureContext(this.tls) : crypto.createCredentials(this.tls),
false)
}, secured).cleartext;
secured_socket.on('error', function(err) {
self.close(true);
secured_socket.on('error', (err) => {
this.close(true);
caller(callback, err);
});
}
}
};
this.command("starttls", response, [220]);
},
parse_smtp_features: function(data) {
var self = this;
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
data.split("\n").forEach(function(ext) {
var 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,
@ -321,238 +315,230 @@ SMTP.prototype = {
// 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.
self.features[parse[1].toLowerCase()] = parse[2] || true;
this.features[parse[1].toLowerCase()] = parse[2] || true;
}
});
return;
},
ehlo: function(callback, domain) {
var self = this,
response = function(err, data) {
if (err) {
caller(callback, err);
} else {
self.parse_smtp_features(data);
if (self.tls && !self._secure) {
self.starttls(function() {
self.ehlo(callback, domain);
});
} else {
caller(callback, err, data);
}
}
};
}
ehlo(callback, domain) {
this.features = {};
this.command("ehlo " + (domain || this.domain), response);
},
this.command(`ehlo ${domain || this.domain}`, (err, data) => {
if (err) {
caller(callback, err);
} else {
this.parse_smtp_features(data);
has_extn: function(opt) {
if (this.tls && !this._secure) {
this.starttls(() => this.ehlo(callback, domain));
} else {
caller(callback, err, data);
}
}
});
}
has_extn(opt) {
return this.features[opt.toLowerCase()] === undefined;
},
}
help: function(callback, args) {
help(callback, args) {
// SMTP 'help' command, returns text from the server
this.command(args ? "help " + args : "help", callback, [211, 214]);
},
this.command(args ? `help ${args}` : 'help', callback, [211, 214]);
}
rset: function(callback) {
this.command("rset", callback);
},
rset(callback) {
this.command('rset', callback);
}
noop: function(callback) {
this.send("noop", callback);
},
noop(callback) {
this.send('noop', callback);
}
mail: function(callback, from) {
this.command("mail FROM:" + from, callback);
},
mail(callback, from) {
this.command(`mail FROM:${from}`, callback);
}
rcpt: function(callback, to) {
this.command("RCPT TO:" + to, callback, [250, 251]);
},
rcpt(callback, to) {
this.command(`RCPT TO:${to}`, callback, [250, 251]);
}
data: function(callback) {
this.command("data", callback, [354]);
},
data(callback) {
this.command('data', callback, [354]);
}
data_end: function(callback) {
this.command(CRLF + ".", callback);
},
data_end(callback) {
this.command(`${CRLF}.`, callback);
}
message: function(data) {
message(data) {
log(data);
this.sock.write(data);
},
}
verify: function(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: function(address, callback) {
expn(address, callback) {
// SMTP 'expn' command -- expands a mailing list.
this.command("expn " + address, callback);
},
this.command(`expn ${address}`, callback);
}
ehlo_or_helo_if_needed: function(callback, domain) {
// Call self.ehlo() and/or self.helo() if needed.
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.
var self = this;
if (!this.features) {
var response = function(err, data) {
caller(callback, err, data);
};
var attempt = function(err, data) {
if (err) self.helo(response, domain);
else caller(callback, err, data);
};
self.ehlo(attempt, domain);
}
},
login: function(callback, user, password, options) {
var self = this,
login = {
user: user ? function() {
return user;
} : self.user,
password: password ? function() {
return password;
} : self.password,
method: options && options.method ? options.method.toUpperCase() : ''
},
domain = options && options.domain ? options.domain : this.domain,
initiate = function(err, data) {
const response = (err, data) => caller(callback, err, data);
this.ehlo((err, data) => {
if (err) {
caller(callback, err);
return;
}
this.helo(response, domain);
} else {
caller(callback, err, data);
}
}, domain);
}
}
/*
* Log in on an SMTP server that requires authentication.
*
* The arguments are:
* - user: The user name to authenticate with.
* - password: The password for the authentication.
*
* If there has been no previous EHLO or HELO command self session, self
* method tries ESMTP EHLO first.
*
* This method will return normally if the authentication was successful.
*/
login(callback, user, password, options) {
const login = {
user: user ? () => user : this.user,
password: password ? () => password : this.password,
method: options && options.method ? options.method.toUpperCase() : '',
};
var method = null,
const domain = options && options.domain ? options.domain : this.domain;
encode_cram_md5 = function(challenge) {
challenge = (new Buffer(challenge, "base64")).toString("ascii");
const initiate = (err, data) => {
if (err) {
caller(callback, err);
return;
}
var hmac = crypto.createHmac('md5', login.password());
hmac.update(challenge);
/*
* Log in on an SMTP server that requires authentication.
*
* The arguments are:
* - user: The user name to authenticate with.
* - password: The password for the authentication.
*
* If there has been no previous EHLO or HELO command self session, self
* method tries ESMTP EHLO first.
*
* This method will return normally if the authentication was successful.
*/
return (new Buffer(login.user() + " " + hmac.digest('hex')).toString("base64"));
},
let method = null;
encode_plain = function() {
return (new Buffer("\u0000" + login.user() + "\u0000" + login.password())).toString("base64");
},
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');
};
encode_xoauth2 = function() {
// console.log("user=" + login.user() + "\1auth=Bearer " + login.password()+"\1\1");
// see: https://developers.google.com/gmail/xoauth2_protocol
return (new Buffer("user=" + login.user() + "\u0001auth=Bearer " + login.password() + "\u0001\u0001")).toString("base64");
};
const encode_plain = () =>
Buffer
.from(`\u0000${login.user()}\u0000${login.password()}`)
.toString('base64');
// List of authentication methods we support: from preferred to
// less preferred methods.
if (!method) {
var preferred = self.authentication;
var auth = "";
// see: https://developers.google.com/gmail/xoauth2_protocol
const encode_xoauth2 = () =>
Buffer
.from(`user=${login.user()}\u0001auth=Bearer ${login.password()}\u0001\u0001`)
.toString('base64');
if (self.features && self.features.auth) {
if (typeof(self.features.auth) === 'string') {
auth = self.features.auth;
}
}
// List of authentication methods we support: from preferred to
// less preferred methods.
if (!method) {
const preferred = this.authentication;
let auth = '';
for (var i = 0; i < preferred.length; i++) {
if (auth.indexOf(preferred[i]) != -1) {
method = preferred[i];
break;
}
}
}
if (this.features && this.features.auth) {
if (typeof(this.features.auth) === 'string') {
auth = this.features.auth;
}
}
// handle bad responses from command differently
var failed = function(err, data) {
self.loggedin = false;
self.close(); // if auth is bad, close the connection, it won't get better by itself
caller(callback, SMTPError('authorization.failed', SMTPError.AUTHFAILED, err, data));
};
for (let i = 0; i < preferred.length; i++) {
if (auth.includes(preferred[i])) {
method = preferred[i];
break;
}
}
}
var response = function(err, data) {
if (err) {
failed(err, data);
} else {
self.loggedin = true;
caller(callback, err, data);
}
};
// handle bad responses from command differently
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));
};
var attempt = function(err, data, msg) {
if (err) {
failed(err, data);
} else {
if (method == AUTH_METHODS.CRAM_MD5) {
self.command(encode_cram_md5(msg), response, [235, 503]);
} else if (method == AUTH_METHODS.LOGIN) {
self.command((new Buffer(login.password())).toString("base64"), response, [235, 503]);
}
}
};
const response = (err, data) => {
if (err) {
failed(err, data);
} else {
this.loggedin = true;
caller(callback, err, data);
}
};
var attempt_user = function(err, data, msg) {
if (err) {
failed(err, data);
} else {
if (method == AUTH_METHODS.LOGIN) {
self.command((new Buffer(login.user())).toString("base64"), attempt, [334]);
}
}
};
const attempt = (err, data, msg) => {
if (err) {
failed(err, data);
} else {
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]);
}
}
};
if (method == AUTH_METHODS.CRAM_MD5) self.command("AUTH " + AUTH_METHODS.CRAM_MD5, attempt, [334]);
const attempt_user = (err, data, msg) => {
if (err) {
failed(err, data);
} else {
if (method == AUTH_METHODS.LOGIN) {
this.command(Buffer.from(login.user()).toString('base64'), attempt, [334]);
}
}
};
else if (method == AUTH_METHODS.LOGIN) self.command("AUTH " + AUTH_METHODS.LOGIN, attempt_user, [334]);
switch (method) {
case AUTH_METHODS.CRAM_MD5:
this.command(`AUTH ${AUTH_METHODS.CRAM_MD5}`, attempt, [334]);
break;
case AUTH_METHODS.LOGIN:
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]);
break;
case AUTH_METHODS.XOAUTH2:
this.command(`AUTH ${AUTH_METHODS.XOAUTH2} ${encode_xoauth2(login.user(), login.password())}`, response, [235, 503]);
break;
default:
const msg = 'no form of authorization supported';
const err = SMTPError(msg, SMTPError.AUTHNOTSUPPORTED, null, data);
caller(callback, err);
break;
}
};
else if (method == AUTH_METHODS.PLAIN) self.command("AUTH " + AUTH_METHODS.PLAIN + " " + encode_plain(login.user(), login.password()), response, [235, 503]);
this.ehlo_or_helo_if_needed(initiate, domain);
}
else if (method == AUTH_METHODS.XOAUTH2) self.command("AUTH " + AUTH_METHODS.XOAUTH2 + " " + encode_xoauth2(login.user(), login.password()), response, [235, 503]);
else if (!method) caller(callback, SMTPError('no form of authorization supported', SMTPError.AUTHNOTSUPPORTED, null, data));
};
self.ehlo_or_helo_if_needed(initiate, domain);
},
close: function(force) {
close(force) {
if (this.sock) {
if (force) {
log("smtp connection destroyed!");
log('smtp connection destroyed!');
this.sock.destroy();
} else {
log("smtp connection closed.");
log('smtp connection closed.');
this.sock.end();
}
}
@ -567,21 +553,14 @@ SMTP.prototype = {
this.sock = null;
this.features = null;
this.loggedin = !(this.user() && this.password());
},
quit: function(callback) {
var self = this,
response = function(err, data) {
caller(callback, err, data);
self.close();
};
this.command("quit", response, [221, 250]);
}
};
for (var each in events.EventEmitter.prototype) {
SMTP.prototype[each] = events.EventEmitter.prototype[each];
quit(callback) {
this.command('quit', (err, data) => {
caller(callback, err, data);
this.close();
}, [221, 250]);
}
}
exports.SMTP = SMTP;

View File

@ -1,60 +1,72 @@
describe("authorize plain", function() {
var parser = require('mailparser').simpleParser;
var smtpServer = require("smtp-server").SMTPServer;
var expect = require("chai").expect;
var fs = require("fs");
var os = require("os");
var path = require('path');
var email = require('../email');
var port = 2526;
var server = null;
var smtp = null;
describe('authorize plain', function() {
const { simpleParser: parser } = require('mailparser');
const { SMTPServer: smtpServer } = require('smtp-server');
const { expect } = require('chai');
const email = require('../email');
const port = 2526;
var send = function(message, verify, done) {
smtp.onData = function(stream, session, callback) {
parser(stream).then(verify).then(done).catch(done);
stream.on('end', callback);
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.send(message, function(err) { if (err) throw err; });
}
server = email.server.connect({
port: port,
user: 'pooh',
password: 'honey',
ssl: true
});
before(function(done) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // prevent CERT_HAS_EXPIRED errors
done();
});
});
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"));
}
}
after(function(done) {
smtp.close(done);
});
server = email.server.connect({port:port, user:"pooh", password:"honey", ssl:true});
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.'
};
after(function(done) {
smtp.close(done);
});
const created = email.message.create(message);
it("login", function(done) {
var 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."
};
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);
});
const callback = 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);
};
send(created, callback, done);
});
});

View File

@ -1,59 +1,71 @@
describe("authorize ssl", function() {
var parser = require('mailparser').simpleParser;
var smtpServer = require('smtp-server').SMTPServer;
var expect = require("chai").expect;
var fs = require("fs");
var os = require("os");
var path = require('path');
var email = require('../email');
var port = 2526;
var server = null;
var smtp = null;
describe('authorize ssl', function() {
const { simpleParser: parser } = require('mailparser');
const { SMTPServer: smtpServer } = require('smtp-server');
const { expect } = require('chai');
const email = require('../email');
const port = 2526;
var send = function(message, verify, done) {
smtp.onData = function(stream, session, callback) {
parser(stream).then(verify).then(done).catch(done);
stream.on('end', callback);
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.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();
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) {
var 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
);
});
});

View File

@ -1,329 +1,455 @@
describe("messages", function() {
var parser = require('mailparser').simpleParser;
var smtpServer = require('smtp-server').SMTPServer;
var expect = require("chai").expect;
var fs = require("fs");
var os = require("os");
var path = require('path');
var email = require('../email');
var port = 2526;
var server = null;
var smtp = null;
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;
var 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);
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);
};
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.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();
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('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 =
it('null text', function(done) {
send(
{
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."
};
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
);
});
send(email.message.create(message), function(mail)
it('empty text', function(done) {
send(
{
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);
});
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("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('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 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 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(message, function(mail) {
expect(mail.html).to.equal(text);
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(
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("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('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(html);
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(text);
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 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 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(headers, function(mail) {
expect(mail.html).to.equal(html);
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(
message,
function(mail) {
expect(mail.html).to.equal(html);
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 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 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.attachments[0].content.toString("base64")).to.equal(image.toString("base64"));
expect(mail.html).to.equal(html);
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);
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 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.html).to.equal(html);
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);
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('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.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.html).to.equal(html);
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 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', 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.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('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[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[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("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('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'
}
]
};
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);
});
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();
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
);
});
});

View File

@ -1,9 +1,9 @@
var path = require('path');
var assert = require('assert');
const path = require('path');
const assert = require('assert');
describe("Connect to wrong email server", function() {
var emailModulePath = require.resolve(path.join(__dirname, '..', 'email'));
var email;
describe('Connect to wrong email server', function() {
const emailModulePath = require.resolve(path.join(__dirname, '..', 'email'));
let email;
beforeEach(function() {
if (require.cache[emailModulePath]) {
@ -12,14 +12,14 @@ describe("Connect to wrong email server", function() {
email = require('../email');
});
it("Should not call callback multiple times with wrong server configuration", function(done) {
it('Should not call callback multiple times with wrong server configuration', function(done) {
this.timeout(5000);
var server = email.server.connect({ host: "bar.baz" });
const server = email.server.connect({ host: 'bar.baz' });
server.send({
from: "foo@bar.baz",
to: "foo@bar.baz",
subject: "hello world",
text: "hello world",
from: 'foo@bar.baz',
to: 'foo@bar.baz',
subject: 'hello world',
text: 'hello world',
}, function(err) {
assert.notEqual(err, null);
done();