node_modules: update (#259)

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
Dawid Dziurla
2026-02-05 08:49:11 +01:00
committed by GitHub
parent a4eb4faebc
commit 62a2d05b79
16 changed files with 335 additions and 131 deletions

6
node_modules/.package-lock.json generated vendored
View File

@@ -197,9 +197,9 @@
} }
}, },
"node_modules/nodemailer": { "node_modules/nodemailer": {
"version": "7.0.13", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.0.tgz",
"integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", "integrity": "sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==",
"license": "MIT-0", "license": "MIT-0",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"

14
node_modules/nodemailer/CHANGELOG.md generated vendored
View File

@@ -1,5 +1,19 @@
# CHANGELOG # CHANGELOG
## [8.0.0](https://github.com/nodemailer/nodemailer/compare/v7.0.13...v8.0.0) (2026-02-04)
### ⚠ BREAKING CHANGES
* Error code 'NoAuth' renamed to 'ENOAUTH'
### Bug Fixes
* add connection fallback to alternative DNS addresses ([e726d6f](https://github.com/nodemailer/nodemailer/commit/e726d6f44aa7ca14e943d4303243cb5494b09c75))
* centralize and standardize error codes ([45062ce](https://github.com/nodemailer/nodemailer/commit/45062ce7a4705f3e63c5d9e606547f4d99fd29b5))
* harden DNS fallback against race conditions and cleanup issues ([4fa3c63](https://github.com/nodemailer/nodemailer/commit/4fa3c63a1f36aefdbaea7f57a133adc458413a47))
* improve socket cleanup to prevent potential memory leaks ([6069fdc](https://github.com/nodemailer/nodemailer/commit/6069fdcff68a3eef9a9bb16b2bf5ddb924c02091))
## [7.0.13](https://github.com/nodemailer/nodemailer/compare/v7.0.12...v7.0.13) (2026-01-27) ## [7.0.13](https://github.com/nodemailer/nodemailer/compare/v7.0.12...v7.0.13) (2026-01-27)

61
node_modules/nodemailer/lib/errors.js generated vendored Normal file
View File

@@ -0,0 +1,61 @@
'use strict';
/**
* Nodemailer Error Codes
*
* Centralized error code definitions for consistent error handling.
*
* Usage:
* const errors = require('./errors');
* let err = new Error('Connection closed');
* err.code = errors.ECONNECTION;
*/
/**
* Error code descriptions for documentation and debugging
*/
const ERROR_CODES = {
// Connection errors
ECONNECTION: 'Connection closed unexpectedly',
ETIMEDOUT: 'Connection or operation timed out',
ESOCKET: 'Socket-level error',
EDNS: 'DNS resolution failed',
// TLS/Security errors
ETLS: 'TLS handshake or STARTTLS failed',
EREQUIRETLS: 'REQUIRETLS not supported by server (RFC 8689)',
// Protocol errors
EPROTOCOL: 'Invalid SMTP server response',
EENVELOPE: 'Invalid mail envelope (sender or recipients)',
EMESSAGE: 'Message delivery error',
ESTREAM: 'Stream processing error',
// Authentication errors
EAUTH: 'Authentication failed',
ENOAUTH: 'Authentication credentials not provided',
EOAUTH2: 'OAuth2 token generation or refresh error',
// Resource errors
EMAXLIMIT: 'Pool resource limit reached (max messages per connection)',
// Transport-specific errors
ESENDMAIL: 'Sendmail command error',
ESES: 'AWS SES transport error',
// Configuration and access errors
ECONFIG: 'Invalid configuration',
EPROXY: 'Proxy connection error',
EFILEACCESS: 'File access rejected (disableFileAccess is set)',
EURLACCESS: 'URL access rejected (disableUrlAccess is set)',
EFETCH: 'HTTP fetch error'
};
// Export error codes as string constants and the full definitions object
module.exports = Object.keys(ERROR_CODES).reduce(
(exports, code) => {
exports[code] = code;
return exports;
},
{ ERROR_CODES }
);

View File

@@ -8,6 +8,7 @@ const PassThrough = require('stream').PassThrough;
const Cookies = require('./cookies'); const Cookies = require('./cookies');
const packageData = require('../../package.json'); const packageData = require('../../package.json');
const net = require('net'); const net = require('net');
const errors = require('../errors');
const MAX_REDIRECTS = 5; const MAX_REDIRECTS = 5;
@@ -76,7 +77,7 @@ function nmfetch(url, options) {
return; return;
} }
finished = true; finished = true;
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
}); });
@@ -99,7 +100,7 @@ function nmfetch(url, options) {
return; return;
} }
finished = true; finished = true;
E.type = 'FETCH'; E.code = errors.EFETCH;
E.sourceUrl = url; E.sourceUrl = url;
fetchRes.emit('error', E); fetchRes.emit('error', E);
return; return;
@@ -147,7 +148,7 @@ function nmfetch(url, options) {
} catch (E) { } catch (E) {
finished = true; finished = true;
setImmediate(() => { setImmediate(() => {
E.type = 'FETCH'; E.code = errors.EFETCH;
E.sourceUrl = url; E.sourceUrl = url;
fetchRes.emit('error', E); fetchRes.emit('error', E);
}); });
@@ -162,7 +163,7 @@ function nmfetch(url, options) {
finished = true; finished = true;
req.abort(); req.abort();
let err = new Error('Request Timeout'); let err = new Error('Request Timeout');
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
}); });
@@ -173,7 +174,7 @@ function nmfetch(url, options) {
return; return;
} }
finished = true; finished = true;
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
}); });
@@ -204,7 +205,7 @@ function nmfetch(url, options) {
if (options.redirects > options.maxRedirects) { if (options.redirects > options.maxRedirects) {
finished = true; finished = true;
let err = new Error('Maximum redirect count exceeded'); let err = new Error('Maximum redirect count exceeded');
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
req.abort(); req.abort();
@@ -222,7 +223,7 @@ function nmfetch(url, options) {
if (res.statusCode >= 300 && !options.allowErrorResponse) { if (res.statusCode >= 300 && !options.allowErrorResponse) {
finished = true; finished = true;
let err = new Error('Invalid status code ' + res.statusCode); let err = new Error('Invalid status code ' + res.statusCode);
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
req.abort(); req.abort();
@@ -234,7 +235,7 @@ function nmfetch(url, options) {
return; return;
} }
finished = true; finished = true;
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
req.abort(); req.abort();
@@ -247,7 +248,7 @@ function nmfetch(url, options) {
return; return;
} }
finished = true; finished = true;
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
req.abort(); req.abort();
@@ -267,7 +268,7 @@ function nmfetch(url, options) {
} }
} catch (err) { } catch (err) {
finished = true; finished = true;
err.type = 'FETCH'; err.code = errors.EFETCH;
err.sourceUrl = url; err.sourceUrl = url;
fetchRes.emit('error', err); fetchRes.emit('error', err);
return; return;

View File

@@ -6,6 +6,7 @@ const mimeTypes = require('../mime-funcs/mime-types');
const MailComposer = require('../mail-composer'); const MailComposer = require('../mail-composer');
const DKIM = require('../dkim'); const DKIM = require('../dkim');
const httpProxyClient = require('../smtp-connection/http-proxy-client'); const httpProxyClient = require('../smtp-connection/http-proxy-client');
const errors = require('../errors');
const util = require('util'); const util = require('util');
const urllib = require('url'); const urllib = require('url');
const packageData = require('../../package.json'); const packageData = require('../../package.json');
@@ -337,7 +338,9 @@ class Mail extends EventEmitter {
case 'socks4': case 'socks4':
case 'socks4a': { case 'socks4a': {
if (!this.meta.has('proxy_socks_module')) { if (!this.meta.has('proxy_socks_module')) {
return callback(new Error('Socks module not loaded')); let err = new Error('Socks module not loaded');
err.code = errors.EPROXY;
return callback(err);
} }
let connect = ipaddress => { let connect = ipaddress => {
let proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient; let proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient;
@@ -394,7 +397,9 @@ class Mail extends EventEmitter {
}); });
} }
} }
callback(new Error('Unknown proxy configuration')); let err = new Error('Unknown proxy configuration');
err.code = errors.EPROXY;
callback(err);
}; };
} }

