mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-08-12 20:01:24 +07:00
node_modules: update (#307)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
+80
-22
@@ -298,6 +298,20 @@ class SMTPConnection extends EventEmitter {
|
||||
try {
|
||||
this._socket.connect(this.port, this.host, () => {
|
||||
this._socket.setKeepAlive(true);
|
||||
|
||||
// a `secure` connection over a caller-provided socket must still
|
||||
// perform the TLS handshake, otherwise AUTH and the message body
|
||||
// would be sent in cleartext despite the caller requesting TLS
|
||||
if (this.secureConnection && !this.alreadySecured) {
|
||||
return this._upgradeConnection(err => {
|
||||
if (err) {
|
||||
this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
|
||||
return;
|
||||
}
|
||||
this._onConnect();
|
||||
});
|
||||
}
|
||||
|
||||
this._onConnect();
|
||||
});
|
||||
this._setupConnectionHandlers();
|
||||
@@ -365,6 +379,14 @@ class SMTPConnection extends EventEmitter {
|
||||
* @param {Boolean} secure Whether to use TLS
|
||||
*/
|
||||
_connectToHost(opts, secure) {
|
||||
// If the client was closed while DNS resolution was in flight, do not open
|
||||
// a socket here: close() ran with this._socket still unset and so had
|
||||
// nothing to tear down, and _onConnect's remedial close() is a no-op once
|
||||
// _closing is set — the freshly connected socket would leak.
|
||||
if (this._destroyed || this._closing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._connectionAttemptId++;
|
||||
const currentAttemptId = this._connectionAttemptId;
|
||||
|
||||
@@ -431,6 +453,9 @@ class SMTPConnection extends EventEmitter {
|
||||
if (this._socket) {
|
||||
try {
|
||||
this._socket.removeListener('error', this._onConnectionSocketError);
|
||||
// Absorb any late teardown error (e.g. a TLS fallback socket emitting
|
||||
// after destroy), mirroring the guard used in close()
|
||||
this._socket.on('error', TEARDOWN_NOOP);
|
||||
this._socket.destroy();
|
||||
} catch (_E) {
|
||||
// ignore
|
||||
@@ -757,6 +782,11 @@ class SMTPConnection extends EventEmitter {
|
||||
* @param {Function} callback Callback to return once connection is reset
|
||||
*/
|
||||
reset(callback) {
|
||||
const isDestroyedMessage = this._isDestroyedMessage('reset');
|
||||
if (isDestroyedMessage) {
|
||||
return callback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
|
||||
}
|
||||
|
||||
this._sendCommand('RSET');
|
||||
this._responseActions.push(str => {
|
||||
if (str.charAt(0) !== '2') {
|
||||
@@ -805,6 +835,9 @@ class SMTPConnection extends EventEmitter {
|
||||
this._socket.removeListener('end', this._onSocketEnd);
|
||||
// Switch from connection-phase error handler to normal error handler
|
||||
this._socket.removeListener('error', this._onConnectionSocketError);
|
||||
// _upgradeConnection (options.connection + secure) may already have attached
|
||||
// the normal handler; remove it first so we never end up with a duplicate
|
||||
this._socket.removeListener('error', this._onSocketError);
|
||||
|
||||
this._socket.on('error', this._onSocketError);
|
||||
this._socket.on('data', this._onSocketData);
|
||||
@@ -994,6 +1027,8 @@ class SMTPConnection extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
this._destroyed = true;
|
||||
// keep the documented public flag in sync with the private state
|
||||
this.destroyed = true;
|
||||
this.emit('end');
|
||||
}
|
||||
|
||||
@@ -1004,6 +1039,15 @@ class SMTPConnection extends EventEmitter {
|
||||
* has been secured
|
||||
*/
|
||||
_upgradeConnection(callback) {
|
||||
// RFC 3207 section 6: the client MUST discard any knowledge obtained from
|
||||
// the server that was not received over the TLS-protected session. Drop any
|
||||
// buffered input received before the handshake so a man-in-the-middle cannot
|
||||
// inject plaintext bytes after the "220" reply (e.g. a CRLF-free fragment that
|
||||
// would otherwise be prepended to the first post-TLS response and parsed as
|
||||
// part of the secured EHLO capabilities). STARTTLS response injection.
|
||||
this._remainder = '';
|
||||
this._responseQueue = [];
|
||||
|
||||
// do not remove all listeners or it breaks node v0.10 as there's
|
||||
// apparently a 'finish' event set that would be cleared as well
|
||||
|
||||
@@ -1032,6 +1076,9 @@ class SMTPConnection extends EventEmitter {
|
||||
socketPlain.removeListener('close', this._onSocketClose);
|
||||
socketPlain.removeListener('end', this._onSocketEnd);
|
||||
socketPlain.removeListener('error', this._onSocketError);
|
||||
// the connection-phase handler is attached when upgrading a pre-opened
|
||||
// options.connection socket; strip it so nothing lingers on the plain socket
|
||||
socketPlain.removeListener('error', this._onConnectionSocketError);
|
||||
};
|
||||
|
||||
this.upgrading = true;
|
||||
@@ -1064,18 +1111,27 @@ class SMTPConnection extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Processes queued responses from the server
|
||||
*
|
||||
* @param {Boolean} force If true, ignores _processing flag
|
||||
*/
|
||||
_processResponse() {
|
||||
if (!this._responseQueue.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let str = (this.lastServerResponse = decodeServerResponse((this._responseQueue.shift() || '').toString()));
|
||||
const raw = (this._responseQueue.shift() || '').toString();
|
||||
|
||||
// Skip unexpected empty lines without consuming a response action or
|
||||
// overwriting lastServerResponse; reprocess whatever else is queued.
|
||||
if (!raw.trim()) {
|
||||
setImmediate(() => this._processResponse());
|
||||
return;
|
||||
}
|
||||
|
||||
let str = (this.lastServerResponse = decodeServerResponse(raw));
|
||||
|
||||
if (/^\d+-/.test(str.split('\n').pop())) {
|
||||
// keep waiting for the final part of multiline response
|
||||
// last line is still a continuation: put the partial response back on the
|
||||
// queue and wait for the rest rather than dropping it
|
||||
this._responseQueue.unshift(raw);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1088,11 +1144,6 @@ class SMTPConnection extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
if (!str.trim()) {
|
||||
// skip unexpected empty lines
|
||||
setImmediate(() => this._processResponse());
|
||||
}
|
||||
|
||||
const action = this._responseActions.shift();
|
||||
|
||||
if (typeof action === 'function') {
|
||||
@@ -1189,6 +1240,23 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 8689: validate REQUIRETLS eligibility before queuing the MAIL FROM
|
||||
// response action, so a rejection here cannot leave an orphaned action in
|
||||
// _responseActions (which would consume the next reply and desync a reused
|
||||
// connection).
|
||||
if (this._envelope.requireTLSExtensionEnabled) {
|
||||
if (!this.secure) {
|
||||
return callback(
|
||||
this._formatError('REQUIRETLS can only be used over TLS connections (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM')
|
||||
);
|
||||
}
|
||||
if (!this._supportedExtensions.includes('REQUIRETLS')) {
|
||||
return callback(
|
||||
this._formatError('Server does not support REQUIRETLS extension (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._responseActions.push(str => {
|
||||
this._actionMAIL(str, callback);
|
||||
});
|
||||
@@ -1225,20 +1293,10 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 8689: If the envelope requests REQUIRETLS extension
|
||||
// then append REQUIRETLS keyword to the MAIL FROM command
|
||||
// Note: REQUIRETLS can only be used over TLS connections and requires server support
|
||||
// RFC 8689: append the REQUIRETLS keyword to MAIL FROM. Eligibility
|
||||
// (TLS connection + server support) was already validated above, before
|
||||
// the response action was queued.
|
||||
if (this._envelope.requireTLSExtensionEnabled) {
|
||||
if (!this.secure) {
|
||||
return callback(
|
||||
this._formatError('REQUIRETLS can only be used over TLS connections (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM')
|
||||
);
|
||||
}
|
||||
if (!this._supportedExtensions.includes('REQUIRETLS')) {
|
||||
return callback(
|
||||
this._formatError('Server does not support REQUIRETLS extension (RFC 8689)', 'EREQUIRETLS', false, 'MAIL FROM')
|
||||
);
|
||||
}
|
||||
args.push('REQUIRETLS');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user