Socket
Socket
Sign inDemoInstall

nodemailer

Package Overview
Dependencies
Maintainers
1
Versions
271
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nodemailer - npm Package Compare versions

Comparing version 4.0.1 to 4.1.0

package-lock.json.2512357107

4

CHANGELOG.md
# CHANGELOG
## v4.1.0 2017-08-28
- Added new methods `createTestAccount` and `getTestMessageUrl` to use autogenerated email accounts from https://Ethereal.email
## v4.0.1 2017-04-13

@@ -4,0 +8,0 @@

8

lib/addressparser/index.js

@@ -76,3 +76,3 @@ 'use strict';

let _regexHandler = function (address) {
let _regexHandler = function(address) {
if (!data.address.length) {

@@ -127,3 +127,2 @@ data.address = [address.trim()];

}
}

@@ -172,4 +171,2 @@

/**

@@ -181,3 +178,4 @@ * Tokenizes the original input string

tokenize() {
let chr, list = [];
let chr,
list = [];
for (let i = 0, len = this.str.length; i < len; i++) {

@@ -184,0 +182,0 @@ chr = this.str.charAt(i);

@@ -201,7 +201,9 @@ 'use strict';

this.options = options || {};
this.keys = [].concat(this.options.keys || {
domainName: options.domainName,
keySelector: options.keySelector,
privateKey: options.privateKey
});
this.keys = [].concat(
this.options.keys || {
domainName: options.domainName,
keySelector: options.keySelector,
privateKey: options.privateKey
}
);
}

@@ -208,0 +210,0 @@

@@ -63,6 +63,6 @@ 'use strict';

}
if (chr === 0x0A && i) {
if (chr === 0x0a && i) {
let pr1 = i - 1 < lblen ? this.lastBytes[i - 1] : data[i - 1 - lblen];
let pr2 = i > 1 ? (i - 2 < lblen ? this.lastBytes[i - 2] : data[i - 2 - lblen]) : false;
if (pr1 === 0x0A) {
if (pr1 === 0x0a) {
this.headersParsed = true;

@@ -72,3 +72,3 @@ headerPos = i - lblen + 1;

break;
} else if (pr1 === 0x0D && pr2 === 0x0A) {
} else if (pr1 === 0x0d && pr2 === 0x0a) {
this.headersParsed = true;

@@ -75,0 +75,0 @@ headerPos = i - lblen + 1;

@@ -28,3 +28,2 @@ 'use strict';

// This crux finds and removes the spaces from the last line and the newline characters after the last non-empty line

@@ -36,3 +35,3 @@ // If we get another chunk that does not match this description then we can restore the previously processed data

if (state === 'file' && (c === 0x0A || c === 0x0D)) {
if (state === 'file' && (c === 0x0a || c === 0x0d)) {
// do nothing, found \n or \r at the end of chunk, stil end of file

@@ -56,4 +55,6 @@ } else if (state === 'file' && (c === 0x09 || c === 0x20)) {

// and if the remainder also matches
if ((state === 'file' && (!this.remainder || /[\r\n]$/.test(this.remainder))) ||
(state === 'line' && (!this.remainder || /[ \t]$/.test(this.remainder)))) {
if (
(state === 'file' && (!this.remainder || /[\r\n]$/.test(this.remainder))) ||
(state === 'line' && (!this.remainder || /[ \t]$/.test(this.remainder)))
) {
// keep everything

@@ -84,7 +85,7 @@ this.remainder += chunk.toString('binary');

for (let i = 0, len = chunk.length; i < len; i++) {
if (i && chunk[i] === 0x0A && chunk[i - 1] !== 0x0D) {
if (i && chunk[i] === 0x0a && chunk[i - 1] !== 0x0d) {
// missing \r before \n
needsFixing = true;
break;
} else if (i && chunk[i] === 0x0D && chunk[i - 1] === 0x20) {
} else if (i && chunk[i] === 0x0d && chunk[i - 1] === 0x20) {
// trailing WSP found

@@ -108,5 +109,6 @@ needsFixing = true;

this.remainder = nextRemainder;
bodyStr = bodyStr.replace(/\r?\n/g, '\n') // use js line endings
.replace(/[ \t]*$/mg, '') // remove line endings, rtrim
.replace(/[ \t]+/mg, ' ') // single spaces
bodyStr = bodyStr
.replace(/\r?\n/g, '\n') // use js line endings
.replace(/[ \t]*$/gm, '') // remove line endings, rtrim
.replace(/[ \t]+/gm, ' ') // single spaces
.replace(/\n/g, '\r\n'); // restore rfc822 line endings

@@ -113,0 +115,0 @@ chunk = Buffer.from(bodyStr, 'binary');

@@ -23,3 +23,4 @@ 'use strict';

// all listed fields from RFC4871 #5.5
let defaultFieldNames = 'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
let defaultFieldNames =
'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
'Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:' +

@@ -26,0 +27,0 @@ 'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +

@@ -39,5 +39,5 @@ 'use strict';

urlparts.hostname.length < domain.length ||
// prefix domains with dot to be sure that partial matches are not used
('.' + urlparts.hostname).substr(-domain.length + 1) !== ('.' + domain)) {
('.' + urlparts.hostname).substr(-domain.length + 1) !== '.' + domain
) {
cookie.domain = urlparts.hostname;

@@ -68,4 +68,3 @@ }

get(url) {
return this.list(url).map(cookie =>
cookie.name + '=' + cookie.value).join('; ');
return this.list(url).map(cookie => cookie.name + '=' + cookie.value).join('; ');
}

@@ -121,3 +120,2 @@

switch (key) {
case 'expires':

@@ -178,3 +176,6 @@ value = new Date(value);

// .foo.com also matches subdomains, foo.com does not
if (urlparts.hostname !== cookie.domain && (cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
if (
urlparts.hostname !== cookie.domain &&
(cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)
) {
return false;

@@ -214,3 +215,2 @@ }

if (this.compare(this.cookies[i], cookie)) {
// check if the cookie needs to be removed instead

@@ -217,0 +217,0 @@ if (this.isExpired(cookie)) {

@@ -13,3 +13,3 @@ 'use strict';

module.exports = function (url, options) {
module.exports = function(url, options) {
return fetch(url, options);

@@ -87,6 +87,10 @@ };

} else if (typeof options.body === 'object') {
body = new Buffer(Object.keys(options.body).map(key => {
let value = options.body[key].toString().trim();
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}).join('&'));
body = new Buffer(
Object.keys(options.body)
.map(key => {
let value = options.body[key].toString().trim();
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
})
.join('&')
);
} else {

@@ -108,3 +112,3 @@ body = new Buffer(options.body.toString().trim());

path: parsed.path,
port: parsed.port ? parsed.port : (parsed.protocol === 'https:' ? 443 : 80),
port: parsed.port ? parsed.port : parsed.protocol === 'https:' ? 443 : 80,
headers,

@@ -111,0 +115,0 @@ rejectUnauthorized: false,

@@ -49,6 +49,11 @@ 'use strict';

}
this.logger.info({
tnx: 'send',
messageId
}, 'Composing JSON structure of %s to <%s>', messageId, recipients.join(', '));
this.logger.info(
{
tnx: 'send',
messageId
},
'Composing JSON structure of %s to <%s>',
messageId,
recipients.join(', ')
);

@@ -58,7 +63,12 @@ setImmediate(() => {

if (err) {
this.logger.error({
err,
tnx: 'send',
messageId
}, 'Failed building JSON structure for %s. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'send',
messageId
},
'Failed building JSON structure for %s. %s',
messageId,
err.message
);
return done(err);

@@ -65,0 +75,0 @@ }

@@ -42,6 +42,9 @@ /* eslint no-undefined: 0 */

} else {
this.message = this._createContentNode(false, [].concat(this._alternatives || []).concat(this._attachments.attached || []).shift() || {
contentType: 'text/plain',
content: ''
});
this.message = this._createContentNode(
false,
[].concat(this._alternatives || []).concat(this._attachments.attached || []).shift() || {
contentType: 'text/plain',
content: ''
}
);
}

@@ -55,15 +58,3 @@

