From 62a2d05b79935ad4fb90ce9079928099579c14ac Mon Sep 17 00:00:00 2001 From: Dawid Dziurla Date: Thu, 5 Feb 2026 08:49:11 +0100 Subject: [PATCH] node_modules: update (#259) Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com> --- node_modules/.package-lock.json | 6 +- node_modules/nodemailer/CHANGELOG.md | 14 ++ node_modules/nodemailer/lib/errors.js | 61 +++++++ node_modules/nodemailer/lib/fetch/index.js | 21 +-- node_modules/nodemailer/lib/mailer/index.js | 9 +- .../nodemailer/lib/mime-node/index.js | 13 +- node_modules/nodemailer/lib/nodemailer.js | 3 +- .../lib/sendmail-transport/index.js | 10 +- node_modules/nodemailer/lib/shared/index.js | 128 +++++++-------- .../lib/smtp-connection/http-proxy-client.js | 5 +- .../nodemailer/lib/smtp-connection/index.js | 152 ++++++++++++++---- .../nodemailer/lib/smtp-pool/index.js | 3 +- .../nodemailer/lib/smtp-pool/pool-resource.js | 5 +- .../nodemailer/lib/smtp-transport/index.js | 5 +- node_modules/nodemailer/lib/xoauth2/index.js | 29 +++- node_modules/nodemailer/package.json | 2 +- 16 files changed, 335 insertions(+), 131 deletions(-) create mode 100644 node_modules/nodemailer/lib/errors.js diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 18580b6c..723da14a 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -197,9 +197,9 @@ } }, "node_modules/nodemailer": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", - "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.0.tgz", + "integrity": "sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==", "license": "MIT-0", "engines": { "node": ">=6.0.0" diff --git a/node_modules/nodemailer/CHANGELOG.md b/node_modules/nodemailer/CHANGELOG.md index 216b96d5..2557c31a 100644 --- a/node_modules/nodemailer/CHANGELOG.md +++ b/node_modules/nodemailer/CHANGELOG.md @@ -1,5 +1,19 @@ # 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) diff --git a/node_modules/nodemailer/lib/errors.js b/node_modules/nodemailer/lib/errors.js new file mode 100644 index 00000000..86177f0a --- /dev/null +++ b/node_modules/nodemailer/lib/errors.js @@ -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 } +); diff --git a/node_modules/nodemailer/lib/fetch/index.js b/node_modules/nodemailer/lib/fetch/index.js index 3c918ce4..3593a3dc 100644 --- a/node_modules/nodemailer/lib/fetch/index.js +++ b/node_modules/nodemailer/lib/fetch/index.js @@ -8,6 +8,7 @@ const PassThrough = require('stream').PassThrough; const Cookies = require('./cookies'); const packageData = require('../../package.json'); const net = require('net'); +const errors = require('../errors'); const MAX_REDIRECTS = 5; @@ -76,7 +77,7 @@ function nmfetch(url, options) { return; } finished = true; - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); }); @@ -99,7 +100,7 @@ function nmfetch(url, options) { return; } finished = true; - E.type = 'FETCH'; + E.code = errors.EFETCH; E.sourceUrl = url; fetchRes.emit('error', E); return; @@ -147,7 +148,7 @@ function nmfetch(url, options) { } catch (E) { finished = true; setImmediate(() => { - E.type = 'FETCH'; + E.code = errors.EFETCH; E.sourceUrl = url; fetchRes.emit('error', E); }); @@ -162,7 +163,7 @@ function nmfetch(url, options) { finished = true; req.abort(); let err = new Error('Request Timeout'); - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); }); @@ -173,7 +174,7 @@ function nmfetch(url, options) { return; } finished = true; - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); }); @@ -204,7 +205,7 @@ function nmfetch(url, options) { if (options.redirects > options.maxRedirects) { finished = true; let err = new Error('Maximum redirect count exceeded'); - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); req.abort(); @@ -222,7 +223,7 @@ function nmfetch(url, options) { if (res.statusCode >= 300 && !options.allowErrorResponse) { finished = true; let err = new Error('Invalid status code ' + res.statusCode); - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); req.abort(); @@ -234,7 +235,7 @@ function nmfetch(url, options) { return; } finished = true; - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); req.abort(); @@ -247,7 +248,7 @@ function nmfetch(url, options) { return; } finished = true; - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); req.abort(); @@ -267,7 +268,7 @@ function nmfetch(url, options) { } } catch (err) { finished = true; - err.type = 'FETCH'; + err.code = errors.EFETCH; err.sourceUrl = url; fetchRes.emit('error', err); return; diff --git a/node_modules/nodemailer/lib/mailer/index.js b/node_modules/nodemailer/lib/mailer/index.js index 6da313e7..983c71c2 100644 --- a/node_modules/nodemailer/lib/mailer/index.js +++ b/node_modules/nodemailer/lib/mailer/index.js @@ -6,6 +6,7 @@ const mimeTypes = require('../mime-funcs/mime-types'); const MailComposer = require('../mail-composer'); const DKIM = require('../dkim'); const httpProxyClient = require('../smtp-connection/http-proxy-client'); +const errors = require('../errors'); const util = require('util'); const urllib = require('url'); const packageData = require('../../package.json'); @@ -337,7 +338,9 @@ class Mail extends EventEmitter { case 'socks4': case 'socks4a': { 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 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); }; } diff --git a/node_modules/nodemailer/lib/mime-node/index.js b/node_modules/nodemailer/lib/mime-node/index.js index d8155437..30c9d5c6 100644 --- a/node_modules/nodemailer/lib/mime-node/index.js +++ b/node_modules/nodemailer/lib/mime-node/index.js @@ -13,6 +13,7 @@ const qp = require('../qp'); const base64 = require('../base64'); const addressparser = require('../addressparser'); const nmfetch = require('../fetch'); +const errors = require('../errors'); const LastNewline = require('./last-newline'); const LeWindows = require('./le-windows'); @@ -979,7 +980,11 @@ class MimeNode { } else if (content && typeof content.path === 'string' && !content.href) { if (this.disableFileAccess) { 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; } // read file @@ -987,7 +992,11 @@ class MimeNode { } else if (content && typeof content.href === 'string') { if (this.disableUrlAccess) { 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; } // fetch URL diff --git a/node_modules/nodemailer/lib/nodemailer.js b/node_modules/nodemailer/lib/nodemailer.js index d8dcdb9b..c763ed7a 100644 --- a/node_modules/nodemailer/lib/nodemailer.js +++ b/node_modules/nodemailer/lib/nodemailer.js @@ -8,6 +8,7 @@ const SendmailTransport = require('./sendmail-transport'); const StreamTransport = require('./stream-transport'); const JSONTransport = require('./json-transport'); const SESTransport = require('./ses-transport'); +const errors = require('./errors'); const nmfetch = require('./fetch'); const packageData = require('../package.json'); @@ -49,7 +50,7 @@ module.exports.createTransport = function (transporter, defaults) { let error = new Error( 'Using legacy SES configuration, expecting @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/' ); - error.code = 'LegacyConfig'; + error.code = errors.ECONFIG; throw error; } transporter = new SESTransport(options); diff --git a/node_modules/nodemailer/lib/sendmail-transport/index.js b/node_modules/nodemailer/lib/sendmail-transport/index.js index b1cc13b0..7a2a9bc2 100644 --- a/node_modules/nodemailer/lib/sendmail-transport/index.js +++ b/node_modules/nodemailer/lib/sendmail-transport/index.js @@ -3,6 +3,7 @@ const spawn = require('child_process').spawn; const packageData = require('../../package.json'); const shared = require('../shared'); +const errors = require('../errors'); /** * Generates a Transport object for Sendmail @@ -72,7 +73,9 @@ class SendmailTransport { .concat(envelope.to || []) .some(addr => /^-/.test(addr)); 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) { @@ -141,6 +144,7 @@ class SendmailTransport { } else { err = new Error('Sendmail exited with code ' + code); } + err.code = errors.ESENDMAIL; this.logger.error( { @@ -202,7 +206,9 @@ class SendmailTransport { sourceStream.pipe(sendmail.stdin); } else { - return callback(new Error('sendmail was not found')); + let err = new Error('sendmail was not found'); + err.code = errors.ESENDMAIL; + return callback(err); } } } diff --git a/node_modules/nodemailer/lib/shared/index.js b/node_modules/nodemailer/lib/shared/index.js index d6fced67..c86dd8f6 100644 --- a/node_modules/nodemailer/lib/shared/index.js +++ b/node_modules/nodemailer/lib/shared/index.js @@ -82,15 +82,22 @@ const formatDNSValue = (value, 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( { servername: value.servername, - host: - !value.addresses || !value.addresses.length - ? null - : value.addresses.length === 1 - ? value.addresses[0] - : value.addresses[Math.floor(Math.random() * value.addresses.length)] + host, + // Include all addresses for connection fallback support + _addresses: addresses }, 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) => { if (err) { - 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: 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 - }) - ); + ipv4Error = err; + } else { + ipv4Addresses = addresses || []; } resolver(6, options.host, options, (err, addresses) => { if (err) { - 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: err - }) - ); - } - return callback(err); + ipv6Error = err; + } else { + ipv6Addresses = addresses || []; } - if (addresses && addresses.length) { + // Combine addresses: IPv4 first, then IPv6 + let allAddresses = ipv4Addresses.concat(ipv6Addresses); + + if (allAddresses.length) { let value = { - addresses, + addresses: allAddresses, 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 { dns.lookup(options.host, { all: true }, (err, addresses) => { if (err) { @@ -247,19 +239,17 @@ module.exports.resolveHostname = (options, callback) => { return callback(err); } - let address = addresses - ? addresses - .filter(addr => isFamilySupported(addr.family)) - .map(addr => addr.address) - .shift() - : false; + // Get all supported addresses from dns.lookup + let supportedAddresses = addresses + ? addresses.filter(addr => isFamilySupported(addr.family)).map(addr => addr.address) + : []; - if (addresses && addresses.length && !address) { + if (addresses && addresses.length && !supportedAddresses.length) { // there are addresses but none can be used 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 return callback( null, @@ -270,7 +260,7 @@ module.exports.resolveHostname = (options, callback) => { } let value = { - addresses: address ? [address] : [options.host], + addresses: supportedAddresses.length ? supportedAddresses : [options.host], servername: options.servername || options.host }; @@ -286,7 +276,7 @@ module.exports.resolveHostname = (options, callback) => { }) ); }); - } catch (_err) { + } catch (lookupErr) { if (cached) { dnsCache.set(options.host, { value: cached.value, @@ -297,11 +287,11 @@ module.exports.resolveHostname = (options, callback) => { null, formatDNSValue(cached.value, { cached: true, - error: err + error: lookupErr }) ); } - return callback(err); + return callback(ipv4Error || ipv6Error || lookupErr); } }); }); diff --git a/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js b/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js index 64819b96..64b0c084 100644 --- a/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +++ b/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js @@ -7,6 +7,7 @@ const net = require('net'); const tls = require('tls'); const urllib = require('url'); +const errors = require('../errors'); /** * Establishes proxied connection to destinationPort @@ -121,7 +122,9 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) { } catch (_E) { // 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); diff --git a/node_modules/nodemailer/lib/smtp-connection/index.js b/node_modules/nodemailer/lib/smtp-connection/index.js index 8e9577d3..cf5cfdbe 100644 --- a/node_modules/nodemailer/lib/smtp-connection/index.js +++ b/node_modules/nodemailer/lib/smtp-connection/index.js @@ -197,6 +197,17 @@ class SMTPConnection extends EventEmitter { this._onSocketClose = () => this._onClose(); this._onSocketEnd = () => this._onEnd(); 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; } - 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) { // connection is already opened this._socket = this.options.connection; - setupConnectionHandlers(); + this._setupConnectionHandlers(); if (this.secureConnection && !this.alreadySecured) { setImmediate(() => @@ -288,7 +291,7 @@ class SMTPConnection extends EventEmitter { this._socket.setKeepAlive(true); this._onConnect(); }); - setupConnectionHandlers(); + this._setupConnectionHandlers(); } catch (E) { return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN')); } @@ -327,15 +330,12 @@ class SMTPConnection extends EventEmitter { opts[key] = resolved[key]; } }); - try { - this._socket = tls.connect(opts, () => { - this._socket.setKeepAlive(true); - this._onConnect(); - }); - setupConnectionHandlers(); - } catch (E) { - return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN')); - } + + // Store fallback addresses for retry on connection failure + this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host); + this._connectOpts = Object.assign({}, opts); + + this._connectToHost(opts, true); }); } else { // connect using plaintext @@ -360,19 +360,101 @@ class SMTPConnection extends EventEmitter { opts[key] = resolved[key]; } }); - try { - this._socket = net.connect(opts, () => { - this._socket.setKeepAlive(true); - this._onConnect(); - }); - setupConnectionHandlers(); - } catch (E) { - return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN')); - } + + // Store fallback addresses for retry on connection failure + this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host); + this._connectOpts = Object.assign({}, opts); + + this._connectToHost(opts, false); }); } } + /** + * 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 */ @@ -414,6 +496,15 @@ class SMTPConnection extends EventEmitter { if (socket && !socket.destroyed) { 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](); } catch (_E) { // just ignore @@ -715,7 +806,10 @@ class SMTPConnection extends EventEmitter { this._socket.removeListener('timeout', this._onSocketTimeout); this._socket.removeListener('close', this._onSocketClose); 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.once('close', this._onSocketClose); this._socket.once('end', this._onSocketEnd); @@ -941,8 +1035,10 @@ class SMTPConnection extends EventEmitter { this.upgrading = false; 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('end', this._onSocketEnd); + socketPlain.removeListener('error', this._onSocketError); return callback(null, true); }); diff --git a/node_modules/nodemailer/lib/smtp-pool/index.js b/node_modules/nodemailer/lib/smtp-pool/index.js index 4c842e5f..2d8210ef 100644 --- a/node_modules/nodemailer/lib/smtp-pool/index.js +++ b/node_modules/nodemailer/lib/smtp-pool/index.js @@ -5,6 +5,7 @@ const PoolResource = require('./pool-resource'); const SMTPConnection = require('../smtp-connection'); const wellKnown = require('../well-known'); const shared = require('../shared'); +const errors = require('../errors'); const packageData = require('../../package.json'); /** @@ -633,7 +634,7 @@ class SMTPPool extends EventEmitter { }); } else if (!auth && connection.allowsAuth && options.forceAuth) { let err = new Error('Authentication info was not provided'); - err.code = 'NoAuth'; + err.code = errors.ENOAUTH; returned = true; connection.close(); diff --git a/node_modules/nodemailer/lib/smtp-pool/pool-resource.js b/node_modules/nodemailer/lib/smtp-pool/pool-resource.js index ffd66670..00531bec 100644 --- a/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +++ b/node_modules/nodemailer/lib/smtp-pool/pool-resource.js @@ -3,6 +3,7 @@ const SMTPConnection = require('../smtp-connection'); const assign = require('../shared').assign; const XOAuth2 = require('../xoauth2'); +const errors = require('../errors'); const EventEmitter = require('events'); /** @@ -121,7 +122,7 @@ class PoolResource extends EventEmitter { let err = new Error('Unexpected socket close'); if (this.connection && this.connection._socket && this.connection._socket.upgrading) { // starttls connection errors - err.code = 'ETLS'; + err.code = errors.ETLS; } callback(err); }, 1000); @@ -226,7 +227,7 @@ class PoolResource extends EventEmitter { let err; if (this.messages >= this.options.maxMessages) { err = new Error('Resource exhausted'); - err.code = 'EMAXLIMIT'; + err.code = errors.EMAXLIMIT; this.connection.close(); this.emit('error', err); } else { diff --git a/node_modules/nodemailer/lib/smtp-transport/index.js b/node_modules/nodemailer/lib/smtp-transport/index.js index d209af42..e0cbd71a 100644 --- a/node_modules/nodemailer/lib/smtp-transport/index.js +++ b/node_modules/nodemailer/lib/smtp-transport/index.js @@ -5,6 +5,7 @@ const SMTPConnection = require('../smtp-connection'); const wellKnown = require('../well-known'); const shared = require('../shared'); const XOAuth2 = require('../xoauth2'); +const errors = require('../errors'); const packageData = require('../../package.json'); /** @@ -190,7 +191,7 @@ class SMTPTransport extends EventEmitter { let err = new Error('Unexpected socket close'); if (connection && connection._socket && connection._socket.upgrading) { // starttls connection errors - err.code = 'ETLS'; + err.code = errors.ETLS; } callback(err); }, 1000); @@ -392,7 +393,7 @@ class SMTPTransport extends EventEmitter { }); } else if (!authData && connection.allowsAuth && options.forceAuth) { let err = new Error('Authentication info was not provided'); - err.code = 'NoAuth'; + err.code = errors.ENOAUTH; returned = true; connection.close(); diff --git a/node_modules/nodemailer/lib/xoauth2/index.js b/node_modules/nodemailer/lib/xoauth2/index.js index e4748c04..8c865c70 100644 --- a/node_modules/nodemailer/lib/xoauth2/index.js +++ b/node_modules/nodemailer/lib/xoauth2/index.js @@ -4,6 +4,7 @@ const Stream = require('stream').Stream; const nmfetch = require('../fetch'); const crypto = require('crypto'); const shared = require('../shared'); +const errors = require('../errors'); /** * XOAUTH2 access_token generator for Gmail. @@ -41,7 +42,9 @@ class XOAuth2 extends Stream { if (options && options.serviceClient) { 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; } @@ -120,7 +123,9 @@ class XOAuth2 extends Stream { 'Cannot renew access token for %s: No refresh mechanism available', 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 @@ -218,7 +223,9 @@ class XOAuth2 extends Stream { try { token = this.jwtSignRS256(tokenData); } 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 = { @@ -232,7 +239,9 @@ class XOAuth2 extends Stream { }; } else { 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 @@ -289,7 +298,9 @@ class XOAuth2 extends Stream { 'Response: %s', (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 = {}; @@ -320,7 +331,9 @@ class XOAuth2 extends Stream { if (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) { @@ -328,7 +341,9 @@ class XOAuth2 extends Stream { 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); }); } diff --git a/node_modules/nodemailer/package.json b/node_modules/nodemailer/package.json index abac570f..ad675bc4 100644 --- a/node_modules/nodemailer/package.json +++ b/node_modules/nodemailer/package.json @@ -1,6 +1,6 @@ { "name": "nodemailer", - "version": "7.0.13", + "version": "8.0.0", "description": "Easy as cake e-mail sending from your Node.js applications", "main": "lib/nodemailer.js", "scripts": {