View File

@@ -13,6 +13,7 @@ const qp = require('../qp');
const base64 = require('../base64'); const base64 = require('../base64');
const addressparser = require('../addressparser'); const addressparser = require('../addressparser');
const nmfetch = require('../fetch'); const nmfetch = require('../fetch');
const errors = require('../errors');
const LastNewline = require('./last-newline'); const LastNewline = require('./last-newline');
const LeWindows = require('./le-windows'); const LeWindows = require('./le-windows');
@@ -979,7 +980,11 @@ class MimeNode {
} else if (content && typeof content.path === 'string' && !content.href) { } else if (content && typeof content.path === 'string' && !content.href) {
if (this.disableFileAccess) { if (this.disableFileAccess) {
contentStream = new PassThrough(); contentStream = new PassThrough();
setImmediate(() => contentStream.emit('error', new Error('File access rejected for ' + content.path))); setImmediate(() => {
let err = new Error('File access rejected for ' + content.path);
err.code = errors.EFILEACCESS;
contentStream.emit('error', err);
});
return contentStream; return contentStream;
} }
// read file // read file
@@ -987,7 +992,11 @@ class MimeNode {
} else if (content && typeof content.href === 'string') { } else if (content && typeof content.href === 'string') {
if (this.disableUrlAccess) { if (this.disableUrlAccess) {
contentStream = new PassThrough(); contentStream = new PassThrough();
setImmediate(() => contentStream.emit('error', new Error('Url access rejected for ' + content.href))); setImmediate(() => {
let err = new Error('Url access rejected for ' + content.href);
err.code = errors.EURLACCESS;
contentStream.emit('error', err);
});
return contentStream; return contentStream;
} }
// fetch URL // fetch URL

View File

@@ -8,6 +8,7 @@ const SendmailTransport = require('./sendmail-transport');
const StreamTransport = require('./stream-transport'); const StreamTransport = require('./stream-transport');
const JSONTransport = require('./json-transport'); const JSONTransport = require('./json-transport');
const SESTransport = require('./ses-transport'); const SESTransport = require('./ses-transport');
const errors = require('./errors');
const nmfetch = require('./fetch'); const nmfetch = require('./fetch');
const packageData = require('../package.json'); const packageData = require('../package.json');
@@ -49,7 +50,7 @@ module.exports.createTransport = function (transporter, defaults) {
let error = new Error( let error = new Error(
'Using legacy SES configuration, expecting @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/' 'Using legacy SES configuration, expecting @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/'
); );
error.code = 'LegacyConfig'; error.code = errors.ECONFIG;
throw error; throw error;
} }
transporter = new SESTransport(options); transporter = new SESTransport(options);

View File

@@ -3,6 +3,7 @@
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const packageData = require('../../package.json'); const packageData = require('../../package.json');
const shared = require('../shared'); const shared = require('../shared');
const errors = require('../errors');
/** /**
* Generates a Transport object for Sendmail * Generates a Transport object for Sendmail
@@ -72,7 +73,9 @@ class SendmailTransport {
.concat(envelope.to || []) .concat(envelope.to || [])
.some(addr => /^-/.test(addr)); .some(addr => /^-/.test(addr));
if (hasInvalidAddresses) { if (hasInvalidAddresses) {
return done(new Error('Can not send mail. Invalid envelope addresses.')); let err = new Error('Can not send mail. Invalid envelope addresses.');
err.code = errors.ESENDMAIL;
return done(err);
} }
if (this.args) { if (this.args) {
@@ -141,6 +144,7 @@ class SendmailTransport {
} else { } else {
err = new Error('Sendmail exited with code ' + code); err = new Error('Sendmail exited with code ' + code);
} }
err.code = errors.ESENDMAIL;
this.logger.error( this.logger.error(
{ {
@@ -202,7 +206,9 @@ class SendmailTransport {
sourceStream.pipe(sendmail.stdin); sourceStream.pipe(sendmail.stdin);
} else { } else {
return callback(new Error('sendmail was not found')); let err = new Error('sendmail was not found');
err.code = errors.ESENDMAIL;
return callback(err);
} }
} }
} }

View File

@@ -82,15 +82,22 @@ const formatDNSValue = (value, extra) => {
return Object.assign({}, extra || {}); return Object.assign({}, extra || {});
} }
let addresses = value.addresses || [];
// Select a random address from available addresses, or null if none
let host = null;
if (addresses.length === 1) {
host = addresses[0];
} else if (addresses.length > 1) {
host = addresses[Math.floor(Math.random() * addresses.length)];
}
return Object.assign( return Object.assign(
{ {
servername: value.servername, servername: value.servername,
host: host,
!value.addresses || !value.addresses.length // Include all addresses for connection fallback support
? null _addresses: addresses
: value.addresses.length === 1
? value.addresses[0]
: value.addresses[Math.floor(Math.random() * value.addresses.length)]
}, },
extra || {} extra || {}
); );
@@ -151,66 +158,32 @@ module.exports.resolveHostname = (options, callback) => {
} }
} }
// Resolve both IPv4 and IPv6 addresses for fallback support
let ipv4Addresses = [];
let ipv6Addresses = [];
let ipv4Error = null;
let ipv6Error = null;
resolver(4, options.host, options, (err, addresses) => { resolver(4, options.host, options, (err, addresses) => {
if (err) { if (err) {
if (cached) { ipv4Error = err;
dnsCache.set(options.host, { } else {
value: cached.value, ipv4Addresses = addresses || [];
expires: Date.now() + (options.dnsTtl || DNS_TTL)
});
return callback(
null,
formatDNSValue(cached.value, {
cached: true,
error: err
})
);
}
return callback(err);
}
if (addresses && addresses.length) {
let value = {
addresses,
servername: options.servername || options.host
};
dnsCache.set(options.host, {
value,
expires: Date.now() + (options.dnsTtl || DNS_TTL)
});
return callback(
null,
formatDNSValue(value, {
cached: false
})
);
} }
resolver(6, options.host, options, (err, addresses) => { resolver(6, options.host, options, (err, addresses) => {
if (err) { if (err) {
if (cached) { ipv6Error = err;
dnsCache.set(options.host, { } else {
value: cached.value, ipv6Addresses = addresses || [];
expires: Date.now() + (options.dnsTtl || DNS_TTL)
});
return callback(
null,
formatDNSValue(cached.value, {
cached: true,
error: err
})
);
}
return callback(err);
} }
if (addresses && addresses.length) { // Combine addresses: IPv4 first, then IPv6
let allAddresses = ipv4Addresses.concat(ipv6Addresses);
if (allAddresses.length) {
let value = { let value = {
addresses, addresses: allAddresses,
servername: options.servername || options.host servername: options.servername || options.host
}; };
@@ -227,6 +200,25 @@ module.exports.resolveHostname = (options, callback) => {
); );
} }
// No addresses from resolve4/resolve6, try dns.lookup as fallback
if (ipv4Error && ipv6Error) {
// Both resolvers had errors
if (cached) {
dnsCache.set(options.host, {
value: cached.value,
expires: Date.now() + (options.dnsTtl || DNS_TTL)
});
return callback(
null,
formatDNSValue(cached.value, {
cached: true,
error: ipv4Error
})
);
}
}
try { try {
dns.lookup(options.host, { all: true }, (err, addresses) => { dns.lookup(options.host, { all: true }, (err, addresses) => {
if (err) { if (err) {
@@ -247,19 +239,17 @@ module.exports.resolveHostname = (options, callback) => {
return callback(err); return callback(err);
} }
let address = addresses // Get all supported addresses from dns.lookup
? addresses let supportedAddresses = addresses
.filter(addr => isFamilySupported(addr.family)) ? addresses.filter(addr => isFamilySupported(addr.family)).map(addr => addr.address)
.map(addr => addr.address) : [];
.shift()
: false;
if (addresses && addresses.length && !address) { if (addresses && addresses.length && !supportedAddresses.length) {
// there are addresses but none can be used // there are addresses but none can be used
console.warn(`Failed to resolve IPv${addresses[0].family} addresses with current network`); console.warn(`Failed to resolve IPv${addresses[0].family} addresses with current network`);
} }
if (!address && cached) { if (!supportedAddresses.length && cached) {
// nothing was found, fallback to cached value // nothing was found, fallback to cached value
return callback( return callback(
null, null,
@@ -270,7 +260,7 @@ module.exports.resolveHostname = (options, callback) => {
} }
let value = { let value = {
addresses: address ? [address] : [options.host], addresses: supportedAddresses.length ? supportedAddresses : [options.host],
servername: options.servername || options.host servername: options.servername || options.host
}; };
@@ -286,7 +276,7 @@ module.exports.resolveHostname = (options, callback) => {
}) })
); );
}); });
} catch (_err) { } catch (lookupErr) {
if (cached) { if (cached) {
dnsCache.set(options.host, { dnsCache.set(options.host, {
value: cached.value, value: cached.value,
@@ -297,11 +287,11 @@ module.exports.resolveHostname = (options, callback) => {
null, null,
formatDNSValue(cached.value, { formatDNSValue(cached.value, {
cached: true, cached: true,
error: err error: lookupErr
}) })
); );
} }
return callback(err); return callback(ipv4Error || ipv6Error || lookupErr);
} }
}); });
}); });

View File

@@ -7,6 +7,7 @@
const net = require('net'); const net = require('net');
const tls = require('tls'); const tls = require('tls');
const urllib = require('url'); const urllib = require('url');
const errors = require('../errors');
/** /**
* Establishes proxied connection to destinationPort * Establishes proxied connection to destinationPort
@@ -121,7 +122,9 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
} catch (_E) { } catch (_E) {
// ignore // ignore
} }
return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''))); let err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
err.code = errors.EPROXY;
return callback(err);
} }
socket.removeListener('error', tempSocketErr); socket.removeListener('error', tempSocketErr);

View File

@@ -197,6 +197,17 @@ class SMTPConnection extends EventEmitter {
this._onSocketClose = () => this._onClose(); this._onSocketClose = () => this._onClose();
this._onSocketEnd = () => this._onEnd(); this._onSocketEnd = () => this._onEnd();
this._onSocketTimeout = () => this._onTimeout(); this._onSocketTimeout = () => this._onTimeout();
/**
* Connection-phase error handler (supports fallback to alternative addresses)
*/
this._onConnectionSocketError = err => this._onConnectionError(err, 'ESOCKET');
/**
* Connection attempt counter for fallback race condition protection
* @private
*/
this._connectionAttemptId = 0;
} }
/** /**
@@ -232,18 +243,10 @@ class SMTPConnection extends EventEmitter {
opts.localAddress = this.options.localAddress; opts.localAddress = this.options.localAddress;
} }
let setupConnectionHandlers = () => {
this._connectionTimeout = setTimeout(() => {
this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
}, this.options.connectionTimeout || CONNECTION_TIMEOUT);
this._socket.on('error', this._onSocketError);
};
if (this.options.connection) { if (this.options.connection) {
// connection is already opened // connection is already opened
this._socket = this.options.connection; this._socket = this.options.connection;
setupConnectionHandlers(); this._setupConnectionHandlers();
if (this.secureConnection && !this.alreadySecured) { if (this.secureConnection && !this.alreadySecured) {
setImmediate(() => setImmediate(() =>
@@ -288,7 +291,7 @@ class SMTPConnection extends EventEmitter {
this._socket.setKeepAlive(true); this._socket.setKeepAlive(true);
this._onConnect(); this._onConnect();
}); });
setupConnectionHandlers(); this._setupConnectionHandlers();
} catch (E) { } catch (E) {
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN')); return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
} }
@@ -327,15 +330,12 @@ class SMTPConnection extends EventEmitter {
opts[key] = resolved[key]; opts[key] = resolved[key];
} }
}); });
try {
this._socket = tls.connect(opts, () => { // Store fallback addresses for retry on connection failure
this._socket.setKeepAlive(true); this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
this._onConnect(); this._connectOpts = Object.assign({}, opts);
});
setupConnectionHandlers(); this._connectToHost(opts, true);
} catch (E) {
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
}
}); });
} else { } else {
// connect using plaintext // connect using plaintext
@@ -360,19 +360,101 @@ class SMTPConnection extends EventEmitter {
opts[key] = resolved[key]; opts[key] = resolved[key];
} }
}); });
try {
this._socket = net.connect(opts, () => { // Store fallback addresses for retry on connection failure
this._socket.setKeepAlive(true); this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
this._onConnect(); this._connectOpts = Object.assign({}, opts);
});
setupConnectionHandlers(); this._connectToHost(opts, false);
} catch (E) {
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
}
}); });
} }
} }
/**
* Attempts to connect to the specified host address
*
* @param {Object} opts Connection options
* @param {Boolean} secure Whether to use TLS
*/
_connectToHost(opts, secure) {
this._connectionAttemptId++;
const currentAttemptId = this._connectionAttemptId;
let connectFn = secure ? tls.connect : net.connect;
try {
this._socket = connectFn(opts, () => {
// Ignore callback if this is a stale connection attempt
if (this._connectionAttemptId !== currentAttemptId) {
return;
}
this._socket.setKeepAlive(true);
this._onConnect();
});
this._setupConnectionHandlers();
} catch (E) {
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
}
}
/**
* Sets up connection timeout and error handlers
*/
_setupConnectionHandlers() {
this._connectionTimeout = setTimeout(() => {
this._onConnectionError('Connection timeout', 'ETIMEDOUT');
}, this.options.connectionTimeout || CONNECTION_TIMEOUT);
this._socket.on('error', this._onConnectionSocketError);
}
/**
* Handles connection errors with fallback to alternative addresses
*
* @param {Error|String} err Error object or message
* @param {String} code Error code
*/
_onConnectionError(err, code) {
clearTimeout(this._connectionTimeout);
// Check if we have fallback addresses to try
let canFallback = this._fallbackAddresses && this._fallbackAddresses.length && this.stage === 'init' && !this._destroyed;
if (!canFallback) {
// No more fallback addresses, report the error
this._onError(err, code, false, 'CONN');
return;
}
let nextHost = this._fallbackAddresses.shift();
this.logger.info(
{
tnx: 'network',
failedHost: this._connectOpts.host,
nextHost,
error: err.message || err
},
'Connection to %s failed, trying %s',
this._connectOpts.host,
nextHost
);
// Clean up current socket
if (this._socket) {
try {
this._socket.removeListener('error', this._onConnectionSocketError);
this._socket.destroy();
} catch (_E) {
// ignore
}
this._socket = null;
}
// Update host and retry
this._connectOpts.host = nextHost;
this._connectToHost(this._connectOpts, this.secureConnection);
}
/** /**
* Sends QUIT * Sends QUIT
*/ */
@@ -414,6 +496,15 @@ class SMTPConnection extends EventEmitter {
if (socket && !socket.destroyed) { if (socket && !socket.destroyed) {
try { try {
// Clear socket timeout to prevent timer leaks
socket.setTimeout(0);
// Remove all listeners to allow proper garbage collection
socket.removeListener('data', this._onSocketData);
socket.removeListener('timeout', this._onSocketTimeout);
socket.removeListener('close', this._onSocketClose);
socket.removeListener('end', this._onSocketEnd);
socket.removeListener('error', this._onSocketError);
socket.removeListener('error', this._onConnectionSocketError);
socket[closeMethod](); socket[closeMethod]();
} catch (_E) { } catch (_E) {
// just ignore // just ignore
@@ -715,7 +806,10 @@ class SMTPConnection extends EventEmitter {
this._socket.removeListener('timeout', this._onSocketTimeout); this._socket.removeListener('timeout', this._onSocketTimeout);
this._socket.removeListener('close', this._onSocketClose); this._socket.removeListener('close', this._onSocketClose);
this._socket.removeListener('end', this._onSocketEnd); this._socket.removeListener('end', this._onSocketEnd);
// Switch from connection-phase error handler to normal error handler
this._socket.removeListener('error', this._onConnectionSocketError);
this._socket.on('error', this._onSocketError);
this._socket.on('data', this._onSocketData); this._socket.on('data', this._onSocketData);
this._socket.once('close', this._onSocketClose); this._socket.once('close', this._onSocketClose);
this._socket.once('end', this._onSocketEnd); this._socket.once('end', this._onSocketEnd);
@@ -941,8 +1035,10 @@ class SMTPConnection extends EventEmitter {
this.upgrading = false; this.upgrading = false;
this._socket.on('data', this._onSocketData); this._socket.on('data', this._onSocketData);
// Remove all listeners from the plain socket to allow proper garbage collection
socketPlain.removeListener('close', this._onSocketClose); socketPlain.removeListener('close', this._onSocketClose);
socketPlain.removeListener('end', this._onSocketEnd); socketPlain.removeListener('end', this._onSocketEnd);
socketPlain.removeListener('error', this._onSocketError);
return callback(null, true); return callback(null, true);
}); });

View File

@@ -5,6 +5,7 @@ const PoolResource = require('./pool-resource');
const SMTPConnection = require('../smtp-connection'); const SMTPConnection = require('../smtp-connection');
const wellKnown = require('../well-known'); const wellKnown = require('../well-known');
const shared = require('../shared'); const shared = require('../shared');
const errors = require('../errors');
const packageData = require('../../package.json'); const packageData = require('../../package.json');
/** /**
@@ -633,7 +634,7 @@ class SMTPPool extends EventEmitter {
}); });
} else if (!auth && connection.allowsAuth && options.forceAuth) { } else if (!auth && connection.allowsAuth && options.forceAuth) {
let err = new Error('Authentication info was not provided'); let err = new Error('Authentication info was not provided');
err.code = 'NoAuth'; err.code = errors.ENOAUTH;
returned = true; returned = true;
connection.close(); connection.close();

View File

@@ -3,6 +3,7 @@
const SMTPConnection = require('../smtp-connection'); const SMTPConnection = require('../smtp-connection');
const assign = require('../shared').assign; const assign = require('../shared').assign;
const XOAuth2 = require('../xoauth2'); const XOAuth2 = require('../xoauth2');
const errors = require('../errors');
const EventEmitter = require('events'); const EventEmitter = require('events');
/** /**
@@ -121,7 +122,7 @@ class PoolResource extends EventEmitter {
let err = new Error('Unexpected socket close'); let err = new Error('Unexpected socket close');
if (this.connection && this.connection._socket && this.connection._socket.upgrading) { if (this.connection && this.connection._socket && this.connection._socket.upgrading) {
// starttls connection errors // starttls connection errors
err.code = 'ETLS'; err.code = errors.ETLS;
} }
callback(err); callback(err);
}, 1000); }, 1000);
@@ -226,7 +227,7 @@ class PoolResource extends EventEmitter {
let err; let err;
if (this.messages >= this.options.maxMessages) { if (this.messages >= this.options.maxMessages) {
err = new Error('Resource exhausted'); err = new Error('Resource exhausted');
err.code = 'EMAXLIMIT'; err.code = errors.EMAXLIMIT;
this.connection.close(); this.connection.close();
this.emit('error', err); this.emit('error', err);
} else { } else {

View File

@@ -5,6 +5,7 @@ const SMTPConnection = require('../smtp-connection');
const wellKnown = require('../well-known'); const wellKnown = require('../well-known');
const shared = require('../shared'); const shared = require('../shared');
const XOAuth2 = require('../xoauth2'); const XOAuth2 = require('../xoauth2');
const errors = require('../errors');
const packageData = require('../../package.json'); const packageData = require('../../package.json');
/** /**
@@ -190,7 +191,7 @@ class SMTPTransport extends EventEmitter {
let err = new Error('Unexpected socket close'); let err = new Error('Unexpected socket close');
if (connection && connection._socket && connection._socket.upgrading) { if (connection && connection._socket && connection._socket.upgrading) {
// starttls connection errors // starttls connection errors
err.code = 'ETLS'; err.code = errors.ETLS;
} }
callback(err); callback(err);
}, 1000); }, 1000);
@@ -392,7 +393,7 @@ class SMTPTransport extends EventEmitter {
}); });
} else if (!authData && connection.allowsAuth && options.forceAuth) { } else if (!authData && connection.allowsAuth && options.forceAuth) {
let err = new Error('Authentication info was not provided'); let err = new Error('Authentication info was not provided');
err.code = 'NoAuth'; err.code = errors.ENOAUTH;
returned = true; returned = true;
connection.close(); connection.close();

View File

@@ -4,6 +4,7 @@ const Stream = require('stream').Stream;
const nmfetch = require('../fetch'); const nmfetch = require('../fetch');
const crypto = require('crypto'); const crypto = require('crypto');
const shared = require('../shared'); const shared = require('../shared');
const errors = require('../errors');
/** /**
* XOAUTH2 access_token generator for Gmail. * XOAUTH2 access_token generator for Gmail.
@@ -41,7 +42,9 @@ class XOAuth2 extends Stream {
if (options && options.serviceClient) { if (options && options.serviceClient) {
if (!options.privateKey || !options.user) { if (!options.privateKey || !options.user) {
setImmediate(() => this.emit('error', new Error('Options "privateKey" and "user" are required for service account!'))); let err = new Error('Options "privateKey" and "user" are required for service account!');
err.code = errors.EOAUTH2;
setImmediate(() => this.emit('error', err));
return; return;
} }
@@ -120,7 +123,9 @@ class XOAuth2 extends Stream {
'Cannot renew access token for %s: No refresh mechanism available', 'Cannot renew access token for %s: No refresh mechanism available',
this.options.user this.options.user
); );
return callback(new Error("Can't create new access token for user")); let err = new Error("Can't create new access token for user");
err.code = errors.EOAUTH2;
return callback(err);
} }
// If renewal already in progress, queue this request instead of starting another // If renewal already in progress, queue this request instead of starting another
@@ -218,7 +223,9 @@ class XOAuth2 extends Stream {
try { try {
token = this.jwtSignRS256(tokenData); token = this.jwtSignRS256(tokenData);
} catch (_err) { } catch (_err) {
return callback(new Error("Can't generate token. Check your auth options")); let err = new Error("Can't generate token. Check your auth options");
err.code = errors.EOAUTH2;
return callback(err);
} }
urlOptions = { urlOptions = {
@@ -232,7 +239,9 @@ class XOAuth2 extends Stream {
}; };
} else { } else {
if (!this.options.refreshToken) { if (!this.options.refreshToken) {
return callback(new Error("Can't create new access token for user")); let err = new Error("Can't create new access token for user");
err.code = errors.EOAUTH2;
return callback(err);
} }
// web app - https://developers.google.com/identity/protocols/OAuth2WebServer // web app - https://developers.google.com/identity/protocols/OAuth2WebServer
@@ -289,7 +298,9 @@ class XOAuth2 extends Stream {
'Response: %s', 'Response: %s',
(body || '').toString() (body || '').toString()
); );
return callback(new Error('Invalid authentication response')); let err = new Error('Invalid authentication response');
err.code = errors.EOAUTH2;
return callback(err);
} }
let logData = {}; let logData = {};
@@ -320,7 +331,9 @@ class XOAuth2 extends Stream {
if (data.error_uri) { if (data.error_uri) {
errorMessage += ' (' + data.error_uri + ')'; errorMessage += ' (' + data.error_uri + ')';
} }
return callback(new Error(errorMessage)); let err = new Error(errorMessage);
err.code = errors.EOAUTH2;
return callback(err);
} }
if (data.access_token) { if (data.access_token) {
@@ -328,7 +341,9 @@ class XOAuth2 extends Stream {
return callback(null, this.accessToken); return callback(null, this.accessToken);
} }
return callback(new Error('No access token')); let err = new Error('No access token');
err.code = errors.EOAUTH2;
return callback(err);
}); });
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "nodemailer", "name": "nodemailer",
"version": "7.0.13", "version": "8.0.0",
"description": "Easy as cake e-mail sending from your Node.js applications", "description": "Easy as cake e-mail sending from your Node.js applications",
"main": "lib/nodemailer.js", "main": "lib/nodemailer.js",
"scripts": { "scripts": {