// Add headers to the root node, always overrides custom headers
[
'from',
'sender',
'to',
'cc',
'bcc',
'reply-to',
'in-reply-to',
'references',
'subject',
'message-id',
'date'
].forEach(header => {
['from', 'sender', 'to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'message-id', 'date'].forEach(header => {
let key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());

@@ -103,4 +94,3 @@ if (this.mail[key]) {

data = {
contentType: attachment.contentType ||
mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin'),
contentType: attachment.contentType || mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin'),
contentDisposition: attachment.contentDisposition || (isMessageNode ? 'inline' : 'attachment'),

@@ -154,3 +144,6 @@ contentTransferEncoding: attachment.contentTransferEncoding

if (this.mail.icalEvent) {
if (typeof this.mail.icalEvent === 'object' && (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)) {
if (
typeof this.mail.icalEvent === 'object' &&
(this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
) {
icalEvent = this.mail.icalEvent;

@@ -197,3 +190,7 @@ } else {

let alternatives = [],
text, html, watchHtml, icalEvent, eventObject;
text,
html,
watchHtml,
icalEvent,
eventObject;

@@ -212,3 +209,6 @@ if (this.mail.text) {

if (this.mail.watchHtml) {
if (typeof this.mail.watchHtml === 'object' && (this.mail.watchHtml.content || this.mail.watchHtml.path || this.mail.watchHtml.href || this.mail.watchHtml.raw)) {
if (
typeof this.mail.watchHtml === 'object' &&
(this.mail.watchHtml.content || this.mail.watchHtml.path || this.mail.watchHtml.href || this.mail.watchHtml.raw)
) {
watchHtml = this.mail.watchHtml;

@@ -226,3 +226,6 @@ } else {

if (this.mail.icalEvent && !(this.mail.attachments && this.mail.attachments.length)) {
if (typeof this.mail.icalEvent === 'object' && (this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)) {
if (
typeof this.mail.icalEvent === 'object' &&
(this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
) {
icalEvent = this.mail.icalEvent;

@@ -264,9 +267,3 @@ } else {

[].
concat(text || []).
concat(watchHtml || []).
concat(html || []).
concat(eventObject || []).
concat(this.mail.alternatives || []).
forEach(alternative => {
[].concat(text || []).concat(watchHtml || []).concat(html || []).concat(eventObject || []).concat(this.mail.alternatives || []).forEach(alternative => {
let data;

@@ -279,4 +276,3 @@

data = {
contentType: alternative.contentType ||
mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
contentType: alternative.contentType || mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
contentTransferEncoding: alternative.contentTransferEncoding

@@ -353,3 +349,3 @@ };

[].concat(!this._useAlternative && this._alternatives || []).concat(this._attachments.attached || []).forEach(element => {
[].concat((!this._useAlternative && this._alternatives) || []).concat(this._attachments.attached || []).forEach(element => {
// if the element is a html node from related subpart then ignore it

@@ -443,6 +439,3 @@ if (!this._useRelated || element !== this._htmlNode) {

let node;
let encoding = (element.encoding || 'utf8')
.toString()
.toLowerCase()
.replace(/[-_\s]/g, '');
let encoding = (element.encoding || 'utf8').toString().toLowerCase().replace(/[-_\s]/g, '');

@@ -522,3 +515,3 @@ if (!parentNode) {

parts[1].split(';').forEach(item => {
if (/^\w+\/[^\/]+$/i.test(item)) {
if (/^\w+\/[^/]+$/i.test(item)) {
element.contentType = element.contentType || item.toLowerCase();

@@ -525,0 +518,0 @@ }

@@ -51,14 +51,22 @@ 'use strict';

this.logger.debug({
tnx: 'create'
}, 'Creating transport: %s', this.getVersionString());
this.logger.debug(
{
tnx: 'create'
},
'Creating transport: %s',
this.getVersionString()
);
// setup emit handlers for the transporter
if (typeof transporter.on === 'function') {
// deprecated log interface
this.transporter.on('log', log => {
this.logger.debug({
tnx: 'transport'
}, '%s: %s', log.type, log.message);
this.logger.debug(
{
tnx: 'transport'
},
'%s: %s',
log.type,
log.message
);
});

@@ -68,6 +76,10 @@

this.transporter.on('error', err => {
this.logger.error({
err,
tnx: 'transport'
}, 'Transport Error: %s', err.message);
this.logger.error(
{
err,
tnx: 'transport'
},
'Transport Error: %s',
err.message
);
this.emit('error', err);

@@ -90,6 +102,10 @@ });

} else {
this.logger.warn({
tnx: 'transport',
methodName: method
}, 'Non existing method %s called for transport', method);
this.logger.warn(
{
tnx: 'transport',
methodName: method
},
'Non existing method %s called for transport',
method
);
return false;

@@ -119,3 +135,3 @@ }

* @param {Object} data E-data description
* @param {Function} callback Callback to run once the sending succeeded or failed
* @param {Function?} callback Callback to run once the sending succeeded or failed
*/

@@ -138,16 +154,25 @@ sendMail(data, callback) {

this.logger.debug({
tnx: 'transport',
name: this.transporter.name,
version: this.transporter.version,
action: 'send'
}, 'Sending mail using %s/%s', this.transporter.name, this.transporter.version);
this.logger.debug(
{
tnx: 'transport',
name: this.transporter.name,
version: this.transporter.version,
action: 'send'
},
'Sending mail using %s/%s',
this.transporter.name,
this.transporter.version
);
this._processPlugins('compile', mail, err => {
if (err) {
this.logger.error({
err,
tnx: 'plugin',
action: 'compile'
}, 'PluginCompile Error: %s', err.message);
this.logger.error(
{
err,
tnx: 'plugin',
action: 'compile'
},
'PluginCompile Error: %s',
err.message
);
return callback(err);

@@ -164,7 +189,11 @@ }

if (err) {
this.logger.error({
err,
tnx: 'plugin',
action: 'stream'
}, 'PluginStream Error: %s', err.message);
this.logger.error(
{
err,
tnx: 'plugin',
action: 'stream'
},
'PluginStream Error: %s',
err.message
);
return callback(err);

@@ -176,7 +205,11 @@ }

let dkim = mail.data.dkim ? new DKIM(mail.data.dkim) : this.dkim;
this.logger.debug({
tnx: 'DKIM',
messageId: mail.message.messageId(),
dkimDomains: dkim.keys.map(key => key.keySelector + '.' + key.domainName).join(', ')
}, 'Signing outgoing message with %s keys', dkim.keys.length);
this.logger.debug(
{
tnx: 'DKIM',
messageId: mail.message.messageId(),
dkimDomains: dkim.keys.map(key => key.keySelector + '.' + key.domainName).join(', ')
},
'Signing outgoing message with %s keys',
dkim.keys.length
);
return dkim.sign(input, mail.data._dkim);

@@ -188,7 +221,11 @@ });

if (args[0]) {
this.logger.error({
err: args[0],
tnx: 'transport',
action: 'send'
}, 'Send Error: %s', args[0].message);
this.logger.error(
{
err: args[0],
tnx: 'transport',
action: 'send'
},
'Send Error: %s',
args[0].message
);
}

@@ -204,10 +241,3 @@ callback(...args);

getVersionString() {
return util.format(
'%s (%s; +%s; %s/%s)',
packageData.name,
packageData.version,
packageData.homepage,
this.transporter.name,
this.transporter.version
);
return util.format('%s (%s; +%s; %s/%s)', packageData.name, packageData.version, packageData.homepage, this.transporter.name, this.transporter.version);
}

@@ -226,7 +256,12 @@

if (userPlugins.length) {
this.logger.debug({
tnx: 'transaction',
pluginCount: userPlugins.length,
this.logger.debug(
{
tnx: 'transaction',
pluginCount: userPlugins.length,
step
},
'Using %s plugins for %s',
userPlugins.length,
step
}, 'Using %s plugins for %s', userPlugins.length, step);
);
}

@@ -295,45 +330,46 @@

case 'socks4':
case 'socks4a':
{
if (!this.meta.has('proxy_socks_module')) {
return callback(new Error('Socks module not loaded'));
}
case 'socks4a': {
if (!this.meta.has('proxy_socks_module')) {
return callback(new Error('Socks module not loaded'));
}
let connect = ipaddress => {
this.meta.get('proxy_socks_module').createConnection({
proxy: {
ipaddress,
port: proxy.port,
type: Number(proxy.protocol.replace(/\D/g, '')) || 5
},
target: {
host: options.host,
port: options.port
},
command: 'connect',
authentication: !proxy.auth ? false : {
let connect = ipaddress => {
this.meta.get('proxy_socks_module').createConnection({
proxy: {
ipaddress,
port: proxy.port,
type: Number(proxy.protocol.replace(/\D/g, '')) || 5
},
target: {
host: options.host,
port: options.port
},
command: 'connect',
authentication: !proxy.auth
? false
: {
username: decodeURIComponent(proxy.auth.split(':').shift()),
password: decodeURIComponent(proxy.auth.split(':').pop())
}
}, (err, socket) => {
if (err) {
return callback(err);
}
return callback(null, {
connection: socket
});
});
};
if (net.isIP(proxy.hostname)) {
return connect(proxy.hostname);
}
return dns.resolve(proxy.hostname, (err, address) => {
}, (err, socket) => {
if (err) {
return callback(err);
}
connect(address);
return callback(null, {
connection: socket
});
});
};
if (net.isIP(proxy.hostname)) {
return connect(proxy.hostname);
}
return dns.resolve(proxy.hostname, (err, address) => {
if (err) {
return callback(err);
}
connect(address);
});
}
}

@@ -345,3 +381,3 @@ callback(new Error('Unknown proxy configuration'));

_convertDataImages(mail, callback) {
if (!this.options.attachDataUrls && !mail.data.attachDataUrls || !mail.data.html) {
if ((!this.options.attachDataUrls && !mail.data.attachDataUrls) || !mail.data.html) {
return callback();

@@ -365,3 +401,3 @@ }

cid,
filename: 'image-' + (++cidCounter) + '.' + mimeTypes.detectExtension(mimeType)
filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
});

@@ -368,0 +404,0 @@ return prefix + 'cid:' + cid;

@@ -49,8 +49,3 @@ 'use strict';

resolveAll(callback) {
let keys = [
[this.data, 'html'],
[this.data, 'text'],
[this.data, 'watchHtml'],
[this.data, 'icalEvent']
];
let keys = [[this.data, 'html'], [this.data, 'text'], [this.data, 'watchHtml'], [this.data, 'icalEvent']];

@@ -85,3 +80,2 @@ if (this.data.alternatives && this.data.alternatives.length) {

}
});

@@ -152,3 +146,3 @@

default:
// do not add anything, since all messages are 'Normal' by default
// do not add anything, since all messages are 'Normal' by default
}

@@ -181,11 +175,14 @@ }

prepared: true,
value: [].concat(value || []).map(value => {
if (typeof value === 'string') {
return this._formatListUrl(value);
}
if (value && value.url) {
return this._formatListUrl(value.url) + (value.comment ? ' (' + value.comment + ')' : '');
}
return '';
}).join(', ')
value: []
.concat(value || [])
.map(value => {
if (typeof value === 'string') {
return this._formatListUrl(value);
}
if (value && value.url) {
return this._formatListUrl(value.url) + (value.comment ? ' (' + value.comment + ')' : '');
}
return '';
})
.join(', ')
};

@@ -207,5 +204,4 @@ })

}
}
module.exports = MailMessage;

@@ -10,3 +10,2 @@ /* eslint no-control-regex:0 */

module.exports = {
/**

@@ -61,3 +60,3 @@ * Checks if a value is plaintext string (uses only printable 7bit chars)

if (maxLength && maxLength > 7 + toCharset.length) {
maxLength -= (7 + toCharset.length);
maxLength -= 7 + toCharset.length;
}

@@ -67,3 +66,3 @@

// https://tools.ietf.org/html/rfc2047#section-5 rule (3)
encodedStr = qp.encode(data).replace(/[^a-z0-9!*+\-\/=]/ig, chr => {
encodedStr = qp.encode(data).replace(/[^a-z0-9!*+\-/=]/gi, chr => {
let ord = chr.charCodeAt(0).toString(16).toUpperCase();

@@ -143,9 +142,12 @@ if (chr === ' ') {

let startIndex = firstMatch.index + (firstMatch[0].match(/[^\s]/) || {
index: 0
}).index;
let startIndex =
firstMatch.index +
(firstMatch[0].match(/[^\s]/) || {
index: 0
}).index;
let endIndex = lastMatch.index + (lastMatch[1] || '').length;
encodedValue =
(startIndex ? value.substr(0, startIndex) : '') + this.encodeWord(value.substring(startIndex, endIndex), mimeWordEncoding || 'Q', maxLength) +
(startIndex ? value.substr(0, startIndex) : '') +
this.encodeWord(value.substring(startIndex, endIndex), mimeWordEncoding || 'Q', maxLength) +
(endIndex < value.length ? value.substr(endIndex) : '');

@@ -172,3 +174,3 @@

this.buildHeaderParam(param, value, 50).forEach(encodedParam => {
if (!/[\s"\\;:\/=\(\),<>@\[\]\?]|^[\-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === '*') {
if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === '*') {
paramsArray.push(encodedParam.key + '=' + encodedParam.value);

@@ -179,3 +181,3 @@ } else {

});
} else if (/[\s'"\\;:\/=\(\),<>@\[\]\?]|^\-/.test(value)) {
} else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
paramsArray.push(param + '=' + JSON.stringify(value));

@@ -218,9 +220,10 @@ } else {

if (this.isPlainText(data)) {
// check if conversion is even needed
if (encodedStr.length <= maxLength) {
return [{
key,
value: encodedStr
}];
return [
{
key,
value: encodedStr
}
];
}

@@ -240,5 +243,3 @@

}
} else {
if (/[\uD800-\uDBFF]/.test(encodedStr)) {

@@ -250,3 +251,3 @@ // string containts surrogate pairs, so normalize it to an array of bytes

ord = chr.charCodeAt(0);
if (ord >= 0xD800 && ord <= 0xDBFF && i < len - 1) {
if (ord >= 0xd800 && ord <= 0xdbff && i < len - 1) {
chr += encodedStr.charAt(i + 1);

@@ -270,3 +271,2 @@ encodedStrArr.push(chr);

for (i = 0, len = encodedStr.length; i < len; i++) {
chr = encodedStr[i];

@@ -396,3 +396,2 @@

escaped = false;
}

@@ -450,19 +449,18 @@ }

// convert "%AB" to "=?charset?Q?=AB?="
response.params[key] = '=?' +
response.params[key] =
'=?' +
response.params[key].charset +
'?Q?' +
value.
// fix invalidly encoded chars
replace(/[=\?_\s]/g,
s => {
let c = s.charCodeAt(0).toString(16);
if (s === ' ') {
return '_';
} else {
return '%' + (c.length < 2 ? '0' : '') + c;
}
}
).
// change from urlencoding to percent encoding
replace(/%/g, '=') +
value
// fix invalidly encoded chars
.replace(/[=?_\s]/g, s => {
let c = s.charCodeAt(0).toString(16);
if (s === ' ') {
return '_';
} else {
return '%' + (c.length < 2 ? '0' : '') + c;
}
})
// change from urlencoding to percent encoding
.replace(/%/g, '=') +
'?=';

@@ -512,3 +510,4 @@ } else {

result = '',
line, match;
line,
match;

@@ -550,3 +549,6 @@ while (pos < len) {

splitMimeEncodedString: (str, maxlen) => {
let curLine, match, chr, done,
let curLine,
match,
chr,
done,
lines = [];

@@ -561,3 +563,3 @@

// move incomplete escaped char back to main
if ((match = curLine.match(/\=[0-9A-F]?$/i))) {
if ((match = curLine.match(/[=][0-9A-F]?$/i))) {
curLine = curLine.substr(0, match.index);

@@ -570,6 +572,6 @@ }

// check if not middle of a unicode char sequence
if ((match = str.substr(curLine.length).match(/^\=([0-9A-F]{2})/i))) {
if ((match = str.substr(curLine.length).match(/^[=]([0-9A-F]{2})/i))) {
chr = parseInt(match[1], 16);
// invalid sequence, move one char back anc recheck
if (chr < 0xC2 && chr > 0x7F) {
if (chr < 0xc2 && chr > 0x7f) {
curLine = curLine.substr(0, curLine.length - 3);

@@ -617,9 +619,8 @@ done = false;

// should never run
return str.replace(/[^\x00-\x1F *'()<>@,;:\\"\[\]?=\u007F-\uFFFF]+/g, '');
return str.replace(/[^\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]+/g, '');
}
// ensure chars that are not handled by encodeURICompent are converted as well
return str.replace(/[\x00-\x1F *'()<>@,;:\\"\[\]?=\u007F-\uFFFF]/g, chr => this.encodeURICharComponent(chr));
return str.replace(/[\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]/g, chr => this.encodeURICharComponent(chr));
}
};

@@ -1,2 +0,2 @@

/* eslint no-undefined: 0 */
/* eslint no-undefined: 0, prefer-spread: 0 */

@@ -179,3 +179,2 @@ 'use strict';

appendChild(childNode) {
if (childNode.rootNode !== this.rootNode) {

@@ -205,3 +204,2 @@ childNode.rootNode = this.rootNode;

if (childNode === this) {
node.rootNode = this.rootNode;

@@ -259,11 +257,9 @@ node.parentNode = this.parentNode;

this.setHeader(key.key, key.value);
}
// allow [{key:'content-type', value: 'text/plain'}]
else if (Array.isArray(key)) {
} else if (Array.isArray(key)) {
// allow [{key:'content-type', value: 'text/plain'}]
key.forEach(i => {
this.setHeader(i.key, i.value);
});
}
// allow {'content-type': 'text/plain'}
else {
} else {
// allow {'content-type': 'text/plain'}
Object.keys(key).forEach(i => {

@@ -318,3 +314,2 @@ this.setHeader(i, key[i]);

addHeader(key, value) {
// Allow setting multiple headers at once

@@ -325,11 +320,9 @@ if (!value && key && typeof key === 'object') {

this.addHeader(key.key, key.value);
}
// allow [{key:'content-type', value: 'text/plain'}]
else if (Array.isArray(key)) {
} else if (Array.isArray(key)) {
// allow [{key:'content-type', value: 'text/plain'}]
key.forEach(i => {
this.addHeader(i.key, i.value);
});
}
// allow {'content-type': 'text/plain'}
else {
} else {
// allow {'content-type': 'text/plain'}
Object.keys(key).forEach(i => {

@@ -544,3 +537,3 @@ this.addHeader(i, key[i]);

if (param !== this.filename || /[\s'"\\;:\/=\(\),<>@\[\]\?]|^\-/.test(param)) {
if (param !== this.filename || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(param)) {
// include value in quotes if needed

@@ -684,3 +677,2 @@ param = '"' + param + '"';

if (this.content) {
if (Object.prototype.toString.call(this.content) === '[object Error]') {

@@ -698,5 +690,4 @@ // content is already errored

let createStream = () => {
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
contentStream = new(transferEncoding === 'base64' ? base64 : qp).Encoder(options);
contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);

@@ -964,10 +955,14 @@ contentStream.pipe(outputStream, {

_parseAddresses(addresses) {
return [].concat.apply([], [].concat(addresses).map(address => { // eslint-disable-line prefer-spread
if (address && address.address) {
address.address = this._normalizeAddress(address.address);
address.name = address.name || '';
return [address];
}
return addressparser(address);
}));
return [].concat.apply(
[],
[].concat(addresses).map(address => {
// eslint-disable-line prefer-spread
if (address && address.address) {
address.address = this._normalizeAddress(address.address);
address.name = address.name || '';
return [address];
}
return addressparser(address);
})
);
}

@@ -982,10 +977,14 @@

_normalizeHeaderKey(key) {
return (key || '').toString().
// no newlines in keys
replace(/\r?\n|\r/g, ' ').
trim().toLowerCase().
// use uppercase words, except MIME
replace(/^X\-SMTPAPI$|^(MIME|DKIM)\b|^[a-z]|\-(SPF|FBL|ID|MD5)$|\-[a-z]/ig, c => c.toUpperCase()).
// special case
replace(/^Content\-Features$/i, 'Content-features');
return (
(key || '')
.toString()
// no newlines in keys
.replace(/\r?\n|\r/g, ' ')
.trim()
.toLowerCase()
// use uppercase words, except MIME
.replace(/^X-SMTPAPI$|^(MIME|DKIM)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
// special case
.replace(/^Content-Features$/i, 'Content-features')
);
}

@@ -1002,3 +1001,3 @@

this.multipart = this.contentType.split('/').reduce((prev, value) => prev === 'multipart' ? value : false);
this.multipart = this.contentType.split('/').reduce((prev, value) => (prev === 'multipart' ? value : false));

@@ -1031,3 +1030,2 @@ if (this.multipart) {

switch (key) {
// Structured headers

@@ -1042,3 +1040,3 @@ case 'From':

// values enclosed in <>
// values enclosed in <>
case 'Message-ID':

@@ -1058,16 +1056,22 @@ case 'In-Reply-To':

// space separated list of values enclosed in <>
// space separated list of values enclosed in <>
case 'References':
value = [].concat.apply([], [].concat(value || '').map(elm => { // eslint-disable-line prefer-spread
elm = (elm || '').toString().replace(/\r?\n|\r/g, ' ').trim();
return elm.replace(/<[^>]*>/g, str => str.replace(/\s/g, '')).split(/\s+/);
})).map(elm => {
if (elm.charAt(0) !== '<') {
elm = '<' + elm;
}
if (elm.charAt(elm.length - 1) !== '>') {
elm = elm + '>';
}
return elm;
});
value = [].concat
.apply(
[],
[].concat(value || '').map(elm => {
// eslint-disable-line prefer-spread
elm = (elm || '').toString().replace(/\r?\n|\r/g, ' ').trim();
return elm.replace(/<[^>]*>/g, str => str.replace(/\s/g, '')).split(/\s+/);
})
)
.map(elm => {
if (elm.charAt(0) !== '<') {
elm = '<' + elm;
}
if (elm.charAt(elm.length - 1) !== '>') {
elm = elm + '>';
}
return elm;
});

@@ -1119,3 +1123,5 @@ return value.join(' ').trim();

} else if (address.group) {
values.push(this._encodeAddressName(address.name) + ':' + (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim() + ';');
values.push(
this._encodeAddressName(address.name) + ':' + (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim() + ';'
);
}

@@ -1206,8 +1212,14 @@ });

_generateMessageId() {
return '<' + [2, 2, 2, 6].reduce(
return (
'<' +
[2, 2, 2, 6].reduce(
// crux to generate UUID-like random strings
(prev, len) => prev + '-' + crypto.randomBytes(len).toString('hex'),
crypto.randomBytes(4).toString('hex')) + '@' +
crypto.randomBytes(4).toString('hex')
) +
'@' +
// try to use the domain of the FROM address or fallback to server hostname
(this.getEnvelope().from || this.hostname || os.hostname() || 'localhost').split('@').pop() + '>';
(this.getEnvelope().from || this.hostname || os.hostname() || 'localhost').split('@').pop() +
'>'
);
}

@@ -1214,0 +1226,0 @@ }

@@ -21,6 +21,6 @@ 'use strict';

_flush(done) {
if (this.lastByte === 0x0A) {
if (this.lastByte === 0x0a) {
return done();
}
if (this.lastByte === 0x0D) {
if (this.lastByte === 0x0d) {
this.push(Buffer.from('\n'));

@@ -27,0 +27,0 @@ return done();

@@ -11,4 +11,8 @@ 'use strict';

const SESTransport = require('./ses-transport');
const fetch = require('./fetch');
const packageData = require('../package.json');
module.exports.createTransport = function (transporter, defaults) {
let testAccount = false;
module.exports.createTransport = function(transporter, defaults) {
let urlConfig;

@@ -24,3 +28,2 @@ let options;

) {
if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) {

@@ -52,1 +55,87 @@ // parse a configuration URL into configuration options

};
module.exports.createTestAccount = function(apiUrl, callback) {
let promise;
if (!callback && typeof apiUrl === 'function') {
callback = apiUrl;
apiUrl = false;
}
if (!callback && typeof Promise === 'function') {
promise = new Promise((resolve, reject) => {
callback = shared.callbackPromise(resolve, reject);
});
}
if (testAccount) {
return callback(null, testAccount);
}
apiUrl = apiUrl || 'https://api.nodemailer.com';
let chunks = [];
let chunklen = 0;
let req = fetch(apiUrl + '/user', {
contentType: 'application/json',
method: 'POST',
body: Buffer.from(
JSON.stringify({
requestor: packageData.name,
version: packageData.version
})
)
});
req.on('readable', () => {
let chunk;
while ((chunk = req.read()) !== null) {
chunks.push(chunk);
chunklen += chunk.length;
}
});
req.once('error', err => callback(err));
req.once('end', () => {
let res = Buffer.concat(chunks, chunklen);
let data;
let err;
try {
data = JSON.parse(res.toString());
} catch (E) {
err = E;
}
if (err) {
return callback(err);
}
if (data.status !== 'success' || data.error) {
return callback(new Error(data.error || 'Request failed'));
}
delete data.status;
testAccount = data;
callback(null, testAccount);
});
return promise;
};
module.exports.getTestMessageUrl = function(info) {
if (!info || !info.response) {
return false;
}
let infoProps = new Map();
info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
infoProps.set(key, value);
});
});
if (testAccount && infoProps.has('MSGID')) {
return testAccount.web + '/message/' + infoProps.get('MSGID');
}
return false;
};

@@ -20,6 +20,6 @@ 'use strict';

[0x09], // <TAB>
[0x0A], // <LF>
[0x0D], // <CR>
[0x20, 0x3C], // <SP>!"#$%&'()*+,-./0123456789:;
[0x3E, 0x7E] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
[0x0a], // <LF>
[0x0d], // <CR>
[0x20, 0x3c], // <SP>!"#$%&'()*+,-./0123456789:;
[0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
];

@@ -84,9 +84,8 @@ let result = '';

continue;
} else if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t\.,!\?][^ \t\.,!\?]*$/))) {
} else if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
// truncate to nearest space
line = line.substr(0, line.length - (match[0].length - 1));
} else if (line.match(/\=[\da-f]{0,2}$/i)) {
} else if (line.match(/[=][\da-f]{0,2}$/i)) {
// push incomplete encoding sequences to the next line
if ((match = line.match(/\=[\da-f]{0,1}$/i))) {
if ((match = line.match(/[=][\da-f]{0,1}$/i))) {
line = line.substr(0, line.length - match[0].length);

@@ -96,3 +95,3 @@ }

// ensure that utf-8 sequences are not split
while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/\=[\da-f]{2}$/ig))) {
while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/[=][\da-f]{2}$/gi))) {
code = parseInt(match[0].substr(1, 2), 16);

@@ -105,3 +104,3 @@ if (code < 128) {

if (code >= 0xC0) {
if (code >= 0xc0) {
break;

@@ -113,3 +112,3 @@ }

if (pos + line.length < len && line.substr(-1) !== '\n') {
if (line.length === lineLength && line.match(/\=[\da-f]{2}$/i)) {
if (line.length === lineLength && line.match(/[=][\da-f]{2}$/i)) {
line = line.substr(0, line.length - 3);

@@ -202,3 +201,2 @@ } else if (line.length === lineLength) {

}
} else {

@@ -205,0 +203,0 @@ qp = encode(chunk);

@@ -102,7 +102,11 @@ 'use strict';

} catch (E) {
this.logger.error({
err: E,
tnx: 'spawn',
messageId
}, 'Error occurred while spawning sendmail. %s', E.message);
this.logger.error(
{
err: E,
tnx: 'spawn',
messageId
},
'Error occurred while spawning sendmail. %s',
E.message
);
return callback(E);

@@ -113,7 +117,12 @@ }

sendmail.on('error', err => {
this.logger.error({
err,
tnx: 'spawn',
messageId
}, 'Error occurred when sending message %s. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'spawn',
messageId
},
'Error occurred when sending message %s. %s',
messageId,
err.message
);
callback(err);

@@ -133,7 +142,12 @@ });

this.logger.error({
err,
tnx: 'stdin',
messageId
}, 'Error sending message %s to sendmail. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'stdin',
messageId
},
'Error sending message %s to sendmail. %s',
messageId,
err.message
);
callback(err);

@@ -144,7 +158,12 @@ });

sendmail.stdin.on('error', err => {
this.logger.error({
err,
tnx: 'stdin',
messageId
}, 'Error occurred when piping message %s to sendmail. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'stdin',
messageId
},
'Error occurred when piping message %s to sendmail. %s',
messageId,
err.message
);
callback(err);

@@ -157,6 +176,11 @@ });

}
this.logger.info({
tnx: 'send',
messageId
}, 'Sending message %s to <%s>', messageId, recipients.join(', '));
this.logger.info(
{
tnx: 'send',
messageId
},
'Sending message %s to <%s>',
messageId,
recipients.join(', ')
);

@@ -167,7 +191,12 @@ transform = this.winbreak ? new LeWindows() : new LeUnix();

transform.once('error', err => {
this.logger.error({
err,
tnx: 'stdin',
messageId
}, 'Error occurred when generating message %s. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'stdin',
messageId
},
'Error occurred when generating message %s. %s',
messageId,
err.message
);
sendmail.kill('SIGINT'); // do not deliver the message

@@ -182,3 +211,2 @@ callback(err);

}
}

@@ -185,0 +213,0 @@ }

@@ -12,3 +12,2 @@ 'use strict';

class LeWindows extends Transform {
constructor(options) {

@@ -28,3 +27,4 @@ super(options);

for (let i = 0, len = chunk.length; i < len; i++) {
if (chunk[i] === 0x0D) { // \n
if (chunk[i] === 0x0d) {
// \n
buf = chunk.slice(lastPos, i);

@@ -31,0 +31,0 @@ lastPos = i + 1;

@@ -12,3 +12,2 @@ 'use strict';

class LeWindows extends Transform {
constructor(options) {

@@ -29,7 +28,5 @@ super(options);

for (let i = 0, len = chunk.length; i < len; i++) {
if (chunk[i] === 0x0A) { // \n
if (
(i && chunk[i - 1] !== 0x0D) ||
(!i && this.lastByte !== 0x0D)
) {
if (chunk[i] === 0x0a) {
// \n
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
if (i > lastPos) {

@@ -36,0 +33,0 @@ buf = chunk.slice(lastPos, i);

@@ -111,3 +111,2 @@ 'use strict';

for (let i = this.rateMessages.length - 1; i >= 0; i--) {
if (this.rateMessages[i].ts >= now - this.rateInterval && (!oldest || this.rateMessages[i].ts < oldest)) {

@@ -165,9 +164,13 @@ oldest = this.rateMessages[i].ts;

}
this.logger.info({
tnx: 'send',
messageId
}, 'Sending message %s to <%s>', messageId, recipients.join(', '));
this.logger.info(
{
tnx: 'send',
messageId
},
'Sending message %s to <%s>',
messageId,
recipients.join(', ')
);
let getRawMessage = next => {
// do not use Message-ID and Date in DKIM signature

@@ -205,31 +208,15 @@ if (!mail.data._dkim) {

setImmediate(() => getRawMessage((err, raw) => {
if (err) {
this.logger.error({
err,
tnx: 'send',
messageId
}, 'Failed creating message for %s. %s', messageId, err.message);
statObject.pending = false;
return callback(err);
}
let sesMessage = {
RawMessage: { // required
Data: raw // required
},
Source: envelope.from,
Destinations: envelope.to
};
Object.keys(mail.data.ses || {}).forEach(key => {
sesMessage[key] = mail.data.ses[key];
});
this.ses.sendRawEmail(sesMessage, (err, data) => {
setImmediate(() =>
getRawMessage((err, raw) => {
if (err) {
this.logger.error({
err,
tnx: 'send'
}, 'Send error for %s: %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'send',
messageId
},
'Failed creating message for %s. %s',
messageId,
err.message
);
statObject.pending = false;

@@ -239,18 +226,47 @@ return callback(err);

let region = this.ses.config && this.ses.config.region || 'us-east-1';
if (region === 'us-east-1') {
region = 'email';
}
let sesMessage = {
RawMessage: {
// required
Data: raw // required
},
Source: envelope.from,
Destinations: envelope.to
};
statObject.pending = false;
callback(null, {
envelope: {
from: envelope.from,
to: envelope.to
},
messageId: '<' + data.MessageId + (!/@/.test(data.MessageId) ? '@' + region + '.amazonses.com' : '') + '>',
response: data.MessageId
Object.keys(mail.data.ses || {}).forEach(key => {
sesMessage[key] = mail.data.ses[key];
});
});
}));
this.ses.sendRawEmail(sesMessage, (err, data) => {
if (err) {
this.logger.error(
{
err,
tnx: 'send'
},
'Send error for %s: %s',
messageId,
err.message
);
statObject.pending = false;
return callback(err);
}
let region = (this.ses.config && this.ses.config.region) || 'us-east-1';
if (region === 'us-east-1') {
region = 'email';
}
statObject.pending = false;
callback(null, {
envelope: {
from: envelope.from,
to: envelope.to
},
messageId: '<' + data.MessageId + (!/@/.test(data.MessageId) ? '@' + region + '.amazonses.com' : '') + '>',
response: data.MessageId
});
});
})
);
}

@@ -272,14 +288,18 @@

this.ses.sendRawEmail({
RawMessage: { // required
Data: 'From: invalid@invalid\r\nTo: invalid@invalid\r\n Subject: Invalid\r\n\r\nInvalid'
this.ses.sendRawEmail(
{
RawMessage: {
// required
Data: 'From: invalid@invalid\r\nTo: invalid@invalid\r\n Subject: Invalid\r\n\r\nInvalid'
},
Source: 'invalid@invalid',
Destinations: ['invalid@invalid']
},
Source: 'invalid@invalid',
Destinations: ['invalid@invalid']
}, err => {
if (err && err.code !== 'InvalidParameterValue') {
return callback(err);
err => {
if (err && err.code !== 'InvalidParameterValue') {
return callback(err);
}
return callback(null, true);
}
return callback(null, true);
});
);

@@ -286,0 +306,0 @@ return promise;

@@ -0,1 +1,3 @@

/* eslint no-console: 0 */
'use strict';

@@ -153,11 +155,12 @@

*/
module.exports.callbackPromise = (resolve, reject) => function () {
let args = Array.from(arguments);
let err = args.shift();
if (err) {
reject(err);
} else {
resolve(...args);
}
};
module.exports.callbackPromise = (resolve, reject) =>
function() {
let args = Array.from(arguments);
let err = args.shift();
if (err) {
reject(err);
} else {
resolve(...args);
}
};

@@ -186,8 +189,5 @@ /**

let content = data && data[key] && data[key].content || data[key];
let content = (data && data[key] && data[key].content) || data[key];
let contentStream;
let encoding = (typeof data[key] === 'object' && data[key].encoding || 'utf8')
.toString()
.toLowerCase()
.replace(/[-_\s]/g, '');
let encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8').toString().toLowerCase().replace(/[-_\s]/g, '');

@@ -236,3 +236,3 @@ if (!content) {

*/
module.exports.assign = function ( /* target, ... sources */ ) {
module.exports.assign = function(/* target, ... sources */) {
let args = Array.from(arguments);

@@ -282,3 +282,2 @@ let target = args.shift() || {};

/**

@@ -335,3 +334,2 @@ * Streams a stream value into a Buffer

function createDefaultLogger(levels) {
let levelMaxLen = 0;

@@ -373,6 +371,3 @@ let levelNames = new Map();

message.split(/\r?\n/).forEach(line => {
console.log('[%s] %s %s', // eslint-disable-line no-console
new Date().toISOString().substr(0, 19).replace(/T/, ' '),
levelNames.get(level),
prefix + line);
console.log('[%s] %s %s', new Date().toISOString().substr(0, 19).replace(/T/, ' '), levelNames.get(level), prefix + line);
});

@@ -379,0 +374,0 @@ };

@@ -13,3 +13,2 @@ 'use strict';

class DataStream extends Transform {
constructor(options) {

@@ -24,3 +23,2 @@ super(options);

this.lastByte = false;
}

@@ -34,3 +32,5 @@

let chunklen = 0;
let i, len, lastPos = 0;
let i,
len,
lastPos = 0;
let buf;

@@ -49,7 +49,5 @@

for (i = 0, len = chunk.length; i < len; i++) {
if (chunk[i] === 0x2E) { // .
if (
(i && chunk[i - 1] === 0x0A) ||
(!i && (!this.lastByte || this.lastByte === 0x0A))
) {
if (chunk[i] === 0x2e) {
// .
if ((i && chunk[i - 1] === 0x0a) || (!i && (!this.lastByte || this.lastByte === 0x0a))) {
buf = chunk.slice(lastPos, i + 1);

@@ -61,7 +59,5 @@ chunks.push(buf);

}
} else if (chunk[i] === 0x0A) { // .
if (
(i && chunk[i - 1] !== 0x0D) ||
(!i && this.lastByte !== 0x0D)
) {
} else if (chunk[i] === 0x0a) {
// .
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
if (i > lastPos) {

@@ -104,5 +100,5 @@ buf = chunk.slice(lastPos, i);

let buf;
if (this.lastByte === 0x0A) {
if (this.lastByte === 0x0a) {
buf = new Buffer('.\r\n');
} else if (this.lastByte === 0x0D) {
} else if (this.lastByte === 0x0d) {
buf = new Buffer('\n.\r\n');

@@ -116,5 +112,4 @@ } else {

}
}
module.exports = DataStream;

@@ -33,3 +33,3 @@ 'use strict';

host: proxy.hostname,
port: Number(proxy.port) ? Number(proxy.port) : (proxy.protocol === 'https:' ? 443 : 80)
port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
};

@@ -48,3 +48,3 @@

let finished = false;
let tempSocketErr = function (err) {
let tempSocketErr = function(err) {
if (finished) {

@@ -77,10 +77,13 @@ return;

// HTTP method
'CONNECT ' + destinationHost + ':' + destinationPort + ' HTTP/1.1\r\n' +
'CONNECT ' +
destinationHost +
':' +
destinationPort +
' HTTP/1.1\r\n' +
// HTTP request headers
Object.keys(reqHeaders).map(key => key + ': ' + reqHeaders[key]).join('\r\n') +
// End request
'\r\n\r\n'
);
// HTTP request headers
Object.keys(reqHeaders).map(key => key + ': ' + reqHeaders[key]).join('\r\n') +
// End request
'\r\n\r\n');
let headers = '';

@@ -116,3 +119,3 @@ let onSocketData = chunk => {

}
return callback(new Error('Invalid response from proxy' + (match && ': ' + match[1] || '')));
return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
}

@@ -119,0 +122,0 @@

@@ -179,5 +179,8 @@ 'use strict';

this.once('connect', () => {
this.logger.debug({
tnx: 'smtp'
}, 'SMTP handshake finished');
this.logger.debug(
{
tnx: 'smtp'
},
'SMTP handshake finished'
);
connectCallback();

@@ -200,9 +203,11 @@ });

if (this.secureConnection && !this.alreadySecured) {
setImmediate(() => this._upgradeConnection(err => {
if (err) {
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
return;
}
this._onConnect();
}));
setImmediate(() =>
this._upgradeConnection(err => {
if (err) {
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
return;
}
this._onConnect();
})
);
} else {

@@ -287,7 +292,11 @@ setImmediate(() => this._onConnect());

this.logger.debug({
tnx: 'smtp'
}, 'Closing connection to the server using "%s"', closeMethod);
this.logger.debug(
{
tnx: 'smtp'
},
'Closing connection to the server using "%s"',
closeMethod
);
let socket = this._socket && this._socket.socket || this._socket;
let socket = (this._socket && this._socket.socket) || this._socket;

@@ -345,7 +354,13 @@ if (socket && !socket.destroyed) {

});
this._sendCommand('AUTH PLAIN ' + new Buffer(
//this._auth.user+'\u0000'+
'\u0000' + // skip authorization identity as it causes problems with some servers
this._auth.credentials.user + '\u0000' +
this._auth.credentials.pass, 'utf-8').toString('base64'));
this._sendCommand(
'AUTH PLAIN ' +
new Buffer(
//this._auth.user+'\u0000'+
'\u0000' + // skip authorization identity as it causes problems with some servers
this._auth.credentials.user +
'\u0000' +
this._auth.credentials.pass,
'utf-8'
).toString('base64')
);
return;

@@ -384,3 +399,3 @@ case 'CRAM-MD5':

let returned = false;
let callback = function () {
let callback = function() {
if (returned) {

@@ -415,3 +430,2 @@ return;

}
});

@@ -445,9 +459,15 @@ }

this.logger.info({
tnx: 'network',
localAddress: this._socket.localAddress,
localPort: this._socket.localPort,
remoteAddress: this._socket.remoteAddress,
remotePort: this._socket.remotePort
}, '%s established to %s:%s', this.secure ? 'Secure connection' : 'Connection', this._socket.remoteAddress, this._socket.remotePort);
this.logger.info(
{
tnx: 'network',
localAddress: this._socket.localAddress,
localPort: this._socket.localPort,
remoteAddress: this._socket.remoteAddress,
remotePort: this._socket.remotePort
},
'%s established to %s:%s',
this.secure ? 'Secure connection' : 'Connection',
this._socket.remoteAddress,
this._socket.remotePort
);

@@ -508,3 +528,3 @@ if (this._destroyed) {

lastline = this._responseQueue[this._responseQueue.length - 1];
if (/^\d+\-/.test(lastline.split('\n').pop())) {
if (/^\d+-/.test(lastline.split('\n').pop())) {
this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];

@@ -577,3 +597,3 @@ continue;

let responseCode = typeof response === 'string' && Number((response.match(/^\d+/) || [])[0]) || false;
let responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
if (responseCode) {

@@ -596,5 +616,8 @@ err.responseCode = responseCode;

_onClose() {
this.logger.info({
tnx: 'network'
}, 'Connection closed');
this.logger.info(
{
tnx: 'network'
},
'Connection closed'
);

@@ -696,5 +719,5 @@ if (this.upgrading && !this._destroyed) {

let str = this.lastServerResponse = (this._responseQueue.shift() || '').toString();
let str = (this.lastServerResponse = (this._responseQueue.shift() || '').toString());
if (/^\d+\-/.test(str.split('\n').pop())) {
if (/^\d+-/.test(str.split('\n').pop())) {
// keep waiting for the final part of multiline response

@@ -705,8 +728,12 @@ return;

if (this.options.debug || this.options.transactionLog) {
this.logger.debug({
tnx: 'server'
}, str.replace(/\r?\n$/, ''));
this.logger.debug(
{
tnx: 'server'
},
str.replace(/\r?\n$/, '')
);
}
if (!str.trim()) { // skip unexpected empty lines
if (!str.trim()) {
// skip unexpected empty lines
setImmediate(() => this._processResponse(true));

@@ -741,5 +768,8 @@ }

if (this.options.debug || this.options.transactionLog) {
this.logger.debug({
tnx: 'client'
}, (str || '').toString().replace(/\r?\n$/, ''));
this.logger.debug(
{
tnx: 'client'
},
(str || '').toString().replace(/\r?\n$/, '')
);
}

@@ -764,5 +794,5 @@

this._envelope = envelope || {};
this._envelope.from = (this._envelope.from && this._envelope.from.address || this._envelope.from || '').toString().trim();
this._envelope.from = ((this._envelope.from && this._envelope.from.address) || this._envelope.from || '').toString().trim();
this._envelope.to = [].concat(this._envelope.to || []).map(to => (to && to.address || to || '').toString().trim());
this._envelope.to = [].concat(this._envelope.to || []).map(to => ((to && to.address) || to || '').toString().trim());

@@ -842,3 +872,3 @@ if (!this._envelope.to.length) {

this._sendCommand('MAIL FROM:<' + (this._envelope.from) + '>' + (args.length ? ' ' + args.join(' ') : ''));
this._sendCommand('MAIL FROM:<' + this._envelope.from + '>' + (args.length ? ' ' + args.join(' ') : ''));
}

@@ -881,3 +911,3 @@

let orcpt = (params.orcpt || params.recipient).toString() || null;
let orcpt = (params.orcpt || params.recipient || '').toString() || null;
if (orcpt && orcpt.indexOf(';') < 0) {

@@ -907,3 +937,3 @@ orcpt = 'rfc822;' + orcpt;

}
return (args.length ? ' ' + args.join(' ') : '');
return args.length ? ' ' + args.join(' ') : '';
}

@@ -937,5 +967,8 @@

while ((chunk = logStream.read())) {
this.logger.debug({
tnx: 'message'
}, chunk.toString('binary').replace(/\r?\n$/, ''));
this.logger.debug(
{
tnx: 'message'
},
chunk.toString('binary').replace(/\r?\n$/, '')
);
}

@@ -947,7 +980,12 @@ });

dataStream.once('end', () => {
this.logger.info({
tnx: 'message',
inByteCount: dataStream.inByteCount,
outByteCount: dataStream.outByteCount
}, '<%s bytes encoded mime message (source size %s bytes)>', dataStream.outByteCount, dataStream.inByteCount);
this.logger.info(
{
tnx: 'message',
inByteCount: dataStream.inByteCount,
outByteCount: dataStream.outByteCount
},
'<%s bytes encoded mime message (source size %s bytes)>',
dataStream.outByteCount,
dataStream.inByteCount
);
});

@@ -1028,3 +1066,3 @@

// Detect if the server supports STARTTLS
if (!this.secure && !this.options.ignoreTLS && (/[ \-]STARTTLS\b/mi.test(str) || this.options.requireTLS)) {
if (!this.secure && !this.options.ignoreTLS && (/[ -]STARTTLS\b/im.test(str) || this.options.requireTLS)) {
this._sendCommand('STARTTLS');

@@ -1036,3 +1074,3 @@ this._responseActions.push(this._actionSTARTTLS);

// Detect if the server supports SMTPUTF8
if (/[ \-]SMTPUTF8\b/mi.test(str)) {
if (/[ -]SMTPUTF8\b/im.test(str)) {
this._supportedExtensions.push('SMTPUTF8');

@@ -1042,3 +1080,3 @@ }

// Detect if the server supports DSN
if (/[ \-]DSN\b/mi.test(str)) {
if (/[ -]DSN\b/im.test(str)) {
this._supportedExtensions.push('DSN');

@@ -1048,3 +1086,3 @@ }

// Detect if the server supports 8BITMIME
if (/[ \-]8BITMIME\b/mi.test(str)) {
if (/[ -]8BITMIME\b/im.test(str)) {
this._supportedExtensions.push('8BITMIME');

@@ -1054,3 +1092,3 @@ }

// Detect if the server supports PIPELINING
if (/[ \-]PIPELINING\b/mi.test(str)) {
if (/[ -]PIPELINING\b/im.test(str)) {
this._supportedExtensions.push('PIPELINING');

@@ -1080,3 +1118,3 @@ }

// Detect if the server supports SIZE extensions (and the max allowed size)
if ((match = str.match(/[ \-]SIZE(?:[ \t]+(\d+))?/mi))) {
if ((match = str.match(/[ -]SIZE(?:[ \t]+(\d+))?/im))) {
this._supportedExtensions.push('SIZE');

@@ -1114,5 +1152,8 @@ this._maxAllowedSize = Number(match[1]) || 0;

if (this.options.opportunisticTLS) {
this.logger.info({
tnx: 'smtp'
}, 'Failed STARTTLS upgrade, continuing unencrypted');
this.logger.info(
{
tnx: 'smtp'
},
'Failed STARTTLS upgrade, continuing unencrypted'
);
return this.emit('connect');

@@ -1130,5 +1171,8 @@ }

this.logger.info({
tnx: 'smtp'
}, 'Connection upgraded with STARTTLS');
this.logger.info(
{
tnx: 'smtp'
},
'Connection upgraded with STARTTLS'
);

@@ -1160,3 +1204,4 @@ if (secured) {

_actionAUTH_LOGIN_USER(str, callback) {
if (!/^334[ \-]/.test(str)) { // expecting '334 VXNlcm5hbWU6'
if (!/^334[ -]/.test(str)) {
// expecting '334 VXNlcm5hbWU6'
callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));

@@ -1205,3 +1250,2 @@ return;

this._sendCommand(new Buffer(prepended).toString('base64'));

@@ -1221,8 +1265,12 @@ }

this.logger.info({
tnx: 'smtp',
username: this._auth.user,
action: 'authenticated',
method: this._authMethod
}, 'User %s authenticated', JSON.stringify(this._auth.user));
this.logger.info(
{
tnx: 'smtp',
username: this._auth.user,
action: 'authenticated',
method: this._authMethod
},
'User %s authenticated',
JSON.stringify(this._auth.user)
);
this.authenticated = true;

@@ -1240,3 +1288,4 @@ callback(null, true);

_actionAUTH_LOGIN_PASS(str, callback) {
if (!/^334[ \-]/.test(str)) { // expecting '334 UGFzc3dvcmQ6'
if (!/^334[ -]/.test(str)) {
// expecting '334 UGFzc3dvcmQ6'
return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));

@@ -1278,17 +1327,25 @@ }

if (str.charAt(0) !== '2') {
this.logger.info({
this.logger.info(
{
tnx: 'smtp',
username: this._auth.user,
action: 'authfail',
method: this._authMethod
},
'User %s failed to authenticate',
JSON.stringify(this._auth.user)
);
return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
}
this.logger.info(
{
tnx: 'smtp',
username: this._auth.user,
action: 'authfail',
action: 'authenticated',
method: this._authMethod
}, 'User %s failed to authenticate', JSON.stringify(this._auth.user));
return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
}
this.logger.info({
tnx: 'smtp',
username: this._auth.user,
action: 'authenticated',
method: this._authMethod
}, 'User %s authenticated', JSON.stringify(this._auth.user));
},
'User %s authenticated',
JSON.stringify(this._auth.user)
);
this.authenticated = true;

@@ -1345,3 +1402,5 @@ callback(null, true);

_actionRCPT(str, callback) {
let message, err, curRecipient = this._recipientQueue.shift();
let message,
err,
curRecipient = this._recipientQueue.shift();
if (Number(str.charAt(0)) !== 2) {

@@ -1456,8 +1515,12 @@ // this is a soft error

if (err) {
this.logger.info({
tnx: 'smtp',
username: this._auth.user,
action: 'authfail',
method: this._authMethod
}, 'User %s failed to authenticate', JSON.stringify(this._auth.user));
this.logger.info(
{
tnx: 'smtp',
username: this._auth.user,
action: 'authfail',
method: this._authMethod
},
'User %s failed to authenticate',
JSON.stringify(this._auth.user)
);
return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));

@@ -1464,0 +1527,0 @@ }

@@ -139,7 +139,11 @@ 'use strict';

connection.close();
this.logger.info({
tnx: 'connection',
cid: connection.id,
action: 'removed'
}, 'Connection #%s removed', connection.id);
this.logger.info(
{
tnx: 'connection',
cid: connection.id,
action: 'removed'
},
'Connection #%s removed',
connection.id
);
}

@@ -149,5 +153,8 @@ }

if (len && !this._connections.length) {
this.logger.debug({
tnx: 'connection'
}, 'All connections removed');
this.logger.debug(
{
tnx: 'connection'
},
'All connections removed'
);
}

@@ -162,5 +169,8 @@

if (!this._queue.length) {
this.logger.debug({
tnx: 'connection'
}, 'Pending queue entries cleared');
this.logger.debug(
{
tnx: 'connection'
},
'Pending queue entries cleared'
);
return;

@@ -173,7 +183,12 @@ }

} catch (E) {
this.logger.error({
err: E,
tnx: 'callback',
cid: connection.id
}, 'Callback error for #%s: %s', connection.id, E.message);
this.logger.error(
{
err: E,
tnx: 'callback',
cid: connection.id
},
'Callback error for #%s: %s',
connection.id,
E.message
);
}

@@ -233,3 +248,3 @@ }

let entry = connection.queueEntry = this._queue.shift();
let entry = (connection.queueEntry = this._queue.shift());
entry.messageId = (connection.queueEntry.mail.message.getHeader('message-id') || '').replace(/[<>\s]/g, '');

@@ -239,8 +254,14 @@

this.logger.debug({
tnx: 'pool',
cid: connection.id,
messageId: entry.messageId,
action: 'assign'
}, 'Assigned message <%s> to #%s (%s)', entry.messageId, connection.id, connection.messages + 1);
this.logger.debug(
{
tnx: 'pool',
cid: connection.id,
messageId: entry.messageId,
action: 'assign'
},
'Assigned message <%s> to #%s (%s)',
entry.messageId,
connection.id,
connection.messages + 1
);

@@ -260,7 +281,12 @@ if (this._rateLimit.limit) {

} catch (E) {
this.logger.error({
err: E,
tnx: 'callback',
cid: connection.id
}, 'Callback error for #%s: %s', connection.id, E.message);
this.logger.error(
{
err: E,
tnx: 'callback',
cid: connection.id
},
'Callback error for #%s: %s',
connection.id,
E.message
);
}

@@ -280,15 +306,23 @@ connection.queueEntry = false;

this.logger.info({
tnx: 'pool',
cid: connection.id,
action: 'conection'
}, 'Created new pool resource #%s', connection.id);
this.logger.info(
{
tnx: 'pool',
cid: connection.id,
action: 'conection'
},
'Created new pool resource #%s',
connection.id
);
// resource comes available
connection.on('available', () => {
this.logger.debug({
tnx: 'connection',
cid: connection.id,
action: 'available'
}, 'Connection #%s became available', connection.id);
this.logger.debug(
{
tnx: 'connection',
cid: connection.id,
action: 'available'
},
'Connection #%s became available',
connection.id
);

@@ -307,13 +341,22 @@ if (this._closed) {

if (err.code !== 'EMAXLIMIT') {
this.logger.error({
err,
tnx: 'pool',
cid: connection.id
}, 'Pool Error for #%s: %s', connection.id, err.message);
this.logger.error(
{
err,
tnx: 'pool',
cid: connection.id
},
'Pool Error for #%s: %s',
connection.id,
err.message
);
} else {
this.logger.debug({
tnx: 'pool',
cid: connection.id,
action: 'maxlimit'
}, 'Max messages limit exchausted for #%s', connection.id);
this.logger.debug(
{
tnx: 'pool',
cid: connection.id,
action: 'maxlimit'
},
'Max messages limit exchausted for #%s',
connection.id
);
}

@@ -325,7 +368,12 @@

} catch (E) {
this.logger.error({
err: E,
tnx: 'callback',
cid: connection.id
}, 'Callback error for #%s: %s', connection.id, E.message);
this.logger.error(
{
err: E,
tnx: 'callback',
cid: connection.id
},
'Callback error for #%s: %s',
connection.id,
E.message
);
}

@@ -342,7 +390,11 @@ connection.queueEntry = false;

connection.once('close', () => {
this.logger.info({
tnx: 'connection',
cid: connection.id,
action: 'closed'
}, 'Connection #%s was closed', connection.id);
this.logger.info(
{
tnx: 'connection',
cid: connection.id,
action: 'closed'
},
'Connection #%s was closed',
connection.id
);

@@ -357,8 +409,13 @@ this._removeConnection(connection);

if (connection.queueEntry) {
this.logger.debug({
tnx: 'pool',
cid: connection.id,
messageId: connection.queueEntry.messageId,
action: 'requeue'
}, 'Re-queued message <%s> for #%s', connection.queueEntry.messageId, connection.id);
this.logger.debug(
{
tnx: 'pool',
cid: connection.id,
messageId: connection.queueEntry.messageId,
action: 'requeue'
},
'Re-queued message <%s> for #%s',
connection.queueEntry.messageId,
connection.id
);
this._queue.unshift(connection.queueEntry);

@@ -475,10 +532,17 @@ connection.queueEntry = false;

if (socketOptions && socketOptions.connection) {
this.logger.info({
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
}, 'Using proxied socket from %s:%s to %s:%s', socketOptions.connection.remoteAddress, socketOptions.connection.remotePort, options.host || '', options.port || '');
this.logger.info(
{
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
},
'Using proxied socket from %s:%s to %s:%s',
socketOptions.connection.remoteAddress,
socketOptions.connection.remotePort,
options.host || '',
options.port || ''
);
options = shared.assign(false, options);

@@ -485,0 +549,0 @@ Object.keys(socketOptions).forEach(key => {

@@ -24,16 +24,15 @@ 'use strict';

switch ((this.options.auth.type || '').toString().toUpperCase()) {
case 'OAUTH2':
{
let oauth2 = new XOAuth2(this.options.auth, this.logger);
oauth2.provisionCallback = this.pool.mailer && this.pool.mailer.get('oauth2_provision_cb') || oauth2.provisionCallback;
this.auth = {
type: 'OAUTH2',
user: this.options.auth.user,
oauth2,
method: 'XOAUTH2'
};
oauth2.on('token', token => this.pool.mailer.emit('token', token));
oauth2.on('error', err => this.emit('error', err));
break;
}
case 'OAUTH2': {
let oauth2 = new XOAuth2(this.options.auth, this.logger);
oauth2.provisionCallback = (this.pool.mailer && this.pool.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
this.auth = {
type: 'OAUTH2',
user: this.options.auth.user,
oauth2,
method: 'XOAUTH2'
};
oauth2.on('token', token => this.pool.mailer.emit('token', token));
oauth2.on('error', err => this.emit('error', err));
break;
}
default:

@@ -73,10 +72,17 @@ this.auth = {

if (socketOptions && socketOptions.connection) {
this.logger.info({
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
}, 'Using proxied socket from %s:%s to %s:%s', socketOptions.connection.remoteAddress, socketOptions.connection.remotePort, options.host || '', options.port || '');
this.logger.info(
{
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
},
'Using proxied socket from %s:%s to %s:%s',
socketOptions.connection.remoteAddress,
socketOptions.connection.remotePort,
options.host || '',
options.port || ''
);

@@ -173,7 +179,13 @@ options = assign(false, options);

}
this.logger.info({
tnx: 'send',
this.logger.info(
{
tnx: 'send',
messageId,
cid: this.id
},
'Sending message %s using #%s to <%s>',
messageId,
cid: this.id
}, 'Sending message %s using #%s to <%s>', messageId, this.id, recipients.join(', '));
this.id,
recipients.join(', ')
);

@@ -180,0 +192,0 @@ if (mail.data.dsn) {

@@ -100,18 +100,17 @@ 'use strict';

switch ((authData.type || '').toString().toUpperCase()) {
case 'OAUTH2':
{
if (!authData.service && !authData.user) {
return false;
}
let oauth2 = new XOAuth2(authData, this.logger);
oauth2.provisionCallback = this.mailer && this.mailer.get('oauth2_provision_cb') || oauth2.provisionCallback;
oauth2.on('token', token => this.mailer.emit('token', token));
oauth2.on('error', err => this.emit('error', err));
return {
type: 'OAUTH2',
user: authData.user,
oauth2,
method: 'XOAUTH2'
};
case 'OAUTH2': {
if (!authData.service && !authData.user) {
return false;
}
let oauth2 = new XOAuth2(authData, this.logger);
oauth2.provisionCallback = (this.mailer && this.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
oauth2.on('token', token => this.mailer.emit('token', token));
oauth2.on('error', err => this.emit('error', err));
return {
type: 'OAUTH2',
user: authData.user,
oauth2,
method: 'XOAUTH2'
};
}
default:

@@ -145,12 +144,18 @@ return {

if (socketOptions && socketOptions.connection) {
this.logger.info(
{
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
},
'Using proxied socket from %s:%s to %s:%s',
socketOptions.connection.remoteAddress,
socketOptions.connection.remotePort,
options.host || '',
options.port || ''
);
this.logger.info({
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
}, 'Using proxied socket from %s:%s to %s:%s', socketOptions.connection.remoteAddress, socketOptions.connection.remotePort, options.host || '', options.port || '');
// only copy options if we need to modify it

@@ -206,6 +211,11 @@ options = shared.assign(false, options);

this.logger.info({
tnx: 'send',
messageId
}, 'Sending message %s to <%s>', messageId, recipients.join(', '));
this.logger.info(
{
tnx: 'send',
messageId
},
'Sending message %s to <%s>',
messageId,
recipients.join(', ')
);

@@ -215,6 +225,11 @@ connection.send(envelope, mail.message.createReadStream(), (err, info) => {

if (err) {
this.logger.error({
err,
tnx: 'send'
}, 'Send error for %s: %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'send'
},
'Send error for %s: %s',
messageId,
err.message
);
return callback(err);

@@ -230,6 +245,11 @@ }

} catch (E) {
this.logger.error({
err: E,
tnx: 'callback'
}, 'Callback error for %s: %s', messageId, E.message);
this.logger.error(
{
err: E,
tnx: 'callback'
},
'Callback error for %s: %s',
messageId,
E.message
);
}

@@ -291,10 +311,17 @@ });

if (socketOptions && socketOptions.connection) {
this.logger.info({
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
}, 'Using proxied socket from %s:%s to %s:%s', socketOptions.connection.remoteAddress, socketOptions.connection.remotePort, options.host || '', options.port || '');
this.logger.info(
{
tnx: 'proxy',
remoteAddress: socketOptions.connection.remoteAddress,
remotePort: socketOptions.connection.remotePort,
destHost: options.host || '',
destPort: options.port || '',
action: 'connected'
},
'Using proxied socket from %s:%s to %s:%s',
socketOptions.connection.remoteAddress,
socketOptions.connection.remotePort,
options.host || '',
options.port || ''
);

@@ -301,0 +328,0 @@ options = shared.assign(false, options);

@@ -52,9 +52,14 @@ 'use strict';

}
this.logger.info({
tnx: 'send',
messageId
}, 'Sending message %s to <%s> using %s line breaks', messageId, recipients.join(', '), this.winbreak ? '<CR><LF>' : '<LF>');
this.logger.info(
{
tnx: 'send',
messageId
},
'Sending message %s to <%s> using %s line breaks',
messageId,
recipients.join(', '),
this.winbreak ? '<CR><LF>' : '<LF>'
);
setImmediate(() => {
let sourceStream;

@@ -70,7 +75,12 @@ let stream;

} catch (E) {
this.logger.error({
err: E,
tnx: 'send',
messageId
}, 'Creating send stream failed for %s. %s', messageId, E.message);
this.logger.error(
{
err: E,
tnx: 'send',
messageId
},
'Creating send stream failed for %s. %s',
messageId,
E.message
);
return done(E);

@@ -81,7 +91,12 @@ }

stream.once('error', err => {
this.logger.error({
err,
tnx: 'send',
messageId
}, 'Failed creating message for %s. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'send',
messageId
},
'Failed creating message for %s. %s',
messageId,
err.message
);
});

@@ -106,15 +121,22 @@ return done(null, {

stream.once('error', err => {
this.logger.error({
err,
tnx: 'send',
messageId
}, 'Failed creating message for %s. %s', messageId, err.message);
this.logger.error(
{
err,
tnx: 'send',
messageId
},
'Failed creating message for %s. %s',
messageId,
err.message
);
return done(err);
});
stream.on('end', () => done(null, {
envelope: mail.data.envelope || mail.message.getEnvelope(),
messageId,
message: Buffer.concat(chunks, chunklen)
}));
stream.on('end', () =>
done(null, {
envelope: mail.data.envelope || mail.message.getEnvelope(),
messageId,
message: Buffer.concat(chunks, chunklen)
})
);
});

@@ -121,0 +143,0 @@ }

@@ -21,3 +21,3 @@ 'use strict';

function normalizeKey(key) {
return key.replace(/[^a-zA-Z0-9.\-]/g, '').toLowerCase();
return key.replace(/[^a-zA-Z0-9.-]/g, '').toLowerCase();
}

@@ -45,5 +45,5 @@

*/
module.exports = function (key) {
module.exports = function(key) {
key = normalizeKey(key.split('@').pop());
return normalized[key] || false;
};

@@ -10,5 +10,3 @@ {

"AOL": {
"domains": [
"aol.com"
],
"domains": ["aol.com"],
"host": "smtp.aol.com",

@@ -30,5 +28,3 @@ "port": 587

"FastMail": {
"domains": [
"fastmail.fm"
],
"domains": ["fastmail.fm"],
"host": "mail.messagingengine.com",

@@ -40,6 +36,3 @@ "port": 465,

"GandiMail": {
"aliases": [
"Gandi",
"Gandi Mail"
],
"aliases": ["Gandi", "Gandi Mail"],
"host": "mail.gandi.net",

@@ -50,9 +43,4 @@ "port": 587

"Gmail": {
"aliases": [
"Google Mail"
],
"domains": [
"gmail.com",
"googlemail.com"
],
"aliases": ["Google Mail"],
"domains": ["gmail.com", "googlemail.com"],
"host": "smtp.gmail.com",

@@ -83,11 +71,4 @@ "port": 465,

"Hotmail": {
"aliases": [
"Outlook",
"Outlook.com",
"Hotmail.com"
],
"domains": [
"hotmail.com",
"outlook.com"
],
"aliases": ["Outlook", "Outlook.com", "Hotmail.com"],
"domains": ["hotmail.com", "outlook.com"],
"host": "smtp.live.com",

@@ -102,6 +83,3 @@ "port": 587,

"aliases": ["Me", "Mac"],
"domains": [
"me.com",
"mac.com"
],
"domains": ["me.com", "mac.com"],
"host": "smtp.mail.me.com",

@@ -141,2 +119,7 @@ "port": 587

"Mailtrap": {
"host": "smtp.mailtrap.io",
"port": 2525
},
"Mandrill": {

@@ -153,6 +136,3 @@ "host": "smtp.mandrillapp.com",

"OpenMailBox": {
"aliases": [
"OMB",
"openmailbox.org"
],
"aliases": ["OMB", "openmailbox.org"],
"host": "smtp.openmailbox.org",

@@ -176,5 +156,3 @@ "port": 465,

"QQ": {
"domains": [
"qq.com"
],
"domains": ["qq.com"],
"host": "smtp.qq.com",

@@ -187,5 +165,3 @@ "port": 465,

"aliases": ["QQ Enterprise"],
"domains": [
"exmail.qq.com"
],
"domains": ["exmail.qq.com"],
"host": "smtp.exmail.qq.com",

@@ -239,9 +215,4 @@ "port": 465,

"Sparkpost": {
"aliases": [
"SparkPost",
"SparkPost Mail"
],
"domains": [
"sparkpost.com"
],
"aliases": ["SparkPost", "SparkPost Mail"],
"domains": ["sparkpost.com"],
"host": "smtp.sparkpostmail.com",

@@ -253,5 +224,3 @@ "port": 587,

"Yahoo": {
"domains": [
"yahoo.com"
],
"domains": ["yahoo.com"],
"host": "smtp.mail.yahoo.com",

@@ -263,5 +232,3 @@ "port": 465,

"Yandex": {
"domains": [
"yandex.ru"
],
"domains": ["yandex.ru"],
"host": "smtp.yandex.ru",

@@ -268,0 +235,0 @@ "port": 465,

@@ -51,7 +51,10 @@ 'use strict';

this.logger = shared.getLogger({
logger
}, {
component: this.options.component || 'OAuth2'
});
this.logger = shared.getLogger(
{
logger
},
{
component: this.options.component || 'OAuth2'
}
);

@@ -70,3 +73,3 @@ this.provisionCallback = typeof this.options.provisionCallback === 'function' ? this.options.provisionCallback : false;

let timeout = Math.max(Number(this.options.timeout) || 0, 0);
this.expires = timeout && (Date.now() + timeout * 1000) || 0;
this.expires = (timeout && Date.now() + timeout * 1000) || 0;
}

@@ -88,14 +91,22 @@ }

if (args[0]) {
this.logger.error({
err: args[0],
tnx: 'OAUTH2',
user: this.options.user,
action: 'renew'
}, 'Failed generating new Access Token for %s', this.options.user);
this.logger.error(
{
err: args[0],
tnx: 'OAUTH2',
user: this.options.user,
action: 'renew'
},
'Failed generating new Access Token for %s',
this.options.user
);
} else {
this.logger.info({
tnx: 'OAUTH2',
user: this.options.user,
action: 'renew'
}, 'Generated new Access Token for %s', this.options.user);
this.logger.info(
{
tnx: 'OAUTH2',
user: this.options.user,
action: 'renew'
},
'Generated new Access Token for %s',
this.options.user
);
}

@@ -129,3 +140,3 @@ callback(...args);

timeout = Math.max(Number(timeout) || 0, 0);
this.expires = timeout && Date.now() + timeout * 1000 || 0;
this.expires = (timeout && Date.now() + timeout * 1000) || 0;

@@ -162,5 +173,3 @@ this.emit('token', {

};
} else {
if (!this.options.refreshToken) {

@@ -220,8 +229,3 @@ return callback(new Error('Can\'t create new access token for user'));

buildXOAuth2Token(accessToken) {
let authData = [
'user=' + (this.options.user || ''),
'auth=Bearer ' + (accessToken || this.accessToken),
'',
''
];
let authData = ['user=' + (this.options.user || ''), 'auth=Bearer ' + (accessToken || this.accessToken), '', ''];
return new Buffer(authData.join('\x01'), 'utf-8').toString('base64');

@@ -290,6 +294,7 @@ }

return data.toString('base64').
replace(/=+/g, ''). // remove '='s
replace(/\+/g, '-'). // '+' → '-'
replace(/\//g, '_'); // '/' → '_'
return data
.toString('base64')
.replace(/[=]+/g, '') // remove '='s
.replace(/\+/g, '-') // '+' → '-'
.replace(/\//g, '_'); // '/' → '_'
}

@@ -304,6 +309,3 @@

jwtSignRS256(payload) {
payload = [
'{"alg":"RS256","typ":"JWT"}',
JSON.stringify(payload)
].map(val => this.toBase64URL(val)).join('.');
payload = ['{"alg":"RS256","typ":"JWT"}', JSON.stringify(payload)].map(val => this.toBase64URL(val)).join('.');
let signature = crypto.createSign('RSA-SHA256').update(payload).sign(this.options.privateKey);

@@ -310,0 +312,0 @@ return payload + '.' + this.toBase64URL(signature);

{
"name": "nodemailer",
"version": "4.0.1",
"description": "Easy as cake e-mail sending from your Node.js applications",
"main": "lib/nodemailer.js",
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
"url": "https://github.com/nodemailer/nodemailer.git"
},
"keywords": [
"Nodemailer"
],
"author": "Andris Reinman",
"license": "MIT",
"bugs": {
"url": "https://github.com/nodemailer/nodemailer/issues"
},
"homepage": "https://nodemailer.com/",
"devDependencies": {
"bunyan": "^1.8.10",
"chai": "^3.5.0",
"eslint-config-nodemailer": "^1.0.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-eslint": "^19.0.0",
"grunt-mocha-test": "^0.13.2",
"libbase64": "^0.1.0",
"libmime": "^3.1.0",
"libqp": "^1.1.0",
"mocha": "^3.2.0",
"proxy": "^0.2.4",
"proxy-test-server": "^1.0.0",
"sinon": "^2.1.0",
"smtp-server": "^3.0.1"
},
"engines": {
"node": ">=6.0.0"
}
"name": "nodemailer",
"version": "4.1.0",
"description": "Easy as cake e-mail sending from your Node.js applications",
"main": "lib/nodemailer.js",
"scripts": {
"test": "grunt"
},
"repository": {
"type": "git",
"url": "https://github.com/nodemailer/nodemailer.git"
},
"keywords": ["Nodemailer"],
"author": "Andris Reinman",
"license": "MIT",
"bugs": {
"url": "https://github.com/nodemailer/nodemailer/issues"
},
"homepage": "https://nodemailer.com/",
"devDependencies": {
"bunyan": "^1.8.12",
"chai": "^4.1.1",
"eslint-config-nodemailer": "^1.2.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-eslint": "^20.0.0",
"grunt-mocha-test": "^0.13.2",
"libbase64": "^0.2.0",
"libmime": "^3.1.0",
"libqp": "^1.1.0",
"mocha": "^3.5.0",
"proxy": "^0.2.4",
"proxy-test-server": "^1.0.0",
"sinon": "^3.2.1",
"smtp-server": "^3.1.0"
},
"engines": {
"node": ">=6.0.0"
}
}

@@ -7,4 +7,2 @@ # Nodemailer

<a href="https://gitter.im/nodemailer/nodemailer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/Join Chat.svg" alt="Gitter chat" height="18"></a> <a href="http://travis-ci.org/nodemailer/nodemailer"><img src="https://secure.travis-ci.org/nodemailer/nodemailer.svg" alt="Build Status" height="18"></a> <a href="http://badge.fury.io/js/nodemailer"><img src="https://badge.fury.io/js/nodemailer.svg" alt="NPM version" height="18"></a> <a href="https://www.npmjs.com/package/nodemailer"><img src="https://img.shields.io/npm/dt/nodemailer.svg" alt="NPM downloads" height="18"></a>
[![NPM](https://nodei.co/npm/nodemailer.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/nodemailer/)

@@ -16,2 +14,26 @@

Nodemailer v4.0.0 and up is licensed under the [MIT license](./LICENSE)
## Having an issue?
#### Nodemailer throws a SyntaxError for "..."
You are using older Node.js version than v6.0. Upgrade Node.js to get support for the spread operator
#### I'm having issues with Gmail
Gmail either works well or it does not work at all. It is probably easier to switch to an alternative service instead of fixing issues with Gmail. If Gmail does not work for you then don't use it.
#### I get ETIMEDOUT errors
Check your firewall settings. Timeout usually occurs when you try to open a connection to a port that is firewalled either on the server or on your machine
#### I get TLS errors
If you are running the code in your own machine, then check your antivirus settings. Antiviruses often mess around with email ports usage. Node.js might not recognize the MITM cert your antivirus is using.
#### I have a different problem
If you are having issues with Nodemailer, then the best way to find help would be [Stack Overflow](https://stackoverflow.com/search?q=nodemailer).
### License
Nodemailer is licensed under the **MIT license**

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc