Bug 1801668 - Avoid "Send Message Error" during idle. r=babolivier

We were logging out of order at one place, which made the logs difficult to understand.
The issue discovered was that now that we reuse connections, the connection to the server
is left open after send. After the send was successful, if the server decides it wants
us to disconnect and sends us 421, that was erroneously interpreted as a send failure.

Also did a bit of modernization.

Differential Revision: https://phabricator.services.mozilla.com/D187532

--HG--
extra : rebase_source : a35791c4415e38ba38be01a6b742290e7279bfd0
extra : amend_source : d7cd1128d4d95460b3b0b78200fddd04e3c80c20
This commit is contained in:
Magnus Melin 2023-09-08 13:12:26 +03:00
Родитель 570e29bd61
Коммит d10608d089
2 изменённых файлов: 95 добавлений и 70 удалений

Просмотреть файл

@ -28,18 +28,17 @@ XPCOMUtils.defineLazyModuleGetters(lazy, {
const nsMsgKey_None = 0xffffffff; const nsMsgKey_None = 0xffffffff;
/** /**
* A module to manage sending processes. * A class to manage sending processes.
* *
* @implements {nsIMsgSend} * @implements {nsIMsgSend}
* @implements {nsIWebProgressListener}
*/ */
function MessageSend() {} class MessageSend {
QueryInterface = ChromeUtils.generateQI([
MessageSend.prototype = {
QueryInterface: ChromeUtils.generateQI([
"nsIMsgSend", "nsIMsgSend",
"nsIWebProgressListener", "nsIWebProgressListener",
]), ]);
classID: Components.ID("{028b9c1e-8d0a-4518-80c2-842e07846eaa}"), classID = Components.ID("{028b9c1e-8d0a-4518-80c2-842e07846eaa}");
async createAndSendMessage( async createAndSendMessage(
editor, editor,
@ -151,7 +150,7 @@ MessageSend.prototype = {
); );
lazy.MsgUtils.sendLogger.debug("Message file created"); lazy.MsgUtils.sendLogger.debug("Message file created");
return this._deliverMessage(messageFile); return this._deliverMessage(messageFile);
}, }
sendMessageFile( sendMessageFile(
userIdentity, userIdentity,
@ -206,7 +205,7 @@ MessageSend.prototype = {
this._messageKey = 0xffffffff; this._messageKey = 0xffffffff;
return this._deliverMessage(messageFile); return this._deliverMessage(messageFile);
}, }
// @see nsIMsgSend // @see nsIMsgSend
createRFC822Message( createRFC822Message(
@ -279,10 +278,10 @@ MessageSend.prototype = {
this._message this._message
.createMessageFile() .createMessageFile()
.then(messageFile => this._deliverMessage(messageFile)); .then(messageFile => this._deliverMessage(messageFile));
}, }
// nsIWebProgressListener. // nsIWebProgressListener.
onLocationChange(webProgress, request, location, flags) {}, onLocationChange(webProgress, request, location, flags) {}
onProgressChange( onProgressChange(
webProgress, webProgress,
request, request,
@ -290,10 +289,10 @@ MessageSend.prototype = {
maxSelfProgress, maxSelfProgress,
curTotalProgress, curTotalProgress,
maxTotalProgress maxTotalProgress
) {}, ) {}
onStatusChange(webProgress, request, status, message) {}, onStatusChange(webProgress, request, status, message) {}
onSecurityChange(webProgress, request, state) {}, onSecurityChange(webProgress, request, state) {}
onContentBlockingEvent(webProgress, request, event) {}, onContentBlockingEvent(webProgress, request, event) {}
onStateChange(webProgress, request, stateFlags, status) { onStateChange(webProgress, request, stateFlags, status) {
if ( if (
stateFlags & Ci.nsIWebProgressListener.STATE_STOP && stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
@ -303,7 +302,7 @@ MessageSend.prototype = {
this._isRetry = false; this._isRetry = false;
this.abort(); this.abort();
} }
}, }
abort() { abort() {
if (this._aborting) { if (this._aborting) {
@ -330,7 +329,7 @@ MessageSend.prototype = {
this.notifyListenerOnStopSending(null, Cr.NS_ERROR_ABORT, null, null); this.notifyListenerOnStopSending(null, Cr.NS_ERROR_ABORT, null, null);
} }
this._aborting = false; this._aborting = false;
}, }
getDefaultPrompt() { getDefaultPrompt() {
if (this._parentWindow) { if (this._parentWindow) {
@ -353,7 +352,7 @@ MessageSend.prototype = {
); );
} }
return prompt; return prompt;
}, }
fail(exitCode, errorMsg) { fail(exitCode, errorMsg) {
this._failed = true; this._failed = true;
@ -379,18 +378,18 @@ MessageSend.prototype = {
this.abort(); this.abort();
return exitCode; return exitCode;
}, }
getPartForDomIndex(domIndex) { getPartForDomIndex(domIndex) {
throw Components.Exception( throw Components.Exception(
"getPartForDomIndex not implemented", "getPartForDomIndex not implemented",
Cr.NS_ERROR_NOT_IMPLEMENTED Cr.NS_ERROR_NOT_IMPLEMENTED
); );
}, }
getProgress() { getProgress() {
return this._sendProgress; return this._sendProgress;
}, }
/** /**
* NOTE: This is a copy of the C++ code, msgId and msgSize are only * NOTE: This is a copy of the C++ code, msgId and msgSize are only
@ -401,21 +400,21 @@ MessageSend.prototype = {
if (this._sendListener) { if (this._sendListener) {
this._sendListener.onStartSending(msgId, msgSize); this._sendListener.onStartSending(msgId, msgSize);
} }
}, }
notifyListenerOnStartCopy() { notifyListenerOnStartCopy() {
lazy.MsgUtils.sendLogger.debug("notifyListenerOnStartCopy"); lazy.MsgUtils.sendLogger.debug("notifyListenerOnStartCopy");
if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) { if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) {
this._sendListener.OnStartCopy(); this._sendListener.OnStartCopy();
} }
}, }
notifyListenerOnProgressCopy(progress, progressMax) { notifyListenerOnProgressCopy(progress, progressMax) {
lazy.MsgUtils.sendLogger.debug("notifyListenerOnProgressCopy"); lazy.MsgUtils.sendLogger.debug("notifyListenerOnProgressCopy");
if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) { if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) {
this._sendListener.OnProgress(progress, progressMax); this._sendListener.OnProgress(progress, progressMax);
} }
}, }
notifyListenerOnStopCopy(status) { notifyListenerOnStopCopy(status) {
lazy.MsgUtils.sendLogger.debug( lazy.MsgUtils.sendLogger.debug(
@ -569,7 +568,7 @@ MessageSend.prototype = {
} }
this._doFcc2(); this._doFcc2();
}, }
notifyListenerOnStopSending(msgId, status, msg, returnFile) { notifyListenerOnStopSending(msgId, status, msg, returnFile) {
lazy.MsgUtils.sendLogger.debug( lazy.MsgUtils.sendLogger.debug(
@ -578,7 +577,7 @@ MessageSend.prototype = {
try { try {
this._sendListener?.onStopSending(msgId, status, msg, returnFile); this._sendListener?.onStopSending(msgId, status, msg, returnFile);
} catch (e) {} } catch (e) {}
}, }
notifyListenerOnTransportSecurityError(msgId, status, secInfo, location) { notifyListenerOnTransportSecurityError(msgId, status, secInfo, location) {
lazy.MsgUtils.sendLogger.debug( lazy.MsgUtils.sendLogger.debug(
@ -595,7 +594,7 @@ MessageSend.prototype = {
location location
); );
} catch (e) {} } catch (e) {}
}, }
/** /**
* Called by nsIMsgFilterService. * Called by nsIMsgFilterService.
@ -617,7 +616,7 @@ MessageSend.prototype = {
} }
this._doFcc2(); this._doFcc2();
}, }
/** /**
* Handle the exit code of message delivery. * Handle the exit code of message delivery.
@ -728,7 +727,7 @@ MessageSend.prototype = {
); );
this._doFcc(); this._doFcc();
}, }
sendDeliveryCallback(url, isNewsDelivery, exitCode) { sendDeliveryCallback(url, isNewsDelivery, exitCode) {
if (isNewsDelivery) { if (isNewsDelivery) {
@ -763,35 +762,35 @@ MessageSend.prototype = {
} }
} }
return this._deliveryExitProcessing(url, isNewsDelivery, exitCode); return this._deliveryExitProcessing(url, isNewsDelivery, exitCode);
}, }
get folderUri() { get folderUri() {
return this._folderUri; return this._folderUri;
}, }
/** /**
* @type {nsMsgKey} * @type {nsMsgKey}
*/ */
set messageKey(key) { set messageKey(key) {
this._messageKey = key; this._messageKey = key;
}, }
/** /**
* @type {nsMsgKey} * @type {nsMsgKey}
*/ */
get messageKey() { get messageKey() {
return this._messageKey; return this._messageKey;
}, }
get sendReport() { get sendReport() {
return this._sendReport; return this._sendReport;
}, }
_setStatusMessage(msg) { _setStatusMessage(msg) {
if (this._sendProgress) { if (this._sendProgress) {
this._sendProgress.onStatusChange(null, null, Cr.NS_OK, msg); this._sendProgress.onStatusChange(null, null, Cr.NS_OK, msg);
} }
}, }
/** /**
* Deliver a message. * Deliver a message.
@ -844,7 +843,7 @@ MessageSend.prototype = {
return; return;
} }
this._deliverAsMail(); this._deliverAsMail();
}, }
/** /**
* Strip Bcc header, create the file to be actually delivered. * Strip Bcc header, create the file to be actually delivered.
@ -886,7 +885,7 @@ MessageSend.prototype = {
combinedContent.set(body, encodedHeader.length); combinedContent.set(body, encodedHeader.length);
await IOUtils.write(deliveryFile.path, combinedContent); await IOUtils.write(deliveryFile.path, combinedContent);
return deliveryFile; return deliveryFile;
}, }
/** /**
* Create the file to be copied to the Sent folder, add X-Mozilla-Status and * Create the file to be copied to the Sent folder, add X-Mozilla-Status and
@ -925,7 +924,7 @@ MessageSend.prototype = {
} }
); );
return copyFile; return copyFile;
}, }
/** /**
* Start copy operation according to this._fcc value. * Start copy operation according to this._fcc value.
@ -937,7 +936,7 @@ MessageSend.prototype = {
} }
this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_Copy; this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_Copy;
this._mimeDoFcc(this._fcc, false, Ci.nsIMsgSend.nsMsgDeliverNow); this._mimeDoFcc(this._fcc, false, Ci.nsIMsgSend.nsMsgDeliverNow);
}, }
/** /**
* Copy a message to a folder, or fallback to a folder depending on pref and * Copy a message to a folder, or fallback to a folder depending on pref and
@ -1045,7 +1044,7 @@ MessageSend.prototype = {
} }
this.notifyListenerOnStopCopy(e.result); this.notifyListenerOnStopCopy(e.result);
} }
}, }
/** /**
* Handle the fcc2 field. Then notify OnStopCopy and clean up. * Handle the fcc2 field. Then notify OnStopCopy and clean up.
@ -1080,7 +1079,7 @@ MessageSend.prototype = {
} }
this._cleanup(); this._cleanup();
}); });
}, }
/** /**
* Run filters on the just sent message. * Run filters on the just sent message.
@ -1097,7 +1096,7 @@ MessageSend.prototype = {
msgWindow, msgWindow,
this this
); );
}, }
_cleanup() { _cleanup() {
lazy.MsgUtils.sendLogger.debug("Clean up temporary files"); lazy.MsgUtils.sendLogger.debug("Clean up temporary files");
@ -1113,7 +1112,7 @@ MessageSend.prototype = {
IOUtils.remove(this._messageFile.path).catch(console.error); IOUtils.remove(this._messageFile.path).catch(console.error);
this._messageFile = null; this._messageFile = null;
} }
}, }
/** /**
* Send this._deliveryFile to smtp service. * Send this._deliveryFile to smtp service.
@ -1140,7 +1139,9 @@ MessageSend.prototype = {
Ci.nsIMimeConverter.MIME_ENCODED_WORD_SIZE Ci.nsIMimeConverter.MIME_ENCODED_WORD_SIZE
) )
); );
lazy.MsgUtils.sendLogger.debug("Delivering mail message"); lazy.MsgUtils.sendLogger.debug(
`Delivering mail message <${this._compFields.messageId}>`
);
let deliveryListener = new MsgDeliveryListener(this, false); let deliveryListener = new MsgDeliveryListener(this, false);
let msgStatus = let msgStatus =
this._sendProgress instanceof Ci.nsIMsgStatusFeedback this._sendProgress instanceof Ci.nsIMsgStatusFeedback
@ -1161,7 +1162,7 @@ MessageSend.prototype = {
{}, {},
this._smtpRequest this._smtpRequest
); );
}, }
/** /**
* Send this._deliveryFile to nntp service. * Send this._deliveryFile to nntp service.
@ -1184,7 +1185,7 @@ MessageSend.prototype = {
msgWindow, msgWindow,
null null
); );
}, }
/** /**
* Collect outgoing addresses to address book. * Collect outgoing addresses to address book.
@ -1203,7 +1204,7 @@ MessageSend.prototype = {
for (let recipient of recipients) { for (let recipient of recipients) {
addressCollector.collectAddress(recipient, createCard); addressCollector.collectAddress(recipient, createCard);
} }
}, }
/** /**
* Check if link text is equivalent to the href. * Check if link text is equivalent to the href.
@ -1223,7 +1224,7 @@ MessageSend.prototype = {
(text.endsWith("/") && text.slice(0, -1) == href) || (text.endsWith("/") && text.slice(0, -1) == href) ||
(href.endsWith("/") && href.slice(0, -1) == text) (href.endsWith("/") && href.slice(0, -1) == text)
); );
}, }
/** /**
* Collect embedded objects as attachments. * Collect embedded objects as attachments.
@ -1331,7 +1332,7 @@ MessageSend.prototype = {
} }
} }
return { embeddedAttachments, embeddedObjects }; return { embeddedAttachments, embeddedObjects };
}, }
/** /**
* Restore embedded objects in editor to their original urls. * Restore embedded objects in editor to their original urls.
@ -1350,7 +1351,7 @@ MessageSend.prototype = {
element.href = url; element.href = url;
} }
} }
}, }
/** /**
* Get the message body from an editor. * Get the message body from an editor.
@ -1383,7 +1384,7 @@ MessageSend.prototype = {
} }
return bodyText; return bodyText;
}, }
/** /**
* Get the first account key of an identity. * Get the first account key of an identity.
@ -1396,25 +1397,29 @@ MessageSend.prototype = {
return servers.length return servers.length
? MailServices.accounts.FindAccountForServer(servers[0])?.key ? MailServices.accounts.FindAccountForServer(servers[0])?.key
: null; : null;
}, }
}; }
/** /**
* A listener to be passed to the SMTP service. * A listener to be passed to the SMTP service.
* *
* @implements {nsIUrlListener} * @implements {nsIUrlListener}
*/ */
function MsgDeliveryListener(msgSend, isNewsDelivery) { class MsgDeliveryListener {
this._msgSend = msgSend; QueryInterface = ChromeUtils.generateQI(["nsIUrlListener"]);
this._isNewsDelivery = isNewsDelivery;
}
MsgDeliveryListener.prototype = { /**
QueryInterface: ChromeUtils.generateQI(["nsIUrlListener"]), * @param {nsIMsgSend} msgSend - Send instance to use.
* @param {boolean} isNewsDelivery - Whether this is an nntp message delivery.
*/
constructor(msgSend, isNewsDelivery) {
this._msgSend = msgSend;
this._isNewsDelivery = isNewsDelivery;
}
OnStartRunningUrl(url) { OnStartRunningUrl(url) {
this._msgSend.notifyListenerOnStartSending(null, 0); this._msgSend.notifyListenerOnStartSending(null, 0);
}, }
OnStopRunningUrl(url, exitCode) { OnStopRunningUrl(url, exitCode) {
lazy.MsgUtils.sendLogger.debug(`OnStopRunningUrl; exitCode=${exitCode}`); lazy.MsgUtils.sendLogger.debug(`OnStopRunningUrl; exitCode=${exitCode}`);
@ -1422,5 +1427,5 @@ MsgDeliveryListener.prototype = {
mailUrl.UnRegisterListener(this); mailUrl.UnRegisterListener(this);
this._msgSend.sendDeliveryCallback(url, this._isNewsDelivery, exitCode); this._msgSend.sendDeliveryCallback(url, this._isNewsDelivery, exitCode);
}, }
}; }

Просмотреть файл

@ -150,10 +150,18 @@ class SmtpClient {
* unsent data. * unsent data.
*/ */
close(immediately) { close(immediately) {
this.logger.debug("Closing connection...");
if (this.socket && this.socket.readyState === "open") { if (this.socket && this.socket.readyState === "open") {
immediately ? this.socket.closeImmediately() : this.socket.close(); if (immediately) {
this.logger.debug(
`Closing connection to ${this._server.hostname} immediately!`
);
this.socket.closeImmediately();
} else {
this.logger.debug(`Closing connection to ${this._server.hostname}...`);
this.socket.close();
}
} else { } else {
this.logger.debug(`Connection to ${this._server.hostname} closed`);
this._free(); this._free();
} }
} }
@ -403,17 +411,17 @@ class SmtpClient {
* @param {Event} evt - Event object. See `evt.data` for the chunk received * @param {Event} evt - Event object. See `evt.data` for the chunk received
*/ */
_onData = async evt => { _onData = async evt => {
let stringPayload = new TextDecoder("UTF-8").decode(
new Uint8Array(evt.data)
);
// "S: " to denote that this is data from the Server.
this.logger.debug(`S: ${stringPayload}`);
// Prevent blocking the main thread, otherwise onclose/onerror may not be // Prevent blocking the main thread, otherwise onclose/onerror may not be
// called in time. test_smtpPasswordFailure3 is such a case, the server // called in time. test_smtpPasswordFailure3 is such a case, the server
// rejects AUTH PLAIN then closes the connection, the client then sends AUTH // rejects AUTH PLAIN then closes the connection, the client then sends AUTH
// LOGIN. This line guarantees onclose is called before sending AUTH LOGIN. // LOGIN. This line guarantees onclose is called before sending AUTH LOGIN.
await new Promise(resolve => setTimeout(resolve)); await new Promise(resolve => setTimeout(resolve));
var stringPayload = new TextDecoder("UTF-8").decode(
new Uint8Array(evt.data)
);
// "S: " to denote that this is data from the Server.
this.logger.debug(`S: ${stringPayload}`);
this._parse(stringPayload); this._parse(stringPayload);
}; };
@ -511,6 +519,17 @@ class SmtpClient {
*/ */
_onCommand(command) { _onCommand(command) {
if (command.statusCode < 200 || command.statusCode >= 400) { if (command.statusCode < 200 || command.statusCode >= 400) {
// @see rfc5321#section-3.8
// 421: SMTP service shutting down and closing transmission channel.
// When that happens during idle, just close the connection.
if (
command.statusCode == 421 &&
this._currentAction == this._actionIdle
) {
this.close(true);
return;
}
this.logger.error( this.logger.error(
`Command failed: ${command.statusCode} ${command.data}; currentAction=${this._currentAction?.name}` `Command failed: ${command.statusCode} ${command.data}; currentAction=${this._currentAction?.name}`
); );
@ -526,6 +545,7 @@ class SmtpClient {
_free() { _free() {
if (!this._freed) { if (!this._freed) {
this._freed = true; this._freed = true;
this.logger.debug("Client ready for reuse");
this.onFree(); this.onFree();
} }
} }