diff options
Diffstat (limited to 'comm/mailnews/compose/src')
46 files changed, 20455 insertions, 0 deletions
diff --git a/comm/mailnews/compose/src/MailtoProtocolHandler.jsm b/comm/mailnews/compose/src/MailtoProtocolHandler.jsm new file mode 100644 index 0000000000..a129e7d750 --- /dev/null +++ b/comm/mailnews/compose/src/MailtoProtocolHandler.jsm @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["MailtoProtocolHandler"]; + +/** + * Protocol handler for mailto: url. + * + * @implements {nsIProtocolHandler} + */ +class MailtoProtocolHandler { + QueryInterface = ChromeUtils.generateQI([Ci.nsIProtocolHandler]); + + scheme = "mailto"; + allowPort = false; + + newChannel(uri, loadInfo) { + // Create an empty pipe to get an inputStream. + let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(true, true, 0, 0); + pipe.outputStream.close(); + + // Create a channel so that we can set contentType onto it. + let streamChannel = Cc[ + "@mozilla.org/network/input-stream-channel;1" + ].createInstance(Ci.nsIInputStreamChannel); + streamChannel.setURI(uri); + streamChannel.contentStream = pipe.inputStream; + + let channel = streamChannel.QueryInterface(Ci.nsIChannel); + // With this set, a nsIContentHandler instance will take over to open a + // compose window. + channel.contentType = "application/x-mailto"; + channel.loadInfo = loadInfo; + return channel; + } +} diff --git a/comm/mailnews/compose/src/MessageSend.jsm b/comm/mailnews/compose/src/MessageSend.jsm new file mode 100644 index 0000000000..914694fa79 --- /dev/null +++ b/comm/mailnews/compose/src/MessageSend.jsm @@ -0,0 +1,1434 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["MessageSend"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + setTimeout: "resource://gre/modules/Timer.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(lazy, { + MailUtils: "resource:///modules/MailUtils.jsm", + jsmime: "resource:///modules/jsmime.jsm", + MimeMessage: "resource:///modules/MimeMessage.jsm", + MsgUtils: "resource:///modules/MimeMessageUtils.jsm", +}); + +// nsMsgKey_None from MailNewsTypes.h. +const nsMsgKey_None = 0xffffffff; + +/** + * A class to manage sending processes. + * + * @implements {nsIMsgSend} + * @implements {nsIWebProgressListener} + */ +class MessageSend { + QueryInterface = ChromeUtils.generateQI([ + "nsIMsgSend", + "nsIWebProgressListener", + ]); + classID = Components.ID("{028b9c1e-8d0a-4518-80c2-842e07846eaa}"); + + async createAndSendMessage( + editor, + userIdentity, + accountKey, + compFields, + isDigest, + dontDeliver, + deliverMode, + msgToReplace, + bodyType, + body, + parentWindow, + progress, + listener, + smtpPassword, + originalMsgURI, + compType + ) { + this._userIdentity = userIdentity; + this._accountKey = accountKey || this._accountKeyForIdentity(userIdentity); + this._compFields = compFields; + this._dontDeliver = dontDeliver; + this._deliverMode = deliverMode; + this._msgToReplace = msgToReplace; + this._sendProgress = progress; + this._smtpPassword = smtpPassword; + this._sendListener = listener; + this._parentWindow = parentWindow; + this._originalMsgURI = originalMsgURI; + this._shouldRemoveMessageFile = true; + + this._sendReport = Cc[ + "@mozilla.org/messengercompose/sendreport;1" + ].createInstance(Ci.nsIMsgSendReport); + this._composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + + // Initialize the error reporting mechanism. + this.sendReport.reset(); + this.sendReport.deliveryMode = deliverMode; + this._setStatusMessage( + this._composeBundle.GetStringFromName("assemblingMailInformation") + ); + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_BuildMessage; + + this._setStatusMessage( + this._composeBundle.GetStringFromName("assemblingMessage") + ); + + this._fcc = lazy.MsgUtils.getFcc( + userIdentity, + compFields, + originalMsgURI, + compType + ); + let { embeddedAttachments, embeddedObjects } = + this._gatherEmbeddedAttachments(editor); + + let bodyText = this._getBodyFromEditor(editor) || body; + // Convert to a binary string. This is because MimeMessage requires it and: + // 1. An attachment content is BinaryString. + // 2. Body text and attachment contents are handled in the same way by + // MimeEncoder to pick encoding and encode. + bodyText = lazy.jsmime.mimeutils.typedArrayToString( + new TextEncoder().encode(bodyText) + ); + + this._restoreEditorContent(embeddedObjects); + this._message = new lazy.MimeMessage( + userIdentity, + compFields, + this._fcc, + bodyType, + bodyText, + deliverMode, + originalMsgURI, + compType, + embeddedAttachments, + this.sendReport + ); + + this._messageKey = nsMsgKey_None; + + this._setStatusMessage( + this._composeBundle.GetStringFromName("creatingMailMessage") + ); + lazy.MsgUtils.sendLogger.debug("Creating message file"); + let messageFile; + try { + // Create a local file from MimeMessage, then pass it to _deliverMessage. + messageFile = await this._message.createMessageFile(); + } catch (e) { + lazy.MsgUtils.sendLogger.error(e); + let errorMsg = ""; + if (e.result == lazy.MsgUtils.NS_MSG_ERROR_ATTACHING_FILE) { + errorMsg = this._composeBundle.formatStringFromName( + "errorAttachingFile", + [e.data.name || e.data.url] + ); + } + this.fail(e.result || Cr.NS_ERROR_FAILURE, errorMsg); + this.notifyListenerOnStopSending(null, e.result, null, null); + return null; + } + this._setStatusMessage( + this._composeBundle.GetStringFromName("assemblingMessageDone") + ); + lazy.MsgUtils.sendLogger.debug("Message file created"); + return this._deliverMessage(messageFile); + } + + sendMessageFile( + userIdentity, + accountKey, + compFields, + messageFile, + deleteSendFileOnCompletion, + digest, + deliverMode, + msgToReplace, + listener, + statusFeedback, + smtpPassword + ) { + this._userIdentity = userIdentity; + this._accountKey = accountKey || this._accountKeyForIdentity(userIdentity); + this._compFields = compFields; + this._deliverMode = deliverMode; + this._msgToReplace = msgToReplace; + this._smtpPassword = smtpPassword; + this._sendListener = listener; + this._statusFeedback = statusFeedback; + this._shouldRemoveMessageFile = deleteSendFileOnCompletion; + + this._sendReport = Cc[ + "@mozilla.org/messengercompose/sendreport;1" + ].createInstance(Ci.nsIMsgSendReport); + this._composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + + // Initialize the error reporting mechanism. + this.sendReport.reset(); + this.sendReport.deliveryMode = deliverMode; + this._setStatusMessage( + this._composeBundle.GetStringFromName("assemblingMailInformation") + ); + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_BuildMessage; + + this._setStatusMessage( + this._composeBundle.GetStringFromName("assemblingMessage") + ); + + this._fcc = lazy.MsgUtils.getFcc( + userIdentity, + compFields, + null, + Ci.nsIMsgCompType.New + ); + + // nsMsgKey_None from MailNewsTypes.h. + this._messageKey = 0xffffffff; + + return this._deliverMessage(messageFile); + } + + // @see nsIMsgSend + createRFC822Message( + userIdentity, + compFields, + bodyType, + bodyText, + isDraft, + attachedFiles, + embeddedObjects, + listener + ) { + this._userIdentity = userIdentity; + this._compFields = compFields; + this._dontDeliver = true; + this._sendListener = listener; + + this._sendReport = Cc[ + "@mozilla.org/messengercompose/sendreport;1" + ].createInstance(Ci.nsIMsgSendReport); + this._composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + + // Initialize the error reporting mechanism. + this.sendReport.reset(); + let deliverMode = isDraft + ? Ci.nsIMsgSend.nsMsgSaveAsDraft + : Ci.nsIMsgSend.nsMsgDeliverNow; + this.sendReport.deliveryMode = deliverMode; + + // Convert nsIMsgAttachedFile[] to nsIMsgAttachment[] + for (let file of attachedFiles) { + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + attachment.name = file.realName; + attachment.url = file.origUrl.spec; + attachment.contentType = file.type; + compFields.addAttachment(attachment); + } + + // Convert nsIMsgEmbeddedImageData[] to nsIMsgAttachment[] + let embeddedAttachments = embeddedObjects.map(obj => { + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + attachment.name = obj.name; + attachment.contentId = obj.cid; + attachment.url = obj.uri.spec; + return attachment; + }); + + this._message = new lazy.MimeMessage( + userIdentity, + compFields, + null, + bodyType, + bodyText, + deliverMode, + null, + Ci.nsIMsgCompType.New, + embeddedAttachments, + this.sendReport + ); + + this._messageKey = nsMsgKey_None; + + // Create a local file from MimeMessage, then pass it to _deliverMessage. + this._message + .createMessageFile() + .then(messageFile => this._deliverMessage(messageFile)); + } + + // nsIWebProgressListener. + onLocationChange(webProgress, request, location, flags) {} + onProgressChange( + webProgress, + request, + curSelfProgress, + maxSelfProgress, + curTotalProgress, + maxTotalProgress + ) {} + onStatusChange(webProgress, request, status, message) {} + onSecurityChange(webProgress, request, state) {} + onContentBlockingEvent(webProgress, request, event) {} + onStateChange(webProgress, request, stateFlags, status) { + if ( + stateFlags & Ci.nsIWebProgressListener.STATE_STOP && + !Components.isSuccessCode(status) + ) { + lazy.MsgUtils.sendLogger.debug("onStateChange with failure. Aborting."); + this._isRetry = false; + this.abort(); + } + } + + abort() { + if (this._aborting) { + return; + } + this._aborting = true; + if (this._smtpRequest?.value) { + this._smtpRequest.value.cancel(Cr.NS_ERROR_ABORT); + this._smtpRequest = null; + } + if (this._msgCopy) { + MailServices.copy.notifyCompletion( + this._copyFile, + this._msgCopy.dstFolder, + Cr.NS_ERROR_ABORT + ); + } else { + // If already in the fcc step, notifyListenerOnStopCopy will do the clean up. + this._cleanup(); + } + if (!this._failed) { + // Emit stopsending event if the sending is cancelled by user, so that + // listeners can do necessary clean up, e.g. reset the sending button. + this.notifyListenerOnStopSending(null, Cr.NS_ERROR_ABORT, null, null); + } + this._aborting = false; + } + + fail(exitCode, errorMsg) { + this._failed = true; + if (!Components.isSuccessCode(exitCode) && exitCode != Cr.NS_ERROR_ABORT) { + lazy.MsgUtils.sendLogger.error( + `Sending failed; ${errorMsg}, exitCode=${exitCode}, originalMsgURI=${this._originalMsgURI}` + ); + this._sendReport.setError( + Ci.nsIMsgSendReport.process_Current, + exitCode, + false + ); + if (errorMsg) { + this._sendReport.setMessage( + Ci.nsIMsgSendReport.process_Current, + errorMsg, + false + ); + } + exitCode = this._sendReport.displayReport(this._parentWindow, true, true); + } + this.abort(); + + return exitCode; + } + + getPartForDomIndex(domIndex) { + throw Components.Exception( + "getPartForDomIndex not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + getProgress() { + return this._sendProgress; + } + + /** + * NOTE: This is a copy of the C++ code, msgId and msgSize are only + * placeholders. Maybe refactor this after nsMsgSend is gone. + */ + notifyListenerOnStartSending(msgId, msgSize) { + lazy.MsgUtils.sendLogger.debug("notifyListenerOnStartSending"); + if (this._sendListener) { + this._sendListener.onStartSending(msgId, msgSize); + } + } + + notifyListenerOnStartCopy() { + lazy.MsgUtils.sendLogger.debug("notifyListenerOnStartCopy"); + if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) { + this._sendListener.OnStartCopy(); + } + } + + notifyListenerOnProgressCopy(progress, progressMax) { + lazy.MsgUtils.sendLogger.debug("notifyListenerOnProgressCopy"); + if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) { + this._sendListener.OnProgress(progress, progressMax); + } + } + + notifyListenerOnStopCopy(status) { + lazy.MsgUtils.sendLogger.debug( + `notifyListenerOnStopCopy; status=${status}` + ); + this._msgCopy = null; + + if (!this._isRetry) { + let statusMsgEntry = Components.isSuccessCode(status) + ? "copyMessageComplete" + : "copyMessageFailed"; + this._setStatusMessage( + this._composeBundle.GetStringFromName(statusMsgEntry) + ); + } else if (Components.isSuccessCode(status)) { + // We got here via retry and the save to sent, drafts or template + // succeeded so take down our progress dialog. We don't need it any more. + this._sendProgress.unregisterListener(this); + this._sendProgress.closeProgressDialog(false); + this._isRetry = false; + } + + if (!Components.isSuccessCode(status)) { + let localFoldersAccountName = + MailServices.accounts.localFoldersServer.prettyName; + let folder = lazy.MailUtils.getOrCreateFolder(this._folderUri); + let accountName = folder?.server.prettyName; + if (!this._fcc || !localFoldersAccountName || !accountName) { + this.fail(Cr.NS_OK, null); + return; + } + + let params = [folder.name, accountName, localFoldersAccountName]; + let promptMsg; + switch (this._deliverMode) { + case Ci.nsIMsgSend.nsMsgDeliverNow: + case Ci.nsIMsgSend.nsMsgSendUnsent: + promptMsg = this._composeBundle.formatStringFromName( + "promptToSaveSentLocally2", + params + ); + break; + case Ci.nsIMsgSend.nsMsgSaveAsDraft: + promptMsg = this._composeBundle.formatStringFromName( + "promptToSaveDraftLocally2", + params + ); + break; + case Ci.nsIMsgSend.nsMsgSaveAsTemplate: + promptMsg = this._composeBundle.formatStringFromName( + "promptToSaveTemplateLocally2", + params + ); + break; + } + if (promptMsg) { + let showCheckBox = { value: false }; + let buttonFlags = + Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING + + Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE + + Ci.nsIPrompt.BUTTON_POS_2 * Ci.nsIPrompt.BUTTON_TITLE_SAVE; + let dialogTitle = + this._composeBundle.GetStringFromName("SaveDialogTitle"); + let buttonLabelRety = + this._composeBundle.GetStringFromName("buttonLabelRetry2"); + let buttonPressed = Services.prompt.confirmEx( + this._parentWindow, + dialogTitle, + promptMsg, + buttonFlags, + buttonLabelRety, + null, + null, + null, + showCheckBox + ); + if (buttonPressed == 0) { + // retry button clicked + // Check we have a progress dialog. + if ( + this._sendProgress.processCanceledByUser && + Services.prefs.getBoolPref("mailnews.show_send_progress") + ) { + let progress = Cc[ + "@mozilla.org/messenger/progress;1" + ].createInstance(Ci.nsIMsgProgress); + + let params = Cc[ + "@mozilla.org/messengercompose/composeprogressparameters;1" + ].createInstance(Ci.nsIMsgComposeProgressParams); + params.subject = this._parentWindow.gMsgCompose.compFields.subject; + params.deliveryMode = this._deliverMode; + + progress.openProgressDialog( + this._parentWindow, + this._sendProgress.msgWindow, + "chrome://messenger/content/messengercompose/sendProgress.xhtml", + false, + params + ); + + progress.onStateChange( + null, + null, + Ci.nsIWebProgressListener.STATE_START, + Cr.NS_OK + ); + + // We want to hear when this is cancelled. + progress.registerListener(this); + + this._sendProgress = progress; + this._isRetry = true; + } + // Ensure statusFeedback is set so progress percent bargraph occurs. + this._sendProgress.msgWindow.statusFeedback = this._sendProgress; + + this._mimeDoFcc(); + return; + } else if (buttonPressed == 2) { + try { + // Try to save to Local Folders/<account name>. Pass null to save + // to local folders and not the configured fcc. + this._mimeDoFcc(null, true, Ci.nsIMsgSend.nsMsgDeliverNow); + return; + } catch (e) { + Services.prompt.alert( + this._parentWindow, + null, + this._composeBundle.GetStringFromName("saveToLocalFoldersFailed") + ); + } + } + } + this.fail(Cr.NS_OK, null); + } + + if ( + !this._fcc2Handled && + this._messageKey != nsMsgKey_None && + [Ci.nsIMsgSend.nsMsgDeliverNow, Ci.nsIMsgSend.nsMsgSendUnsent].includes( + this._deliverMode + ) + ) { + try { + this._filterSentMessage(); + } catch (e) { + this.onStopOperation(e.result); + } + return; + } + + this._doFcc2(); + } + + notifyListenerOnStopSending(msgId, status, msg, returnFile) { + lazy.MsgUtils.sendLogger.debug( + `notifyListenerOnStopSending; status=${status}` + ); + try { + this._sendListener?.onStopSending(msgId, status, msg, returnFile); + } catch (e) {} + } + + notifyListenerOnTransportSecurityError(msgId, status, secInfo, location) { + lazy.MsgUtils.sendLogger.debug( + `notifyListenerOnTransportSecurityError; status=${status}, location=${location}` + ); + if (!this._sendListener) { + return; + } + try { + this._sendListener.onTransportSecurityError( + msgId, + status, + secInfo, + location + ); + } catch (e) {} + } + + /** + * Called by nsIMsgFilterService. + */ + onStopOperation(status) { + lazy.MsgUtils.sendLogger.debug(`onStopOperation; status=${status}`); + if (Components.isSuccessCode(status)) { + this._setStatusMessage( + this._composeBundle.GetStringFromName("filterMessageComplete") + ); + } else { + this._setStatusMessage( + this._composeBundle.GetStringFromName("filterMessageFailed") + ); + Services.prompt.alert( + this._parentWindow, + null, + this._composeBundle.GetStringFromName("errorFilteringMsg") + ); + } + + this._doFcc2(); + } + + /** + * Handle the exit code of message delivery. + * + * @param {nsIURI} url - The delivered message uri. + * @param {boolean} isNewsDelivery - The message was delivered to newsgroup. + * @param {nsreault} exitCode - The exit code of message delivery. + */ + _deliveryExitProcessing(url, isNewsDelivery, exitCode) { + lazy.MsgUtils.sendLogger.debug( + `Delivery exit processing; exitCode=${exitCode}` + ); + if (!Components.isSuccessCode(exitCode)) { + let isNSSError = false; + let errorName = lazy.MsgUtils.getErrorStringName(exitCode); + let errorMsg; + if ( + [ + lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER, + lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_REFUSED, + lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED, + lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_TIMEOUT, + lazy.MsgUtils.NS_ERROR_SMTP_PASSWORD_UNDEFINED, + lazy.MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, + lazy.MsgUtils.NS_ERROR_SMTP_AUTH_GSSAPI, + lazy.MsgUtils.NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED, + lazy.MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL, + lazy.MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL, + lazy.MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT, + lazy.MsgUtils.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS, + ].includes(exitCode) + ) { + errorMsg = lazy.MsgUtils.formatStringWithSMTPHostName( + this._userIdentity, + this._composeBundle, + errorName + ); + } else { + let nssErrorsService = Cc[ + "@mozilla.org/nss_errors_service;1" + ].getService(Ci.nsINSSErrorsService); + try { + // This is a server security issue as determined by the Mozilla + // platform. To the Mozilla security message string, appended a string + // having additional information with the server name encoded. + errorMsg = nssErrorsService.getErrorMessage(exitCode); + errorMsg += + "\n" + + lazy.MsgUtils.formatStringWithSMTPHostName( + this._userIdentity, + this._composeBundle, + "smtpSecurityIssue" + ); + isNSSError = true; + } catch (e) { + if (url.errorMessage) { + // url.errorMessage is an already localized message, usually + // combined with the error message from SMTP server. + errorMsg = url.errorMessage; + } else if (errorName != "sendFailed") { + // Not the default string. A mailnews error occurred that does not + // require the server name to be encoded. Just print the descriptive + // string. + errorMsg = this._composeBundle.GetStringFromName(errorName); + } else { + errorMsg = this._composeBundle.GetStringFromName( + "sendFailedUnexpected" + ); + // nsIStringBundle.formatStringFromName doesn't work with %X. + errorMsg.replace("%X", `0x${exitCode.toString(16)}`); + errorMsg = + "\n" + + lazy.MsgUtils.formatStringWithSMTPHostName( + this._userIdentity, + this._composeBundle, + "smtpSendFailedUnknownReason" + ); + } + } + } + if (isNSSError) { + let u = url.QueryInterface(Ci.nsIMsgMailNewsUrl); + this.notifyListenerOnTransportSecurityError( + null, + exitCode, + u.failedSecInfo, + u.asciiHostPort + ); + } + this.notifyListenerOnStopSending(null, exitCode, null, null); + this.fail(exitCode, errorMsg); + return; + } + + if ( + isNewsDelivery && + (this._compFields.to || this._compFields.cc || this._compFields.bcc) + ) { + this._deliverAsMail(); + return; + } + + this.notifyListenerOnStopSending( + this._compFields.messageId, + exitCode, + null, + null + ); + + this._doFcc(); + } + + sendDeliveryCallback(url, isNewsDelivery, exitCode) { + if (isNewsDelivery) { + if ( + !Components.isSuccessCode(exitCode) && + exitCode != Cr.NS_ERROR_ABORT && + !lazy.MsgUtils.isMsgError(exitCode) + ) { + exitCode = lazy.MsgUtils.NS_ERROR_POST_FAILED; + } + return this._deliveryExitProcessing(url, isNewsDelivery, exitCode); + } + if (!Components.isSuccessCode(exitCode)) { + switch (exitCode) { + case Cr.NS_ERROR_UNKNOWN_HOST: + case Cr.NS_ERROR_UNKNOWN_PROXY_HOST: + exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER; + break; + case Cr.NS_ERROR_CONNECTION_REFUSED: + case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED: + exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_REFUSED; + break; + case Cr.NS_ERROR_NET_INTERRUPT: + exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED; + break; + case Cr.NS_ERROR_NET_TIMEOUT: + case Cr.NS_ERROR_NET_RESET: + exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_TIMEOUT; + break; + default: + break; + } + } + return this._deliveryExitProcessing(url, isNewsDelivery, exitCode); + } + + get folderUri() { + return this._folderUri; + } + + /** + * @type {nsMsgKey} + */ + set messageKey(key) { + this._messageKey = key; + } + + /** + * @type {nsMsgKey} + */ + get messageKey() { + return this._messageKey; + } + + get sendReport() { + return this._sendReport; + } + + _setStatusMessage(msg) { + if (this._sendProgress) { + this._sendProgress.onStatusChange(null, null, Cr.NS_OK, msg); + } + } + + /** + * Deliver a message. + * + * @param {nsIFile} file - The message file to deliver. + */ + async _deliverMessage(file) { + if (this._dontDeliver) { + this.notifyListenerOnStopSending(null, Cr.NS_OK, null, file); + return; + } + + this._messageFile = file; + if ( + [ + Ci.nsIMsgSend.nsMsgQueueForLater, + Ci.nsIMsgSend.nsMsgDeliverBackground, + Ci.nsIMsgSend.nsMsgSaveAsDraft, + Ci.nsIMsgSend.nsMsgSaveAsTemplate, + ].includes(this._deliverMode) + ) { + await this._mimeDoFcc(); + return; + } + + let warningSize = Services.prefs.getIntPref( + "mailnews.message_warning_size" + ); + if (warningSize > 0 && file.fileSize > warningSize) { + let messenger = Cc["@mozilla.org/messenger;1"].createInstance( + Ci.nsIMessenger + ); + let msg = this._composeBundle.formatStringFromName( + "largeMessageSendWarning", + [messenger.formatFileSize(file.fileSize)] + ); + if (!Services.prompt.confirm(this._parentWindow, null, msg)) { + this.fail(lazy.MsgUtils.NS_ERROR_BUT_DONT_SHOW_ALERT, msg); + throw Components.Exception( + "Cancelled sending large message", + Cr.NS_ERROR_FAILURE + ); + } + } + + this._deliveryFile = await this._createDeliveryFile(); + if (this._compFields.newsgroups) { + this._deliverAsNews(); + return; + } + await this._deliverAsMail(); + } + + /** + * Strip Bcc header, create the file to be actually delivered. + * + * @returns {nsIFile} + */ + async _createDeliveryFile() { + if (!this._compFields.bcc) { + return this._messageFile; + } + let deliveryFile = Services.dirsvc.get("TmpD", Ci.nsIFile); + deliveryFile.append("nsemail.tmp"); + deliveryFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + let content = await IOUtils.read(this._messageFile.path); + let bodyIndex = content.findIndex( + (el, index) => + // header and body are separated by \r\n\r\n + el == 13 && + content[index + 1] == 10 && + content[index + 2] == 13 && + content[index + 3] == 10 + ); + let header = new TextDecoder("UTF-8").decode(content.slice(0, bodyIndex)); + let lastLinePruned = false; + let headerToWrite = ""; + for (let line of header.split("\r\n")) { + if (line.startsWith("Bcc") || (line.startsWith(" ") && lastLinePruned)) { + lastLinePruned = true; + continue; + } + lastLinePruned = false; + headerToWrite += `${line}\r\n`; + } + let encodedHeader = new TextEncoder().encode(headerToWrite); + // Prevent extra \r\n, which was already added to the last head line. + let body = content.slice(bodyIndex + 2); + let combinedContent = new Uint8Array(encodedHeader.length + body.length); + combinedContent.set(encodedHeader); + combinedContent.set(body, encodedHeader.length); + await IOUtils.write(deliveryFile.path, combinedContent); + return deliveryFile; + } + + /** + * Create the file to be copied to the Sent folder, add X-Mozilla-Status and + * X-Mozilla-Status2 if needed. + * + * @returns {nsIFile} + */ + async _createCopyFile() { + if (!this._folderUri.startsWith("mailbox:")) { + return this._messageFile; + } + + // Add a `From - Date` line, so that nsLocalMailFolder.cpp won't add a + // dummy envelope. The date string will be parsed by PR_ParseTimeString. + // TODO: this should not be added to Maildir, see bug 1686852. + let contentToWrite = `From - ${new Date().toUTCString()}\r\n`; + let xMozillaStatus = lazy.MsgUtils.getXMozillaStatus(this._deliverMode); + let xMozillaStatus2 = lazy.MsgUtils.getXMozillaStatus2(this._deliverMode); + if (xMozillaStatus) { + contentToWrite += `X-Mozilla-Status: ${xMozillaStatus}\r\n`; + } + if (xMozillaStatus2) { + contentToWrite += `X-Mozilla-Status2: ${xMozillaStatus2}\r\n`; + } + + // Create a separate copy file when there are extra headers. + let copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile); + copyFile.append("nscopy.tmp"); + copyFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + await IOUtils.writeUTF8(copyFile.path, contentToWrite); + await IOUtils.write( + copyFile.path, + await IOUtils.read(this._messageFile.path), + { + mode: "append", + } + ); + return copyFile; + } + + /** + * Start copy operation according to this._fcc value. + */ + async _doFcc() { + if (!this._fcc || !lazy.MsgUtils.canSaveToFolder(this._fcc)) { + this.notifyListenerOnStopCopy(Cr.NS_OK); + return; + } + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_Copy; + this._mimeDoFcc(this._fcc, false, Ci.nsIMsgSend.nsMsgDeliverNow); + } + + /** + * Copy a message to a folder, or fallback to a folder depending on pref and + * deliverMode, usually Drafts/Sent. + * + * @param {string} [fccHeader=this._fcc] - The target folder uri to copy the + * message to. + * @param {boolean} [throwOnError=false] - By default notifyListenerOnStopCopy + * is called on error. When throwOnError is true, the caller can handle the + * error by itself. + * @param {nsMsgDeliverMode} [deliverMode=this._deliverMode] - The deliver mode. + */ + async _mimeDoFcc( + fccHeader = this._fcc, + throwOnError = false, + deliverMode = this._deliverMode + ) { + let folder; + let folderUri; + if (fccHeader) { + folder = lazy.MailUtils.getExistingFolder(fccHeader); + } + if ( + [Ci.nsIMsgSend.nsMsgDeliverNow, Ci.nsIMsgSend.nsMsgSendUnsent].includes( + deliverMode + ) && + folder + ) { + this._folderUri = fccHeader; + } else if (fccHeader == null) { + // Set fcc_header to a special folder in Local Folders "account" since can't + // save to Sent mbox, typically because imap connection is down. This + // folder is created if it doesn't yet exist. + let rootFolder = MailServices.accounts.localFoldersServer.rootMsgFolder; + folderUri = rootFolder.URI + "/"; + + // Now append the special folder name folder to the local folder uri. + if ( + [ + Ci.nsIMsgSend.nsMsgDeliverNow, + Ci.nsIMsgSend.nsMsgSendUnsent, + Ci.nsIMsgSend.nsMsgSaveAsDraft, + Ci.nsIMsgSend.nsMsgSaveAsTemplate, + ].includes(this._deliverMode) + ) { + // Typically, this appends "Sent-", "Drafts-" or "Templates-" to folder + // and then has the account name appended, e.g., .../Sent-MyImapAccount. + let folder = lazy.MailUtils.getOrCreateFolder(this._folderUri); + folderUri += folder.name + "-"; + } + if (this._fcc) { + // Get the account name where the "save to" failed. + let accountName = lazy.MailUtils.getOrCreateFolder(this._fcc).server + .prettyName; + + // Now append the imap account name (escaped) to the folder uri. + folderUri += accountName; + this._folderUri = folderUri; + } + } else { + this._folderUri = lazy.MsgUtils.getMsgFolderURIFromPrefs( + this._userIdentity, + this._deliverMode + ); + if ( + (this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft && + this._compFields.draftId) || + (this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate && + this._compFields.templateId) + ) { + // Turn the draft/template ID into a folder URI string. + let messenger = Cc["@mozilla.org/messenger;1"].createInstance( + Ci.nsIMessenger + ); + try { + // This can fail if the user renames/removed/moved the folder. + folderUri = messenger.msgHdrFromURI( + this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft + ? this._compFields.draftId + : this._compFields.templateId + ).folder.URI; + } catch (ex) { + console.warn(ex); + } + // Only accept it if it's a subfolder of the identity's draft/template folder. + if (folderUri?.startsWith(this._folderUri)) { + this._folderUri = folderUri; + } + } + } + lazy.MsgUtils.sendLogger.debug( + `Processing fcc; folderUri=${this._folderUri}` + ); + + this._msgCopy = Cc[ + "@mozilla.org/messengercompose/msgcopy;1" + ].createInstance(Ci.nsIMsgCopy); + this._copyFile = await this._createCopyFile(); + lazy.MsgUtils.sendLogger.debug("fcc file created"); + + // Notify nsMsgCompose about the saved folder. + if (this._sendListener) { + this._sendListener.onGetDraftFolderURI( + this._compFields.messageId, + this._folderUri + ); + } + folder = lazy.MailUtils.getOrCreateFolder(this._folderUri); + let statusMsg = this._composeBundle.formatStringFromName( + "copyMessageStart", + [folder?.name || "?"] + ); + this._setStatusMessage(statusMsg); + lazy.MsgUtils.sendLogger.debug("startCopyOperation"); + try { + this._msgCopy.startCopyOperation( + this._userIdentity, + this._copyFile, + this._deliverMode, + this, + this._folderUri, + this._msgToReplace + ); + } catch (e) { + lazy.MsgUtils.sendLogger.warn( + `startCopyOperation failed with ${e.result}` + ); + if (throwOnError) { + throw Components.Exception("startCopyOperation failed", e.result); + } + this.notifyListenerOnStopCopy(e.result); + } + } + + /** + * Handle the fcc2 field. Then notify OnStopCopy and clean up. + */ + _doFcc2() { + // Handle fcc2 only once. + if (!this._fcc2Handled && this._compFields.fcc2) { + lazy.MsgUtils.sendLogger.debug("Processing fcc2"); + this._fcc2Handled = true; + this._mimeDoFcc( + this._compFields.fcc2, + false, + Ci.nsIMsgSend.nsMsgDeliverNow + ); + return; + } + + // NOTE: When nsMsgCompose receives OnStopCopy, it will release nsIMsgSend + // instance and close the compose window, which prevents the Promise from + // resolving in MsgComposeCommands.js. Use setTimeout to work around it. + lazy.setTimeout(() => { + try { + if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) { + this._sendListener.OnStopCopy(0); + } + } catch (e) { + // Ignore the return value of OnStopCopy. Non-zero nsresult will throw + // when going through XPConnect. In this case, we don't care about it. + console.warn( + `OnStopCopy failed with 0x${e.result.toString(16)}\n${e.stack}` + ); + } + this._cleanup(); + }); + } + + /** + * Run filters on the just sent message. + */ + _filterSentMessage() { + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_Filter; + let folder = lazy.MailUtils.getExistingFolder(this._folderUri); + let msgHdr = folder.GetMessageHeader(this._messageKey); + let msgWindow = this._sendProgress?.msgWindow; + return MailServices.filters.applyFilters( + Ci.nsMsgFilterType.PostOutgoing, + [msgHdr], + folder, + msgWindow, + this + ); + } + + _cleanup() { + lazy.MsgUtils.sendLogger.debug("Clean up temporary files"); + if (this._copyFile && this._copyFile != this._messageFile) { + IOUtils.remove(this._copyFile.path).catch(console.error); + this._copyFile = null; + } + if (this._deliveryFile && this._deliveryFile != this._messageFile) { + IOUtils.remove(this._deliveryFile.path).catch(console.error); + this._deliveryFile = null; + } + if (this._messageFile && this._shouldRemoveMessageFile) { + IOUtils.remove(this._messageFile.path).catch(console.error); + this._messageFile = null; + } + } + + /** + * Send this._deliveryFile to smtp service. + */ + async _deliverAsMail() { + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_SMTP; + this._setStatusMessage( + this._composeBundle.GetStringFromName("sendingMessage") + ); + let recipients = [ + this._compFields.to, + this._compFields.cc, + this._compFields.bcc, + ].filter(Boolean); + this._collectAddressesToAddressBook(recipients); + let converter = Cc["@mozilla.org/messenger/mimeconverter;1"].getService( + Ci.nsIMimeConverter + ); + let encodedRecipients = encodeURIComponent( + converter.encodeMimePartIIStr_UTF8( + recipients.join(","), + true, + 0, + Ci.nsIMimeConverter.MIME_ENCODED_WORD_SIZE + ) + ); + lazy.MsgUtils.sendLogger.debug( + `Delivering mail message <${this._compFields.messageId}>` + ); + let deliveryListener = new MsgDeliveryListener(this, false); + let msgStatus = + this._sendProgress instanceof Ci.nsIMsgStatusFeedback + ? this._sendProgress + : this._statusFeedback; + this._smtpRequest = {}; + // Do async call. This is necessary to ensure _smtpRequest is set so that + // cancel function can be obtained. + await MailServices.smtp.wrappedJSObject.sendMailMessage( + this._deliveryFile, + encodedRecipients, + this._userIdentity, + this._compFields.from, + this._smtpPassword, + deliveryListener, + msgStatus, + null, + this._compFields.DSN, + this._compFields.messageId, + {}, + this._smtpRequest + ); + } + + /** + * Send this._deliveryFile to nntp service. + */ + _deliverAsNews() { + this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_NNTP; + lazy.MsgUtils.sendLogger.debug("Delivering news message"); + let deliveryListener = new MsgDeliveryListener(this, true); + let msgWindow; + try { + msgWindow = + this._sendProgress?.msgWindow || + MailServices.mailSession.topmostMsgWindow; + } catch (e) {} + MailServices.nntp.postMessage( + this._deliveryFile, + this._compFields.newsgroups, + this._accountKey, + deliveryListener, + msgWindow, + null + ); + } + + /** + * Collect outgoing addresses to address book. + * + * @param {string[]} recipients - Outgoing addresses including to/cc/bcc. + */ + _collectAddressesToAddressBook(recipients) { + let createCard = Services.prefs.getBoolPref( + "mail.collect_email_address_outgoing", + false + ); + + let addressCollector = Cc[ + "@mozilla.org/addressbook/services/addressCollector;1" + ].getService(Ci.nsIAbAddressCollector); + for (let recipient of recipients) { + addressCollector.collectAddress(recipient, createCard); + } + } + + /** + * Check if link text is equivalent to the href. + * + * @param {string} text - The innerHTML of a <a> element. + * @param {string} href - The href of a <a> element. + * @returns {boolean} true if text is equivalent to href. + */ + _isLinkFreeText(text, href) { + href = href.trim(); + if (href.startsWith("mailto:")) { + return this._isLinkFreeText(text, href.slice("mailto:".length)); + } + text = text.trim(); + return ( + text == href || + (text.endsWith("/") && text.slice(0, -1) == href) || + (href.endsWith("/") && href.slice(0, -1) == text) + ); + } + + /** + * Collect embedded objects as attachments. + * + * @returns {object} collected + * @returns {nsIMsgAttachment[]} collected.embeddedAttachments + * @returns {object[]} collected.embeddedObjects objects {element, url} + */ + _gatherEmbeddedAttachments(editor) { + let embeddedAttachments = []; + let embeddedObjects = []; + + if (!editor || !editor.document) { + return { embeddedAttachments, embeddedObjects }; + } + let nodes = []; + nodes.push(...editor.document.querySelectorAll("img")); + nodes.push(...editor.document.querySelectorAll("a")); + let body = editor.document.querySelector("body[background]"); + if (body) { + nodes.push(body); + } + + let urlCidCache = {}; + for (let element of nodes) { + if (element.tagName == "A" && element.href) { + if (this._isLinkFreeText(element.innerHTML, element.href)) { + // Set this special classname, which is recognized by nsIParserUtils, + // so that links are not duplicated in text/plain. + element.classList.add("moz-txt-link-freetext"); + } + } + let isImage = false; + let url; + let name; + let mozDoNotSend = element.getAttribute("moz-do-not-send"); + if (mozDoNotSend && mozDoNotSend != "false") { + // Only empty or moz-do-not-send="false" may be accepted later. + continue; + } + if (element.tagName == "BODY" && element.background) { + isImage = true; + url = element.background; + } else if (element.tagName == "IMG" && element.src) { + isImage = true; + url = element.src; + name = element.name; + } else if (element.tagName == "A" && element.href) { + url = element.href; + name = element.name; + } else { + continue; + } + let acceptObject = false; + // Before going further, check what scheme we're dealing with. Files need to + // be converted to data URLs during composition. "Attaching" means + // sending as a cid: part instead of original URL. + if (/^https?:\/\//i.test(url)) { + acceptObject = + (isImage && + Services.prefs.getBoolPref( + "mail.compose.attach_http_images", + false + )) || + mozDoNotSend == "false"; + } + if (/^(data|news|snews|nntp):/i.test(url)) { + acceptObject = true; + } + if (!acceptObject) { + continue; + } + + let cid; + if (urlCidCache[url]) { + // If an url has already been inserted as MimePart, just reuse the cid. + cid = urlCidCache[url]; + } else { + cid = lazy.MsgUtils.makeContentId( + this._userIdentity, + embeddedAttachments.length + 1 + ); + urlCidCache[url] = cid; + + let attachment = Cc[ + "@mozilla.org/messengercompose/attachment;1" + ].createInstance(Ci.nsIMsgAttachment); + attachment.name = name || lazy.MsgUtils.pickFileNameFromUrl(url); + attachment.contentId = cid; + attachment.url = url; + embeddedAttachments.push(attachment); + } + embeddedObjects.push({ + element, + url, + }); + + let newUrl = `cid:${cid}`; + if (element.tagName == "BODY") { + element.background = newUrl; + } else if (element.tagName == "IMG") { + element.src = newUrl; + } else if (element.tagName == "A") { + element.href = newUrl; + } + } + return { embeddedAttachments, embeddedObjects }; + } + + /** + * Restore embedded objects in editor to their original urls. + * + * @param {object[]} embeddedObjects - An array of embedded objects. + * @param {Element} embeddedObjects.element + * @param {string} embeddedObjects.url + */ + _restoreEditorContent(embeddedObjects) { + for (let { element, url } of embeddedObjects) { + if (element.tagName == "BODY") { + element.background = url; + } else if (element.tagName == "IMG") { + element.src = url; + } else if (element.tagName == "A") { + element.href = url; + } + } + } + + /** + * Get the message body from an editor. + * + * @param {nsIEditor} editor - The editor instance. + * @returns {string} + */ + _getBodyFromEditor(editor) { + if (!editor) { + return ""; + } + + let flags = + Ci.nsIDocumentEncoder.OutputFormatted | + Ci.nsIDocumentEncoder.OutputNoFormattingInPre | + Ci.nsIDocumentEncoder.OutputDisallowLineBreaking; + // bodyText is UTF-16 string. + let bodyText = editor.outputToString("text/html", flags); + + // No need to do conversion if forcing plain text. + if (!this._compFields.forcePlainText) { + let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService( + Ci.mozITXTToHTMLConv + ); + let csFlags = Ci.mozITXTToHTMLConv.kURLs; + if (Services.prefs.getBoolPref("mail.send_struct", false)) { + csFlags |= Ci.mozITXTToHTMLConv.kStructPhrase; + } + bodyText = cs.scanHTML(bodyText, csFlags); + } + + return bodyText; + } + + /** + * Get the first account key of an identity. + * + * @param {nsIMsgIdentity} identity - The identity. + * @returns {string} + */ + _accountKeyForIdentity(identity) { + let servers = MailServices.accounts.getServersForIdentity(identity); + return servers.length + ? MailServices.accounts.FindAccountForServer(servers[0])?.key + : null; + } +} + +/** + * A listener to be passed to the SMTP service. + * + * @implements {nsIUrlListener} + */ +class MsgDeliveryListener { + 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) { + this._msgSend.notifyListenerOnStartSending(null, 0); + } + + OnStopRunningUrl(url, exitCode) { + lazy.MsgUtils.sendLogger.debug(`OnStopRunningUrl; exitCode=${exitCode}`); + let mailUrl = url.QueryInterface(Ci.nsIMsgMailNewsUrl); + mailUrl.UnRegisterListener(this); + + this._msgSend.sendDeliveryCallback(url, this._isNewsDelivery, exitCode); + } +} diff --git a/comm/mailnews/compose/src/MimeEncoder.jsm b/comm/mailnews/compose/src/MimeEncoder.jsm new file mode 100644 index 0000000000..ab4c60de42 --- /dev/null +++ b/comm/mailnews/compose/src/MimeEncoder.jsm @@ -0,0 +1,430 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["MimeEncoder"]; + +const LINELENGTH_ENCODING_THRESHOLD = 990; +const MESSAGE_RFC822 = "message/rfc822"; + +/** + * A class to pick Content-Transfer-Encoding for a MimePart, and encode MimePart + * body accordingly. + */ +class MimeEncoder { + /** + * Create a MimeEncoder. + * + * @param {string} charset + * @param {string} contentType + * @param {boolean} forceMsgEncoding + * @param {boolean} isMainBody + * @param {string} content + */ + constructor(charset, contentType, forceMsgEncoding, isMainBody, content) { + this._charset = charset; + this._contentType = contentType.toLowerCase(); + this._forceMsgEncoding = forceMsgEncoding; + this._isMainBody = isMainBody; + this._body = content; + this._bodySize = content.length; + + // The encoding value will be used to set Content-Transfer-Encoding header + // and encode this._body. + this._encoding = ""; + + // Flags used to pick encoding. + this._highBitCount = 0; + this._unPrintableCount = 0; + this._ctrlCount = 0; + this._nullCount = 0; + this._hasCr = 0; + this._hasLf = 0; + this._hasCrLf = 0; + this._maxColumn = 0; + } + + /** + * @type {string} + */ + get encoding() { + return this._encoding; + } + + /** + * Use the combination of charset, content type and scanning this._body to + * decide what encoding it should have. + */ + pickEncoding() { + this._analyzeBody(); + + let strictlyMime = Services.prefs.getBoolPref("mail.strictly_mime"); + let needsB64 = false; + let isUsingQP = false; + + // Allow users to override our percentage-wise guess on whether + // the file is text or binary. + let forceB64 = Services.prefs.getBoolPref("mail.file_attach_binary"); + + // If the content-type is "image/" or something else known to be binary or + // several flavors of newlines are present, use base64 unless we're attaching + // a message (so that we don't get confused by newline conversions). + if ( + !this._isMainBody && + (forceB64 || + this._requiresB64() || + this._hasCr + this._hasLf + this._hasCrLf != 1) && + this._contentType != MESSAGE_RFC822 + ) { + needsB64 = true; + } else { + // Otherwise, we need to pick an encoding based on the contents of the + // document. + let encodeP = false; + + // Force quoted-printable if the sender does not allow conversion to 7bit. + if ( + this._forceMsgEncoding || + this._maxColumn > LINELENGTH_ENCODING_THRESHOLD || + (strictlyMime && this._unPrintableCount) || + this._nullCount + ) { + if ( + this._isMainBody && + this._contentType == "text/plain" && + // From rfc3676#section-4.2, Quoted-Printable encoding SHOULD NOT be + // used with Format=Flowed unless absolutely necessary. + Services.prefs.getBoolPref("mailnews.send_plaintext_flowed") + ) { + needsB64 = true; + } else { + encodeP = true; + } + } + + // MIME requires a special case that these types never be encoded. + if ( + this._contentType.startsWith("message") || + this._contentType.startsWith("multipart") + ) { + encodeP = false; + } + + let manager = Cc["@mozilla.org/charset-converter-manager;1"].getService( + Ci.nsICharsetConverterManager + ); + let isCharsetMultiByte = false; + try { + isCharsetMultiByte = + manager.getCharsetData(this._charset, ".isMultibyte") == "true"; + } catch {} + + // If the Mail charset is multibyte, we force it to use Base64 for + // attachments. + if ( + !this._isMainBody && + this._charset && + isCharsetMultiByte && + (this._contentType.startsWith("text") || + // text/vcard synonym + this._contentType == "application/directory") + ) { + needsB64 = true; + } else if (this._charset == "ISO-2022-JP") { + this._encoding = "7bit"; + } else if (encodeP && this._unPrintableCount > this._bodySize / 10) { + // If the document contains more than 10% unprintable characters, + // then that seems like a good candidate for base64 instead of + // quoted-printable. + needsB64 = true; + } else if (encodeP) { + this._encoding = "quoted-printable"; + isUsingQP = true; + } else if (this._highBitCount > 0) { + this._encoding = "8bit"; + } else { + this._encoding = "7bit"; + } + } + + // Always base64 binary data. + if (needsB64) { + this._encoding = "base64"; + } + + // According to RFC 821 we must always have lines shorter than 998 bytes. + // To encode "long lines" use a CTE that will transmit shorter lines. + // Switch to base64 if we are not already using "quoted printable". + + // We don't do this for message/rfc822 attachments, since we can't + // change the original Content-Transfer-Encoding of the message we're + // attaching. We rely on the original message complying with RFC 821, + // if it doesn't we won't either. Not ideal. + if ( + this._contentType != MESSAGE_RFC822 && + this._maxColumn > LINELENGTH_ENCODING_THRESHOLD && + !isUsingQP + ) { + this._encoding = "base64"; + } + } + + /** + * Encode this._body according to the value of this.encoding. + */ + encode() { + let output; + if (this.encoding == "base64") { + output = this._encodeBase64(); + } else if (this.encoding == "quoted-printable") { + output = this._encodeQP(); + } else { + output = this._body.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n"); + } + if (!output.endsWith("\r\n")) { + output += "\r\n"; + } + return output; + } + + /** + * Scan this._body to set flags that will be used by pickEncoding. + */ + _analyzeBody() { + let currentColumn = 0; + let prevCharWasCr = false; + + for (let i = 0; i < this._bodySize; i++) { + let ch = this._body.charAt(i); + let charCode = this._body.charCodeAt(i); + if (charCode > 126) { + this._highBitCount++; + this._unPrintableCount++; + } else if (ch < " " && !"\t\r\n".includes(ch)) { + this._unPrintableCount++; + this._ctrlCount++; + if (ch == "\0") { + this._nullCount++; + } + } + + if ("\r\n".includes(ch)) { + if (ch == "\r") { + if (prevCharWasCr) { + this._hasCr = 1; + } else { + prevCharWasCr = true; + } + } else if (prevCharWasCr) { + if (currentColumn == 0) { + this._hasCrLf = 1; + } else { + this._hasCr = 1; + this._hasLf = 1; + } + prevCharWasCr = false; + } else { + this._hasLf = 1; + } + + if (this._maxColumn < currentColumn) { + this._maxColumn = currentColumn; + } + currentColumn = 0; + } else { + currentColumn++; + } + } + + if (this._maxColumn < currentColumn) { + this._maxColumn = currentColumn; + } + } + + /** + * Determine if base64 is required according to contentType. + */ + _requiresB64() { + if (this._contentType == "application/x-unknown-content-type") { + // Unknown types don't necessarily require encoding. (Note that + // "unknown" and "application/octet-stream" aren't the same.) + return false; + } + if ( + this._contentType.startsWith("image/") || + this._contentType.startsWith("audio/") || + this._contentType.startsWith("video/") || + this._contentType.startsWith("application/") + ) { + // The following types are application/ or image/ types that are actually + // known to contain textual data (meaning line-based, not binary, where + // CRLF conversion is desired rather than disastrous.) So, if the type + // is any of these, it does not *require* base64, and if we do need to + // encode it for other reasons, we'll probably use quoted-printable. + // But, if it's not one of these types, then we assume that any subtypes + // of the non-"text/" types are binary data, where CRLF conversion would + // corrupt it, so we use base64 right off the bat. + // The reason it's desirable to ship these as text instead of just using + // base64 all the time is mainly to preserve the readability of them for + // non-MIME users: if I mail a /bin/sh script to someone, it might not + // need to be encoded at all, so we should leave it readable if we can. + // This list of types was derived from the comp.mail.mime FAQ, section + // 10.2.2, "List of known unregistered MIME types" on 2-Feb-96. + const typesWhichAreReallyText = [ + "application/mac-binhex40", // APPLICATION_BINHEX + "application/pgp", // APPLICATION_PGP + "application/pgp-keys", + "application/x-pgp-message", // APPLICATION_PGP2 + "application/postscript", // APPLICATION_POSTSCRIPT + "application/x-uuencode", // APPLICATION_UUENCODE + "application/x-uue", // APPLICATION_UUENCODE2 + "application/uue", // APPLICATION_UUENCODE4 + "application/uuencode", // APPLICATION_UUENCODE3 + "application/sgml", + "application/x-csh", + "application/javascript", + "application/ecmascript", + "application/x-javascript", + "application/x-latex", + "application/x-macbinhex40", + "application/x-ns-proxy-autoconfig", + "application/x-www-form-urlencoded", + "application/x-perl", + "application/x-sh", + "application/x-shar", + "application/x-tcl", + "application/x-tex", + "application/x-texinfo", + "application/x-troff", + "application/x-troff-man", + "application/x-troff-me", + "application/x-troff-ms", + "application/x-troff-ms", + "application/x-wais-source", + "image/x-bitmap", + "image/x-pbm", + "image/x-pgm", + "image/x-portable-anymap", + "image/x-portable-bitmap", + "image/x-portable-graymap", + "image/x-portable-pixmap", // IMAGE_PPM + "image/x-ppm", + "image/x-xbitmap", // IMAGE_XBM + "image/x-xbm", // IMAGE_XBM2 + "image/xbm", // IMAGE_XBM3 + "image/x-xpixmap", + "image/x-xpm", + ]; + if (typesWhichAreReallyText.includes(this._contentType)) { + return false; + } + return true; + } + return false; + } + + /** + * Base64 encoding. See RFC 2045 6.8. We use the built-in `btoa`, then ensure + * line width is no more than 72. + */ + _encodeBase64() { + let encoded = btoa(this._body); + let ret = ""; + let length = encoded.length; + let i = 0; + let limit = 72; + while (true) { + if (i * limit > length) { + break; + } + ret += encoded.substr(i * limit, limit) + "\r\n"; + i++; + } + return ret; + } + + /** + * Quoted-printable encoding. See RFC 2045 6.7. + */ + _encodeQP() { + let currentColumn = 0; + let hexdigits = "0123456789ABCDEF"; + let white = false; + let out = ""; + + function encodeChar(ch) { + let charCode = ch.charCodeAt(0); + let ret = "="; + ret += hexdigits[charCode >> 4]; + ret += hexdigits[charCode & 0xf]; + return ret; + } + + for (let i = 0; i < this._bodySize; i++) { + let ch = this._body.charAt(i); + let charCode = this._body.charCodeAt(i); + if (ch == "\r" || ch == "\n") { + // If it's CRLF, swallow two chars instead of one. + if (i + 1 < this._bodySize && ch == "\r" && this._body[i + 1] == "\n") { + i++; + } + + // Whitespace cannot be allowed to occur at the end of the line, so we + // back up and replace the whitespace with its code. + if (white) { + let whiteChar = out.slice(-1); + out = out.slice(0, -1); + out += encodeChar(whiteChar); + } + + // Now write out the newline. + out += "\r"; + out += "\n"; + white = false; + currentColumn = 0; + } else if ( + currentColumn == 0 && + (ch == "." || + (ch == "F" && + (i >= this._bodySize - 1 || this._body[i + 1] == "r") && + (i >= this._bodySize - 2 || this._body[i + 2] == "o") && + (i >= this._bodySize - 3 || this._body[i + 3] == "m") && + (i >= this._bodySize - 4 || this._body[i + 4] == " "))) + ) { + // Just to be SMTP-safe, if "." appears in column 0, encode it. + // If this line begins with "From " (or it could but we don't have enough + // data in the buffer to be certain), encode the 'F' in hex to avoid + // potential problems with BSD mailbox formats. + white = false; + out += encodeChar(ch); + currentColumn += 3; + } else if ( + (charCode >= 33 && charCode <= 60) || + (charCode >= 62 && charCode <= 126) + ) { + // Printable characters except for '=' + white = false; + out += ch; + currentColumn++; + } else if (ch == " " || ch == "\t") { + // Whitespace + white = true; + out += ch; + currentColumn++; + } else { + white = false; + out += encodeChar(ch); + currentColumn += 3; + } + + if (currentColumn >= 73) { + // Soft line break for readability + out += "=\r\n"; + white = false; + currentColumn = 0; + } + } + + return out; + } +} diff --git a/comm/mailnews/compose/src/MimeMessage.jsm b/comm/mailnews/compose/src/MimeMessage.jsm new file mode 100644 index 0000000000..9423e84004 --- /dev/null +++ b/comm/mailnews/compose/src/MimeMessage.jsm @@ -0,0 +1,625 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["MimeMessage"]; + +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +let { MimeMultiPart, MimePart } = ChromeUtils.import( + "resource:///modules/MimePart.jsm" +); +let { MsgUtils } = ChromeUtils.import( + "resource:///modules/MimeMessageUtils.jsm" +); +let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); + +/** + * A class to create a top MimePart and write to a tmp file. It works like this: + * 1. collect top level MIME headers (_gatherMimeHeaders) + * 2. collect HTML/plain main body as MimePart[] (_gatherMainParts) + * 3. collect attachments as MimePart[] (_gatherAttachmentParts) + * 4. construct a top MimePart with above headers and MimePart[] (_initMimePart) + * 5. write the top MimePart to a tmp file (createMessageFile) + * NOTE: It's possible we will want to replace nsIMsgSend with the interfaces of + * MimeMessage. As a part of it, we will add a `send` method to this class. + */ +class MimeMessage { + /** + * Construct a MimeMessage. + * + * @param {nsIMsgIdentity} userIdentity + * @param {nsIMsgCompFields} compFields + * @param {string} fcc - The FCC header value. + * @param {string} bodyType + * @param {BinaryString} bodyText - This is ensured to be a 8-bit string, to + * be handled the same as attachment content. + * @param {nsMsgDeliverMode} deliverMode + * @param {string} originalMsgURI + * @param {MSG_ComposeType} compType + * @param {nsIMsgAttachment[]} embeddedAttachments - Usually Embedded images. + * @param {nsIMsgSendReport} sendReport - Used by _startCryptoEncapsulation. + */ + constructor( + userIdentity, + compFields, + fcc, + bodyType, + bodyText, + deliverMode, + originalMsgURI, + compType, + embeddedAttachments, + sendReport + ) { + this._userIdentity = userIdentity; + this._compFields = compFields; + this._fcc = fcc; + this._bodyType = bodyType; + this._bodyText = bodyText; + this._deliverMode = deliverMode; + this._compType = compType; + this._embeddedAttachments = embeddedAttachments; + this._sendReport = sendReport; + } + + /** + * Write a MimeMessage to a tmp file. + * + * @returns {nsIFile} + */ + async createMessageFile() { + let topPart = this._initMimePart(); + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("nsemail.eml"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + let fstream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + this._fstream = Cc[ + "@mozilla.org/network/buffered-output-stream;1" + ].createInstance(Ci.nsIBufferedOutputStream); + fstream.init(file, -1, -1, 0); + this._fstream.init(fstream, 16 * 1024); + + this._composeSecure = this._getComposeSecure(); + if (this._composeSecure) { + await this._writePart(topPart); + this._composeSecure.finishCryptoEncapsulation(false, this._sendReport); + } else { + await this._writePart(topPart); + } + + this._fstream.close(); + fstream.close(); + + return file; + } + + /** + * Create a top MimePart to represent the full message. + * + * @returns {MimePart} + */ + _initMimePart() { + let { plainPart, htmlPart } = this._gatherMainParts(); + let embeddedParts = this._gatherEmbeddedParts(); + let attachmentParts = this._gatherAttachmentParts(); + + let relatedPart = htmlPart; + if (htmlPart && embeddedParts.length > 0) { + relatedPart = new MimeMultiPart("related"); + relatedPart.addPart(htmlPart); + relatedPart.addParts(embeddedParts); + } + let mainParts = [plainPart, relatedPart].filter(Boolean); + let topPart; + if (attachmentParts.length > 0) { + // Use multipart/mixed as long as there is at least one attachment. + topPart = new MimeMultiPart("mixed"); + if (plainPart && relatedPart) { + // Wrap mainParts inside a multipart/alternative MimePart. + let alternativePart = new MimeMultiPart("alternative"); + alternativePart.addParts(mainParts); + topPart.addPart(alternativePart); + } else { + topPart.addParts(mainParts); + } + topPart.addParts(attachmentParts); + } else { + if (mainParts.length > 1) { + // Mark the topPart as multipart/alternative. + topPart = new MimeMultiPart("alternative"); + } else { + topPart = new MimePart(); + } + topPart.addParts(mainParts); + } + + topPart.setHeaders(this._gatherMimeHeaders()); + + return topPart; + } + + /** + * Collect top level headers like From/To/Subject into a Map. + */ + _gatherMimeHeaders() { + let messageId = this._compFields.messageId; + if ( + !messageId && + (this._compFields.to || + this._compFields.cc || + this._compFields.bcc || + !this._compFields.newsgroups || + this._userIdentity.getBoolAttribute("generate_news_message_id")) + ) { + // Try to use the domain name of the From header to generate the message ID. We + // specifically don't use the nsIMsgIdentity associated with the account, because + // the user might have changed the address in the From header to use a different + // domain, and we don't want to leak the relationship between the domains. + const fromHdr = MailServices.headerParser.parseEncodedHeaderW( + this._compFields.from + ); + const fromAddr = fromHdr[0].email; + + // Extract the host from the address, if any, and generate a message ID from it. + // If we can't get a host for the message ID, let SMTP populate the header. + const atIndex = fromAddr.indexOf("@"); + if (atIndex >= 0) { + messageId = Cc["@mozilla.org/messengercompose/computils;1"] + .createInstance(Ci.nsIMsgCompUtils) + .msgGenerateMessageId( + this._userIdentity, + fromAddr.slice(atIndex + 1) + ); + } + + this._compFields.messageId = messageId; + } + let headers = new Map([ + ["message-id", messageId], + ["date", new Date()], + ["mime-version", "1.0"], + ]); + + if (Services.prefs.getBoolPref("mailnews.headers.sendUserAgent")) { + if (Services.prefs.getBoolPref("mailnews.headers.useMinimalUserAgent")) { + headers.set( + "user-agent", + Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandFullName") + ); + } else { + headers.set( + "user-agent", + Cc["@mozilla.org/network/protocol;1?name=http"].getService( + Ci.nsIHttpProtocolHandler + ).userAgent + ); + } + } + + for (let headerName of [...this._compFields.headerNames]) { + let headerContent = this._compFields.getRawHeader(headerName); + if (headerContent) { + headers.set(headerName, headerContent); + } + } + let isDraft = [ + Ci.nsIMsgSend.nsMsgQueueForLater, + Ci.nsIMsgSend.nsMsgDeliverBackground, + Ci.nsIMsgSend.nsMsgSaveAsDraft, + Ci.nsIMsgSend.nsMsgSaveAsTemplate, + ].includes(this._deliverMode); + + let undisclosedRecipients = MsgUtils.getUndisclosedRecipients( + this._compFields, + this._deliverMode + ); + if (undisclosedRecipients) { + headers.set("to", undisclosedRecipients); + } + + if (isDraft) { + headers + .set( + "x-mozilla-draft-info", + MsgUtils.getXMozillaDraftInfo(this._compFields) + ) + .set("x-identity-key", this._userIdentity.key) + .set("fcc", this._fcc); + } + + if (messageId) { + // MDN request header requires to have MessageID header presented in the + // message in order to coorelate the MDN reports to the original message. + headers + .set( + "disposition-notification-to", + MsgUtils.getDispositionNotificationTo( + this._compFields, + this._deliverMode + ) + ) + .set( + "return-receipt-to", + MsgUtils.getReturnReceiptTo(this._compFields, this._deliverMode) + ); + } + + for (let { headerName, headerValue } of MsgUtils.getDefaultCustomHeaders( + this._userIdentity + )) { + headers.set(headerName, headerValue); + } + + let rawMftHeader = headers.get("mail-followup-to"); + // If there's already a Mail-Followup-To header, don't need to do anything. + if (!rawMftHeader) { + headers.set( + "mail-followup-to", + MsgUtils.getMailFollowupToHeader(this._compFields, this._userIdentity) + ); + } + + let rawMrtHeader = headers.get("mail-reply-to"); + // If there's already a Mail-Reply-To header, don't need to do anything. + if (!rawMrtHeader) { + headers.set( + "mail-reply-to", + MsgUtils.getMailReplyToHeader( + this._compFields, + this._userIdentity, + rawMrtHeader + ) + ); + } + + let rawPriority = headers.get("x-priority"); + if (rawPriority) { + headers.set("x-priority", MsgUtils.getXPriority(rawPriority)); + } + + let rawReferences = headers.get("references"); + if (rawReferences) { + let references = MsgUtils.getReferences(rawReferences); + // Don't reset "references" header if references is undefined. + if (references) { + headers.set("references", references); + } + headers.set("in-reply-to", MsgUtils.getInReplyTo(rawReferences)); + } + if ( + rawReferences && + [ + Ci.nsIMsgCompType.ForwardInline, + Ci.nsIMsgCompType.ForwardAsAttachment, + ].includes(this._compType) + ) { + headers.set("x-forwarded-message-id", rawReferences); + } + + let rawNewsgroups = headers.get("newsgroups"); + if (rawNewsgroups) { + let { newsgroups, newshost } = MsgUtils.getNewsgroups( + this._deliverMode, + rawNewsgroups + ); + // Don't reset "newsgroups" header if newsgroups is undefined. + if (newsgroups) { + headers.set("newsgroups", newsgroups); + } + headers.set("x-mozilla-news-host", newshost); + } + + return headers; + } + + /** + * Determine if the message should include an HTML part, a plain part or both. + * + * @returns {{plainPart: MimePart, htmlPart: MimePart}} + */ + _gatherMainParts() { + let formatFlowed = Services.prefs.getBoolPref( + "mailnews.send_plaintext_flowed" + ); + let formatParam = ""; + if (formatFlowed) { + // Set format=flowed as in RFC 2646 according to the preference. + formatParam += "; format=flowed"; + } + + let htmlPart = null; + let plainPart = null; + let parts = {}; + + if (this._bodyType === "text/html") { + htmlPart = new MimePart( + this._bodyType, + this._compFields.forceMsgEncoding, + true + ); + htmlPart.setHeader("content-type", "text/html; charset=UTF-8"); + htmlPart.bodyText = this._bodyText; + } else if (this._bodyType === "text/plain") { + plainPart = new MimePart( + this._bodyType, + this._compFields.forceMsgEncoding, + true + ); + plainPart.setHeader( + "content-type", + `text/plain; charset=UTF-8${formatParam}` + ); + plainPart.bodyText = this._bodyText; + parts.plainPart = plainPart; + } + + // Assemble a multipart/alternative message. + if ( + (this._compFields.forcePlainText || + this._compFields.useMultipartAlternative) && + plainPart === null && + htmlPart !== null + ) { + plainPart = new MimePart( + "text/plain", + this._compFields.forceMsgEncoding, + true + ); + plainPart.setHeader( + "content-type", + `text/plain; charset=UTF-8${formatParam}` + ); + // nsIParserUtils.convertToPlainText expects unicode string. + let plainUnicode = MsgUtils.convertToPlainText( + new TextDecoder().decode( + jsmime.mimeutils.stringToTypedArray(this._bodyText) + ), + formatFlowed + ); + // MimePart.bodyText should be binary string. + plainPart.bodyText = jsmime.mimeutils.typedArrayToString( + new TextEncoder().encode(plainUnicode) + ); + + parts.plainPart = plainPart; + } + + // If useMultipartAlternative is true, send multipart/alternative message. + // Otherwise, send the plainPart only. + if (htmlPart) { + if ( + (plainPart && this._compFields.useMultipartAlternative) || + !plainPart + ) { + parts.htmlPart = htmlPart; + } + } + + return parts; + } + + /** + * Collect local attachments. + * + * @returns {MimePart[]} + */ + _gatherAttachmentParts() { + let attachments = [...this._compFields.attachments]; + let cloudParts = []; + let localParts = []; + + for (let attachment of attachments) { + let part; + if (attachment.htmlAnnotation) { + part = new MimePart(); + // MimePart.bodyText should be binary string. + part.bodyText = jsmime.mimeutils.typedArrayToString( + new TextEncoder().encode(attachment.htmlAnnotation) + ); + part.setHeader("content-type", "text/html; charset=utf-8"); + + let suffix = /\.html$/i.test(attachment.name) ? "" : ".html"; + let encodedFilename = MsgUtils.rfc2231ParamFolding( + "filename", + `${attachment.name}${suffix}` + ); + part.setHeader("content-disposition", `attachment; ${encodedFilename}`); + } else { + part = new MimePart(null, this._compFields.forceMsgEncoding, false); + part.setBodyAttachment(attachment); + } + + let cloudPartHeader = MsgUtils.getXMozillaCloudPart( + this._deliverMode, + attachment + ); + if (cloudPartHeader) { + part.setHeader("x-mozilla-cloud-part", cloudPartHeader); + } + + localParts.push(part); + } + // Cloud attachments are handled before local attachments in the C++ + // implementation. We follow it here so that no need to change tests. + return cloudParts.concat(localParts); + } + + /** + * Collect embedded objects as attachments. + * + * @returns {MimePart[]} + */ + _gatherEmbeddedParts() { + return this._embeddedAttachments.map(attachment => { + let part = new MimePart(null, this._compFields.forceMsgEncoding, false); + part.setBodyAttachment(attachment, "inline", attachment.contentId); + return part; + }); + } + + /** + * If crypto encapsulation is required, returns an nsIMsgComposeSecure instance. + * + * @returns {nsIMsgComposeSecure} + */ + _getComposeSecure() { + let secureCompose = this._compFields.composeSecure; + if (!secureCompose) { + return null; + } + + if ( + this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft && + !this._userIdentity.getBoolAttribute("autoEncryptDrafts") + ) { + return null; + } + + if ( + !secureCompose.requiresCryptoEncapsulation( + this._userIdentity, + this._compFields + ) + ) { + return null; + } + return secureCompose; + } + + /** + * Pass a stream and other params to this._composeSecure to start crypto + * encapsulation. + */ + _startCryptoEncapsulation() { + let recipients = [ + this._compFields.to, + this._compFields.cc, + this._compFields.bcc, + this._compFields.newsgroups, + ] + .filter(Boolean) + .join(","); + + this._composeSecure.beginCryptoEncapsulation( + this._fstream, + recipients, + this._compFields, + this._userIdentity, + this._sendReport, + this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft + ); + this._cryptoEncapsulationStarted = true; + } + + /** + * Recursively write an MimePart and its parts to a this._fstream. + * + * @param {MimePart} curPart - The MimePart to write out. + * @param {number} [depth=0] - Nested level of a part. + */ + async _writePart(curPart, depth = 0) { + let bodyString; + try { + // `getEncodedBodyString()` returns a binary string. + bodyString = await curPart.getEncodedBodyString(); + } catch (e) { + if (e.data && /^data:/i.test(e.data.url)) { + // Invalid data uri should not prevent sending message. + return; + } + throw e; + } + + if (depth == 0 && this._composeSecure) { + // Crypto encapsulation will add a new content-type header. + curPart.deleteHeader("content-type"); + if (curPart.parts.length > 1) { + // Move child parts one layer deeper so that the message is still well + // formed after crypto encapsulation. + let newChild = new MimeMultiPart(curPart.subtype); + newChild.parts = curPart._parts; + curPart.parts = [newChild]; + } + } + + // Write out headers, there could be non-ASCII in the headers + // which we need to encode into UTF-8. + this._writeString(curPart.getHeaderString()); + + // Start crypto encapsulation if needed. + if (depth == 0 && this._composeSecure) { + this._startCryptoEncapsulation(); + } + + // Recursively write out parts. + if (curPart.parts.length) { + // single part message + if (curPart.parts.length === 1) { + await this._writePart(curPart.parts[0], depth + 1); + this._writeBinaryString(bodyString); + return; + } + + // We can safely use `_writeBinaryString()` for ASCII strings. + this._writeBinaryString("\r\n"); + if (depth == 0) { + // Current part is a top part and multipart container. + this._writeBinaryString( + "This is a multi-part message in MIME format.\r\n" + ); + } + + // multipart message + for (let part of curPart.parts) { + this._writeBinaryString(`--${curPart.separator}\r\n`); + await this._writePart(part, depth + 1); + } + this._writeBinaryString(`\r\n--${curPart.separator}--\r\n`); + if (depth > 1) { + // If more separators follow, make sure there is a blank line after + // this one. + this._writeBinaryString("\r\n"); + } + } else { + this._writeBinaryString(`\r\n`); + } + + // Ensure there is exactly one blank line after a part and before + // the boundary, and exactly one blank line between boundary lines. + // This works around bugs in other software that erroneously remove + // additional blank lines, thereby causing verification failures of + // OpenPGP or S/MIME signatures. For example see bug 1731529. + + // Write out body. + this._writeBinaryString(bodyString); + } + + /** + * Write a binary string to this._fstream. + * + * @param {BinaryString} str - The binary string to write. + */ + _writeBinaryString(str) { + this._cryptoEncapsulationStarted + ? this._composeSecure.mimeCryptoWriteBlock(str, str.length) + : this._fstream.write(str, str.length); + } + + /** + * Write a string to this._fstream. + * + * @param {string} str - The string to write. + */ + _writeString(str) { + this._writeBinaryString( + jsmime.mimeutils.typedArrayToString(new TextEncoder().encode(str)) + ); + } +} diff --git a/comm/mailnews/compose/src/MimeMessageUtils.jsm b/comm/mailnews/compose/src/MimeMessageUtils.jsm new file mode 100644 index 0000000000..65682b6d4c --- /dev/null +++ b/comm/mailnews/compose/src/MimeMessageUtils.jsm @@ -0,0 +1,1058 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["MsgUtils"]; + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); +var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); + +// Defined in ErrorList.h. +const NS_ERROR_MODULE_BASE_OFFSET = 69; +const NS_ERROR_MODULE_MAILNEWS = 16; + +/** + * Generate an NS_ERROR code from a MAILNEWS error code. See NS_ERROR_GENERATE + * in nsError.h and NS_MSG_GENERATE_FAILURE in nsComposeStrings.h. + * + * @param {number} code - The error code in MAILNEWS module. + * @returns {number} + */ +function generateNSError(code) { + return ( + ((1 << 31) | + ((NS_ERROR_MODULE_MAILNEWS + NS_ERROR_MODULE_BASE_OFFSET) << 16) | + code) >>> + 0 + ); +} + +/** + * Collection of helper functions for message sending process. + */ +var MsgUtils = { + /** + * Error codes defined in nsComposeStrings.h + */ + NS_MSG_UNABLE_TO_OPEN_FILE: generateNSError(12500), + NS_MSG_UNABLE_TO_OPEN_TMP_FILE: generateNSError(12501), + NS_MSG_UNABLE_TO_SAVE_TEMPLATE: generateNSError(12502), + NS_MSG_UNABLE_TO_SAVE_DRAFT: generateNSError(12503), + NS_MSG_COULDNT_OPEN_FCC_FOLDER: generateNSError(12506), + NS_MSG_NO_SENDER: generateNSError(12510), + NS_MSG_NO_RECIPIENTS: generateNSError(12511), + NS_MSG_ERROR_WRITING_FILE: generateNSError(12512), + NS_ERROR_SENDING_FROM_COMMAND: generateNSError(12514), + NS_ERROR_SENDING_DATA_COMMAND: generateNSError(12516), + NS_ERROR_SENDING_MESSAGE: generateNSError(12517), + NS_ERROR_POST_FAILED: generateNSError(12518), + NS_ERROR_SMTP_SERVER_ERROR: generateNSError(12524), + NS_MSG_UNABLE_TO_SEND_LATER: generateNSError(12525), + NS_ERROR_COMMUNICATIONS_ERROR: generateNSError(12526), + NS_ERROR_BUT_DONT_SHOW_ALERT: generateNSError(12527), + NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS: generateNSError(12529), + NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY: generateNSError(12530), + NS_ERROR_MIME_MPART_ATTACHMENT_ERROR: generateNSError(12531), + + // 12554 is taken by NS_ERROR_NNTP_NO_CROSS_POSTING. use 12555 as the next one + + // For message sending report + NS_MSG_ERROR_READING_FILE: generateNSError(12563), + + NS_MSG_ERROR_ATTACHING_FILE: generateNSError(12570), + + NS_ERROR_SMTP_GREETING: generateNSError(12572), + + NS_ERROR_SENDING_RCPT_COMMAND: generateNSError(12575), + + NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS: generateNSError(12582), + + NS_ERROR_SMTP_PASSWORD_UNDEFINED: generateNSError(12584), + NS_ERROR_SMTP_SEND_NOT_ALLOWED: generateNSError(12585), + NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED: generateNSError(12586), + NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2: generateNSError(12588), + + NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER: generateNSError(12589), + NS_ERROR_SMTP_SEND_FAILED_REFUSED: generateNSError(12590), + NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED: generateNSError(12591), + NS_ERROR_SMTP_SEND_FAILED_TIMEOUT: generateNSError(12592), + NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON: generateNSError(12593), + + NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL: generateNSError(12594), + NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL: generateNSError(12595), + NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT: generateNSError(12596), + NS_ERROR_SMTP_AUTH_FAILURE: generateNSError(12597), + NS_ERROR_SMTP_AUTH_GSSAPI: generateNSError(12598), + NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED: generateNSError(12599), + + NS_ERROR_ILLEGAL_LOCALPART: generateNSError(12601), + + NS_ERROR_CLIENTID: generateNSError(12610), + NS_ERROR_CLIENTID_PERMISSION: generateNSError(12611), + + sendLogger: console.createInstance({ + prefix: "mailnews.send", + maxLogLevel: "Warn", + maxLogLevelPref: "mailnews.send.loglevel", + }), + + smtpLogger: console.createInstance({ + prefix: "mailnews.smtp", + maxLogLevel: "Warn", + maxLogLevelPref: "mailnews.smtp.loglevel", + }), + + /** + * NS_IS_MSG_ERROR in msgCore.h. + * + * @param {nsresult} err - The nsresult value. + * @returns {boolean} + */ + isMsgError(err) { + return ( + (((err >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff) == + NS_ERROR_MODULE_MAILNEWS + ); + }, + + /** + * Convert html to text to form a multipart/alternative message. The output + * depends on preference. + * + * @param {string} input - The HTML text to convert. + * @param {boolean} formatFlowed - A flag to enable OutputFormatFlowed. + * @returns {string} + */ + convertToPlainText(input, formatFlowed) { + let wrapWidth = Services.prefs.getIntPref("mailnews.wraplength", 72); + if (wrapWidth == 0 || wrapWidth > 990) { + wrapWidth = 990; + } else if (wrapWidth < 10) { + wrapWidth = 10; + } + + let flags = + Ci.nsIDocumentEncoder.OutputPersistNBSP | + Ci.nsIDocumentEncoder.OutputFormatted | + Ci.nsIDocumentEncoder.OutputDisallowLineBreaking; + if (formatFlowed) { + flags |= Ci.nsIDocumentEncoder.OutputFormatFlowed; + } + + let parserUtils = Cc["@mozilla.org/parserutils;1"].getService( + Ci.nsIParserUtils + ); + return parserUtils.convertToPlainText(input, flags, wrapWidth); + }, + + /** + * Get the list of default custom headers. + * + * @param {nsIMsgIdentity} userIdentity - User identity. + * @returns {{headerName: string, headerValue: string}[]} + */ + getDefaultCustomHeaders(userIdentity) { + // mail.identity.<id#>.headers pref is a comma separated value of pref names + // containing headers to add headers are stored in + let headerAttributes = userIdentity + .getUnicharAttribute("headers") + .split(","); + let headers = []; + for (let attr of headerAttributes) { + // mail.identity.<id#>.header.<header name> grab all the headers + let attrValue = userIdentity.getUnicharAttribute(`header.${attr}`); + if (attrValue) { + let colonIndex = attrValue.indexOf(":"); + headers.push({ + headerName: attrValue.slice(0, colonIndex), + headerValue: attrValue.slice(colonIndex + 1).trim(), + }); + } + } + return headers; + }, + + /** + * Get the fcc value. + * + * @param {nsIMsgIdentity} userIdentity - The user identity. + * @param {nsIMsgCompFields} compFields - The compose fields. + * @param {string} originalMsgURI - The original message uri, can be null. + * @param {MSG_ComposeType} compType - The compose type. + * @returns {string} + */ + getFcc(userIdentity, compFields, originalMsgURI, compType) { + // Check if the default fcc has been overridden. + let fcc = ""; + let useDefaultFcc = true; + if (compFields.fcc) { + if (compFields.fcc.startsWith("nocopy://")) { + useDefaultFcc = false; + fcc = ""; + } else { + let folder = MailUtils.getExistingFolder(compFields.fcc); + if (folder) { + useDefaultFcc = false; + fcc = compFields.fcc; + } + } + } + + // If the identity pref "fcc" is set to false, then we will not do the default + // FCC operation but still allow the override. + if (!userIdentity.doFcc) { + return fcc; + } + + // We use default FCC setting if it's not set or was set to an invalid + // folder. + if (useDefaultFcc) { + // Only check whether the user wants the message in the original message + // folder if the msgcomptype is some kind of a reply. + if ( + originalMsgURI && + [ + Ci.nsIMsgCompType.Reply, + Ci.nsIMsgCompType.ReplyAll, + Ci.nsIMsgCompType.ReplyToGroup, + Ci.nsIMsgCompType.ReplyToSender, + Ci.nsIMsgCompType.ReplyToSenderAndGroup, + Ci.nsIMsgCompType.ReplyToList, + Ci.nsIMsgCompType.ReplyWithTemplate, + ].includes(compType) + ) { + let msgHdr; + try { + msgHdr = + MailServices.messageServiceFromURI( + originalMsgURI + ).messageURIToMsgHdr(originalMsgURI); + } catch (e) { + console.warn( + `messageServiceFromURI failed for ${originalMsgURI}\n${e.stack}` + ); + } + if (msgHdr) { + let folder = msgHdr.folder; + if ( + folder && + folder.canFileMessages && + folder.server && + folder.server.getCharValue("type") != "rss" && + userIdentity.fccReplyFollowsParent + ) { + fcc = folder.URI; + useDefaultFcc = false; + } + } + } + + if (useDefaultFcc) { + let uri = this.getMsgFolderURIFromPrefs( + userIdentity, + Ci.nsIMsgSend.nsMsgDeliverNow + ); + fcc = uri == "nocopy://" ? "" : uri; + } + } + + return fcc; + }, + + canSaveToFolder(folderUri) { + let folder = MailUtils.getOrCreateFolder(folderUri); + if (folder.server) { + return folder.server.canFileMessagesOnServer; + } + return false; + }, + + /** + * Get the To header value. When we don't have disclosed recipient but only + * Bcc, use the undisclosedRecipients entry from composeMsgs.properties as the + * To header value to prevent problem with some servers. + * + * @param {nsIMsgCompFields} compFields - The compose fields. + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @returns {string} + */ + getUndisclosedRecipients(compFields, deliverMode) { + // Newsgroups count as recipients. + let hasDisclosedRecipient = + compFields.to || compFields.cc || compFields.newsgroups; + // If we are saving the message as a draft, don't bother inserting the + // undisclosed recipients field. We'll take care of that when we really send + // the message. + if ( + hasDisclosedRecipient || + [ + Ci.nsIMsgSend.nsMsgDeliverBackground, + Ci.nsIMsgSend.nsMsgSaveAsDraft, + Ci.nsIMsgSend.nsMsgSaveAsTemplate, + ].includes(deliverMode) || + !Services.prefs.getBoolPref("mail.compose.add_undisclosed_recipients") + ) { + return ""; + } + let composeBundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + let undisclosedRecipients = composeBundle.GetStringFromName( + "undisclosedRecipients" + ); + let recipients = MailServices.headerParser.makeGroupObject( + undisclosedRecipients, + [] + ); + return recipients.toString(); + }, + + /** + * Get the Mail-Followup-To header value. + * See bug #204339 and http://cr.yp.to/proto/replyto.html for details + * + * @param {nsIMsgCompFields} compFields - The compose fields. + * @param {nsIMsgIdentity} userIdentity - The user identity. + * @returns {string} + */ + getMailFollowupToHeader(compFields, userIdentity) { + let mailLists = userIdentity.getUnicharAttribute( + "subscribed_mailing_lists" + ); + if (!mailLists || !(compFields.to || compFields.cc)) { + return ""; + } + let recipients = compFields.to; + if (recipients) { + if (compFields.cc) { + recipients += `,${compFields.cc}`; + } + } else { + recipients = compFields.cc; + } + let recipientsDedup = + MailServices.headerParser.removeDuplicateAddresses(recipients); + let recipientsWithoutMailList = + MailServices.headerParser.removeDuplicateAddresses( + recipientsDedup, + mailLists + ); + if (recipientsDedup != recipientsWithoutMailList) { + return recipients; + } + return ""; + }, + + /** + * Get the Mail-Reply-To header value. + * See bug #204339 and http://cr.yp.to/proto/replyto.html for details + * + * @param {nsIMsgCompFields} compFields - The compose fields. + * @param {nsIMsgIdentity} userIdentity - The user identity. + * @returns {string} + */ + getMailReplyToHeader(compFields, userIdentity) { + let mailLists = userIdentity.getUnicharAttribute( + "replyto_mangling_mailing_lists" + ); + if ( + !mailLists || + mailLists[0] == "*" || + !(compFields.to || compFields.cc) + ) { + return ""; + } + let recipients = compFields.to; + if (recipients) { + if (compFields.cc) { + recipients += `,${compFields.cc}`; + } + } else { + recipients = compFields.cc; + } + let recipientsDedup = + MailServices.headerParser.removeDuplicateAddresses(recipients); + let recipientsWithoutMailList = + MailServices.headerParser.removeDuplicateAddresses( + recipientsDedup, + mailLists + ); + if (recipientsDedup != recipientsWithoutMailList) { + return compFields.replyTo || compFields.from; + } + return ""; + }, + + /** + * Get the X-Mozilla-Draft-Info header value. + * + * @param {nsIMsgCompFields} compFields - The compose fields. + * @returns {string} + */ + getXMozillaDraftInfo(compFields) { + let getCompField = (property, key) => { + let value = compFields[property] ? 1 : 0; + return `${key}=${value}; `; + }; + let draftInfo = "internal/draft; "; + draftInfo += getCompField("attachVCard", "vcard"); + + let receiptValue = 0; + if (compFields.returnReceipt) { + // slight change compared to 4.x; we used to use receipt= to tell + // whether the draft/template has request for either MDN or DNS or both + // return receipt; since the DNS is out of the picture we now use the + // header type + 1 to tell whether user has requested the return receipt + receiptValue = compFields.receiptHeaderType + 1; + } + draftInfo += `receipt=${receiptValue}; `; + + draftInfo += getCompField("DSN", "DSN"); + draftInfo += "uuencode=0; "; + draftInfo += getCompField("attachmentReminder", "attachmentreminder"); + draftInfo += `deliveryformat=${compFields.deliveryFormat}`; + + return draftInfo; + }, + + /** + * Get the X-Mozilla-Cloud-Part header value. + * + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @param {nsIMsgAttachment} attachment - The cloud attachment. + * @returns {string} + */ + getXMozillaCloudPart(deliverMode, attachment) { + let value = ""; + if (attachment.sendViaCloud && attachment.contentLocation) { + value += `cloudFile; url=${attachment.contentLocation}`; + + if ( + (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft || + deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate) && + attachment.cloudFileAccountKey && + attachment.cloudPartHeaderData + ) { + value += `; provider=${attachment.cloudFileAccountKey}`; + value += `; ${this.rfc2231ParamFolding( + "data", + attachment.cloudPartHeaderData + )}`; + } + } + return value; + }, + + /** + * Get the X-Mozilla-Status header value. The header value will be used to set + * some nsMsgMessageFlags. Including the Read flag for message in a local + * folder. + * + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @returns {string} + */ + getXMozillaStatus(deliverMode) { + if ( + ![ + Ci.nsIMsgSend.nsMsgQueueForLater, + Ci.nsIMsgSend.nsMsgSaveAsDraft, + Ci.nsIMsgSend.nsMsgSaveAsTemplate, + Ci.nsIMsgSend.nsMsgDeliverNow, + Ci.nsIMsgSend.nsMsgSendUnsent, + Ci.nsIMsgSend.nsMsgDeliverBackground, + ].includes(deliverMode) + ) { + return ""; + } + let flags = 0; + if (deliverMode == Ci.nsIMsgSend.nsMsgQueueForLater) { + flags |= Ci.nsMsgMessageFlags.Queued; + } else if ( + deliverMode != Ci.nsIMsgSend.nsMsgSaveAsDraft && + deliverMode != Ci.nsIMsgSend.nsMsgDeliverBackground + ) { + flags |= Ci.nsMsgMessageFlags.Read; + } + return flags.toString(16).padStart(4, "0"); + }, + + /** + * Get the X-Mozilla-Status2 header value. The header value will be used to + * set some nsMsgMessageFlags. + * + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @returns {string} + */ + getXMozillaStatus2(deliverMode) { + if ( + ![ + Ci.nsIMsgSend.nsMsgQueueForLater, + Ci.nsIMsgSend.nsMsgSaveAsDraft, + Ci.nsIMsgSend.nsMsgSaveAsTemplate, + Ci.nsIMsgSend.nsMsgDeliverNow, + Ci.nsIMsgSend.nsMsgSendUnsent, + Ci.nsIMsgSend.nsMsgDeliverBackground, + ].includes(deliverMode) + ) { + return ""; + } + let flags = 0; + if (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate) { + flags |= Ci.nsMsgMessageFlags.Template; + } else if ( + deliverMode == Ci.nsIMsgSend.nsMsgDeliverNow || + deliverMode == Ci.nsIMsgSend.nsMsgSendUnsent + ) { + flags &= ~Ci.nsMsgMessageFlags.MDNReportNeeded; + flags |= Ci.nsMsgMessageFlags.MDNReportSent; + } + return flags.toString(16).padStart(8, "0"); + }, + + /** + * Get the Disposition-Notification-To header value. + * + * @param {nsIMsgCompFields} compFields - The compose fields. + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @returns {{dnt: string, rrt: string}} + */ + getDispositionNotificationTo(compFields, deliverMode) { + if ( + compFields.returnReceipt && + deliverMode != Ci.nsIMsgSend.nsMsgSaveAsDraft && + deliverMode != Ci.nsIMsgSend.nsMsgSaveAsTemplate && + compFields.receiptHeaderType != Ci.nsIMsgMdnGenerator.eRrtType + ) { + return compFields.from; + } + return ""; + }, + + /** + * Get the Return-Receipt-To header value. + * + * @param {nsIMsgCompFields} compFields - The compose fields. + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @returns {{dnt: string, rrt: string}} + */ + getReturnReceiptTo(compFields, deliverMode) { + if ( + compFields.returnReceipt && + deliverMode != Ci.nsIMsgSend.nsMsgSaveAsDraft && + deliverMode != Ci.nsIMsgSend.nsMsgSaveAsTemplate && + compFields.receiptHeaderType != Ci.nsIMsgMdnGenerator.eDntType + ) { + return compFields.from; + } + return ""; + }, + + /** + * Get the value of X-Priority header. + * + * @param {string} rawPriority - Raw X-Priority content. + * @returns {string} + */ + getXPriority(rawPriority) { + rawPriority = rawPriority.toLowerCase(); + let priorityValue = Ci.nsMsgPriority.Default; + let priorityValueString = "0"; + let priorityName = "None"; + if (rawPriority.startsWith("1") || rawPriority.startsWith("highest")) { + priorityValue = Ci.nsMsgPriority.highest; + priorityValueString = "1"; + priorityName = "Highest"; + } else if ( + rawPriority.startsWith("2") || + // "high" must be tested after "highest". + rawPriority.startsWith("high") || + rawPriority.startsWith("urgent") + ) { + priorityValue = Ci.nsMsgPriority.high; + priorityValueString = "2"; + priorityName = "High"; + } else if ( + rawPriority.startsWith("3") || + rawPriority.startsWith("normal") + ) { + priorityValue = Ci.nsMsgPriority.normal; + priorityValueString = "3"; + priorityName = "Normal"; + } else if ( + rawPriority.startsWith("5") || + rawPriority.startsWith("lowest") + ) { + priorityValue = Ci.nsMsgPriority.lowest; + priorityValueString = "5"; + priorityName = "Lowest"; + } else if ( + rawPriority.startsWith("4") || + // "low" must be tested after "lowest". + rawPriority.startsWith("low") + ) { + priorityValue = Ci.nsMsgPriority.low; + priorityValueString = "4"; + priorityName = "Low"; + } + if (priorityValue == Ci.nsMsgPriority.Default) { + return ""; + } + return `${priorityValueString} (${priorityName})`; + }, + + /** + * Get the References header value. + * + * @param {string} references - Raw References header content. + * @returns {string} + */ + getReferences(references) { + if (references.length <= 986) { + return ""; + } + // The References header should be kept under 998 characters: if it's too + // long, trim out the earliest references to make it smaller. + let newReferences = ""; + let firstRef = references.indexOf("<"); + let secondRef = references.indexOf("<", firstRef + 1); + if (secondRef > 0) { + newReferences = references.slice(0, secondRef); + let bracket = references.indexOf( + "<", + references.length + newReferences.length - 986 + ); + if (bracket > 0) { + newReferences += references.slice(bracket); + } + } + return newReferences; + }, + + /** + * Get the In-Reply-To header value. + * + * @param {string} references - Raw References header content. + * @returns {string} + */ + getInReplyTo(references) { + // The In-Reply-To header is the last entry in the references header... + let bracket = references.lastIndexOf("<"); + if (bracket >= 0) { + return references.slice(bracket); + } + return ""; + }, + + /** + * Get the value of Newsgroups and X-Mozilla-News-Host header. + * + * @param {nsMsgDeliverMode} deliverMode - Message deliver mode. + * @param {string} newsgroups - Raw newsgroups header content. + * @returns {{newsgroups: string, newshost: string}} + */ + getNewsgroups(deliverMode, newsgroups) { + let nntpService = Cc["@mozilla.org/messenger/nntpservice;1"].getService( + Ci.nsINntpService + ); + let newsgroupsHeaderVal = {}; + let newshostHeaderVal = {}; + nntpService.generateNewsHeaderValsForPosting( + newsgroups, + newsgroupsHeaderVal, + newshostHeaderVal + ); + + // If we are here, we are NOT going to send this now. (i.e. it is a Draft, + // Send Later file, etc...). Because of that, we need to store what the user + // typed in on the original composition window for use later when rebuilding + // the headers + if ( + deliverMode == Ci.nsIMsgSend.nsMsgDeliverNow || + deliverMode == Ci.nsIMsgSend.nsMsgSendUnsent + ) { + // This is going to be saved for later, that means we should just store + // what the user typed into the "Newsgroup" line in the + // HEADER_X_MOZILLA_NEWSHOST header for later use by "Send Unsent + // Messages", "Drafts" or "Templates" + newshostHeaderVal.value = ""; + } + return { + newsgroups: newsgroupsHeaderVal.value, + newshost: newshostHeaderVal.value, + }; + }, + + /** + * Get the Content-Location header value. + * + * @param {string} baseUrl - The base url of an HTML attachment. + * @returns {string} + */ + getContentLocation(baseUrl) { + let lowerBaseUrl = baseUrl.toLowerCase(); + if ( + !baseUrl.includes(":") || + lowerBaseUrl.startsWith("news:") || + lowerBaseUrl.startsWith("snews:") || + lowerBaseUrl.startsWith("imap:") || + lowerBaseUrl.startsWith("file:") || + lowerBaseUrl.startsWith("mailbox:") + ) { + return ""; + } + let transformMap = { + " ": "%20", + "\t": "%09", + "\n": "%0A", + "\r": "%0D", + }; + let value = ""; + for (let char of baseUrl) { + value += transformMap[char] || char; + } + return value; + }, + + /** + * Given a string, convert it to 'qtext' (quoted text) for RFC822 header + * purposes. + */ + makeFilenameQtext(srcText, stripCRLFs) { + let size = srcText.length; + let ret = ""; + for (let i = 0; i < size; i++) { + let char = srcText.charAt(i); + if ( + char == "\\" || + char == '"' || + (!stripCRLFs && + char == "\r" && + (srcText[i + 1] != "\n" || + (srcText[i + 1] == "\n" && i + 2 < size && srcText[i + 2] != " "))) + ) { + ret += "\\"; + } + + if ( + stripCRLFs && + char == "\r" && + srcText[i + 1] == "\n" && + i + 2 < size && + srcText[i + 2] == " " + ) { + i += 3; + } else { + ret += char; + } + } + return ret; + }, + + /** + * Encode parameter value according to RFC 2047. + * + * @param {string} value - The parameter value. + * @returns {string} + */ + rfc2047EncodeParam(value) { + let converter = Cc["@mozilla.org/messenger/mimeconverter;1"].getService( + Ci.nsIMimeConverter + ); + + let encoded = converter.encodeMimePartIIStr_UTF8( + value, + false, + 0, + Ci.nsIMimeConverter.MIME_ENCODED_WORD_SIZE + ); + + return this.makeFilenameQtext(encoded, false); + }, + + /** + * Encode parameter value according to RFC 2231. + * + * @param {string} paramName - The parameter name. + * @param {string} paramValue - The parameter value. + * @returns {string} + */ + rfc2231ParamFolding(paramName, paramValue) { + // this is to guarantee the folded line will never be greater + // than 78 = 75 + CRLFLWSP + const PR_MAX_FOLDING_LEN = 75; + + let needsEscape = false; + let encoder = new TextEncoder(); + let dupParamValue = jsmime.mimeutils.typedArrayToString( + encoder.encode(paramValue) + ); + + if (/[\x80-\xff]/.test(dupParamValue)) { + needsEscape = true; + dupParamValue = Services.io.escapeString( + dupParamValue, + Ci.nsINetUtil.ESCAPE_ALL + ); + } else { + dupParamValue = this.makeFilenameQtext(dupParamValue, true); + } + + let paramNameLen = paramName.length; + let paramValueLen = dupParamValue.length; + paramNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___" + let foldedParam = ""; + + if (paramValueLen + paramNameLen + "UTF-8".length < PR_MAX_FOLDING_LEN) { + foldedParam = paramName; + if (needsEscape) { + foldedParam += "*=UTF-8''"; + } else { + foldedParam += '="'; + } + foldedParam += dupParamValue; + if (!needsEscape) { + foldedParam += '"'; + } + } else { + let curLineLen = 0; + let counter = 0; + let start = 0; + let end = null; + + while (paramValueLen > 0) { + curLineLen = 0; + if (counter == 0) { + foldedParam = paramName; + } else { + foldedParam += `;\r\n ${paramName}`; + } + foldedParam += `*${counter}`; + curLineLen += `*${counter}`.length; + if (needsEscape) { + foldedParam += "*="; + if (counter == 0) { + foldedParam += "UTF-8''"; + curLineLen += "UTF-8".length; + } + } else { + foldedParam += '="'; + } + counter++; + curLineLen += paramNameLen; + if (paramValueLen <= PR_MAX_FOLDING_LEN - curLineLen) { + end = start + paramValueLen; + } else { + end = start + (PR_MAX_FOLDING_LEN - curLineLen); + } + + if (end && needsEscape) { + // Check to see if we are in the middle of escaped char. + // We use ESCAPE_ALL, so every third character is a '%'. + if (end - 1 > start && dupParamValue[end - 1] == "%") { + end -= 1; + } else if (end - 2 > start && dupParamValue[end - 2] == "%") { + end -= 2; + } + // *end is now a '%'. + // Check if the following UTF-8 octet is a continuation. + while (end - 3 > start && "89AB".includes(dupParamValue[end + 1])) { + end -= 3; + } + } + foldedParam += dupParamValue.slice(start, end); + if (!needsEscape) { + foldedParam += '"'; + } + paramValueLen -= end - start; + start = end; + } + } + + return foldedParam; + }, + + /** + * Get the target message folder to copy to. + * + * @param {nsIMsgIdentity} userIdentity - The user identity. + * @param {nsMsgDeliverMode} deliverMode - The deliver mode. + * @returns {string} + */ + getMsgFolderURIFromPrefs(userIdentity, deliverMode) { + if ( + deliverMode == Ci.nsIMsgSend.nsMsgQueueForLater || + deliverMode == Ci.nsIMsgSend.nsMsgDeliverBackground + ) { + let uri = Services.prefs.getCharPref("mail.default_sendlater_uri"); + // check if uri is unescaped, and if so, escape it and reset the pef. + if (!uri) { + return "anyfolder://"; + } else if (uri.includes(" ")) { + uri.replaceAll(" ", "%20"); + Services.prefs.setCharPref("mail.default_sendlater_uri", uri); + } + return uri; + } else if (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft) { + return userIdentity.draftFolder; + } else if (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate) { + return userIdentity.stationeryFolder; + } + if (userIdentity.doFcc) { + return userIdentity.fccFolder; + } + return ""; + }, + + /** + * Get the error string name of an exit code. The name will corresponds to an + * entry in composeMsgs.properties. + * + * @param {nsresult} exitCode - Exit code of sending mail process. + * @returns {string} + */ + getErrorStringName(exitCode) { + let codeNameMap = { + [this.NS_MSG_UNABLE_TO_OPEN_FILE]: "unableToOpenFile", + [this.NS_MSG_UNABLE_TO_OPEN_TMP_FILE]: "unableToOpenTmpFile", + [this.NS_MSG_UNABLE_TO_SAVE_TEMPLATE]: "unableToSaveTemplate", + [this.NS_MSG_UNABLE_TO_SAVE_DRAFT]: "unableToSaveDraft", + [this.NS_MSG_COULDNT_OPEN_FCC_FOLDER]: "couldntOpenFccFolder", + [this.NS_MSG_NO_SENDER]: "noSender", + [this.NS_MSG_NO_RECIPIENTS]: "noRecipients", + [this.NS_MSG_ERROR_WRITING_FILE]: "errorWritingFile", + [this.NS_ERROR_SENDING_FROM_COMMAND]: "errorSendingFromCommand", + [this.NS_ERROR_SENDING_DATA_COMMAND]: "errorSendingDataCommand", + [this.NS_ERROR_SENDING_MESSAGE]: "errorSendingMessage", + [this.NS_ERROR_POST_FAILED]: "postFailed", + [this.NS_ERROR_SMTP_SERVER_ERROR]: "smtpServerError", + [this.NS_MSG_UNABLE_TO_SEND_LATER]: "unableToSendLater", + [this.NS_ERROR_COMMUNICATIONS_ERROR]: "communicationsError", + [this.NS_ERROR_BUT_DONT_SHOW_ALERT]: "dontShowAlert", + [this.NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS]: + "couldNotGetUsersMailAddress2", + [this.NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY]: + "couldNotGetSendersIdentity", + [this.NS_ERROR_MIME_MPART_ATTACHMENT_ERROR]: "mimeMpartAttachmentError", + [this.NS_ERROR_NNTP_NO_CROSS_POSTING]: "nntpNoCrossPosting", + [this.NS_MSG_ERROR_READING_FILE]: "errorReadingFile", + [this.NS_MSG_ERROR_ATTACHING_FILE]: "errorAttachingFile", + [this.NS_ERROR_SMTP_GREETING]: "incorrectSmtpGreeting", + [this.NS_ERROR_SENDING_RCPT_COMMAND]: "errorSendingRcptCommand", + [this.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS]: "startTlsFailed", + [this.NS_ERROR_SMTP_PASSWORD_UNDEFINED]: "smtpPasswordUndefined", + [this.NS_ERROR_SMTP_SEND_NOT_ALLOWED]: "smtpSendNotAllowed", + [this.NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED]: "smtpTooManyRecipients", + [this.NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2]: "smtpPermSizeExceeded2", + [this.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER]: + "smtpSendFailedUnknownServer", + [this.NS_ERROR_SMTP_SEND_FAILED_REFUSED]: "smtpSendRequestRefused", + [this.NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED]: "smtpSendInterrupted", + [this.NS_ERROR_SMTP_SEND_FAILED_TIMEOUT]: "smtpSendTimeout", + [this.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON]: + "smtpSendFailedUnknownReason", + [this.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL]: + "smtpHintAuthEncryptToPlainNoSsl", + [this.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL]: + "smtpHintAuthEncryptToPlainSsl", + [this.NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT]: + "smtpHintAuthPlainToEncrypt", + [this.NS_ERROR_SMTP_AUTH_FAILURE]: "smtpAuthFailure", + [this.NS_ERROR_SMTP_AUTH_GSSAPI]: "smtpAuthGssapi", + [this.NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED]: "smtpAuthMechNotSupported", + [this.NS_ERROR_ILLEGAL_LOCALPART]: "errorIllegalLocalPart2", + [this.NS_ERROR_CLIENTID]: "smtpClientid", + [this.NS_ERROR_CLIENTID_PERMISSION]: "smtpClientidPermission", + }; + return codeNameMap[exitCode] || "sendFailed"; + }, + + /** + * Format the error message that will be shown to the user. + * + * @param {nsIMsgIdentity} userIdentity - User identity. + * @param {nsIStringBundle} composeBundle - Localized string bundle. + * @param {string} errorName - The error name derived from an exit code. + * @returns {string} + */ + formatStringWithSMTPHostName(userIdentity, composeBundle, errorName) { + let smtpServer = MailServices.smtp.getServerByIdentity(userIdentity); + let smtpHostname = smtpServer.hostname; + return composeBundle.formatStringFromName(errorName, [smtpHostname]); + }, + + /** + * Generate random alphanumeric string. + * + * @param {number} size - The length of generated string. + * @returns {string} + */ + randomString(size) { + let length = Math.round((size * 3) / 4); + return btoa( + String.fromCharCode( + ...[...Array(length)].map(() => Math.floor(Math.random() * 256)) + ) + ) + .slice(0, size) + .replaceAll(/[+/=]/g, "0"); + }, + + /** + * Generate a content id to be used by embedded images. + * + * @param {nsIMsgIdentity} userIdentity - User identity. + * @param {number} partNum - The number of embedded MimePart. + * @returns {string} + */ + makeContentId(userIdentity, partNum) { + let domain = userIdentity.email.split("@")[1]; + return `part${partNum}.${this.randomString(8)}.${this.randomString( + 8 + )}@${domain}`; + }, + + /** + * Pick a file name from the file URL. + * + * @param {string} url - The file URL. + * @returns {string} + */ + pickFileNameFromUrl(url) { + if (/^(news|snews|imap|mailbox):/i.test(url)) { + // No sensible file name in it, + return ""; + } + if (/^data:/i.test(url)) { + let matches = /filename=(.*);/.exec(url); + if (matches && matches[1]) { + return decodeURIComponent(matches[1]); + } + let mimeType = url.slice(5, url.indexOf(";")); + let extname = ""; + try { + extname = Cc["@mozilla.org/mime;1"] + .getService(Ci.nsIMIMEService) + .getPrimaryExtension(mimeType, null); + if (!extname) { + return ""; + } + } catch (e) { + return ""; + } + return `${this.randomString(16)}.${extname}`; + } + // Take the part after the last / or \. + let lastSlash = url.lastIndexOf("\\"); + if (lastSlash == -1) { + lastSlash = url.lastIndexOf("/"); + } + // Strip any search or anchor. + return url + .slice(lastSlash + 1) + .split("?")[0] + .split("#")[0]; + }, +}; diff --git a/comm/mailnews/compose/src/MimePart.jsm b/comm/mailnews/compose/src/MimePart.jsm new file mode 100644 index 0000000000..a22c0527af --- /dev/null +++ b/comm/mailnews/compose/src/MimePart.jsm @@ -0,0 +1,378 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["MimePart", "MimeMultiPart"]; + +let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); +let { MimeEncoder } = ChromeUtils.import("resource:///modules/MimeEncoder.jsm"); +let { MsgUtils } = ChromeUtils.import( + "resource:///modules/MimeMessageUtils.jsm" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MailStringUtils } = ChromeUtils.import( + "resource:///modules/MailStringUtils.jsm" +); +var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +/** + * A class to represent a RFC2045 message. MimePart can be nested, each MimePart + * can contain a list of MimePart. HTML and plain text are parts as well. Use + * class MimeMultiPart for multipart/*, that's why this class doesn't expose an + * addPart method + */ +class MimePart { + /** + * @param {string} contentType - Content type of the part, e.g. text/plain. + * @param {boolean} forceMsgEncoding - A flag used to determine Content-Transfer-Encoding. + * @param {boolean} isMainBody - The part is main part or an attachment part. + */ + constructor(contentType = "", forceMsgEncoding = false, isMainBody = false) { + this._charset = "UTF-8"; + this._contentType = contentType; + this._forceMsgEncoding = forceMsgEncoding; + this._isMainBody = isMainBody; + + this._headers = new Map(); + // 8-bit string to avoid converting back and forth. + this._bodyText = ""; + this._bodyAttachment = null; + this._contentDisposition = null; + this._contentId = null; + this._separator = ""; + this._parts = []; + } + + /** + * @type {BinaryString} text - The string to use as body. + */ + set bodyText(text) { + this._bodyText = text.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n"); + } + + /** + * @type {MimePart[]} - The child parts. + */ + get parts() { + return this._parts; + } + + /** + * @type {MimePart[]} parts - The child parts. + */ + set parts(parts) { + this._parts = parts; + } + + /** + * @type {string} - The separator string. + */ + get separator() { + return this._separator; + } + + /** + * Set a header. + * + * @param {string} name - The header name, e.g. "content-type". + * @param {string} content - The header content, e.g. "text/plain". + */ + setHeader(name, content) { + if (!content) { + return; + } + // There is no Content-Type encoder in jsmime yet. If content is not string, + // assume it's already a structured header. + if (name == "content-type" || typeof content != "string") { + // _headers will be passed to jsmime, which requires header content to be + // an array. + this._headers.set(name, [content]); + return; + } + try { + this._headers.set(name, [ + jsmime.headerparser.parseStructuredHeader(name, content), + ]); + } catch (e) { + this._headers.set(name, [content.trim()]); + } + } + + /** + * Delete a header. + * + * @param {string} name - The header name to delete, e.g. "content-type". + */ + deleteHeader(name) { + this._headers.delete(name); + } + + /** + * Set headers by an iterable. + * + * @param {Iterable.<string, string>} entries - The header entries. + */ + setHeaders(entries) { + for (let [name, content] of entries) { + this.setHeader(name, content); + } + } + + /** + * Set an attachment as body, with optional contentDisposition and contentId. + * + * @param {nsIMsgAttachment} attachment - The attachment to use as body. + * @param {string} [contentDisposition=attachment] - "attachment" or "inline". + * @param {string} [contentId] - The url of an embedded object is cid:contentId. + */ + setBodyAttachment( + attachment, + contentDisposition = "attachment", + contentId = null + ) { + this._bodyAttachment = attachment; + this._contentDisposition = contentDisposition; + this._contentId = contentId; + } + + /** + * Add a child part. + * + * @param {MimePart} part - A MimePart. + */ + addPart(part) { + this._parts.push(part); + } + + /** + * Add child parts. + * + * @param {MimePart[]} parts - An array of MimePart. + */ + addParts(parts) { + this._parts.push(...parts); + } + + /** + * Pick an encoding according to _bodyText or _bodyAttachment content. Set + * content-transfer-encoding header, then return the encoded value. + * + * @returns {BinaryString} + */ + async getEncodedBodyString() { + let bodyString = this._bodyText; + // If this is an attachment part, use the attachment content as bodyString. + if (this._bodyAttachment) { + try { + bodyString = await this._fetchAttachment(); + } catch (e) { + MsgUtils.sendLogger.error( + `Failed to fetch attachment; name=${this._bodyAttachment.name}, url=${this._bodyAttachment.url}, error=${e}` + ); + throw Components.Exception( + "Failed to fetch attachment", + MsgUtils.NS_MSG_ERROR_ATTACHING_FILE, + e.stack, + this._bodyAttachment + ); + } + } + if (bodyString) { + let encoder = new MimeEncoder( + this._charset, + this._contentType, + this._forceMsgEncoding, + this._isMainBody, + bodyString + ); + encoder.pickEncoding(); + this.setHeader("content-transfer-encoding", encoder.encoding); + bodyString = encoder.encode(); + } else if (this._isMainBody) { + this.setHeader("content-transfer-encoding", "7bit"); + } + return bodyString; + } + + /** + * Use jsmime to convert _headers to string. + * + * @returns {string} + */ + getHeaderString() { + return jsmime.headeremitter.emitStructuredHeaders(this._headers, { + useASCII: true, + sanitizeDate: Services.prefs.getBoolPref( + "mail.sanitize_date_header", + false + ), + }); + } + + /** + * Fetch the attached message file to get its content. + * + * @returns {string} + */ + async _fetchMsgAttachment() { + let msgService = MailServices.messageServiceFromURI( + this._bodyAttachment.url + ); + return new Promise((resolve, reject) => { + let streamListener = { + _data: "", + _stream: null, + onDataAvailable(request, inputStream, offset, count) { + if (!this._stream) { + this._stream = Cc[ + "@mozilla.org/scriptableinputstream;1" + ].createInstance(Ci.nsIScriptableInputStream); + this._stream.init(inputStream); + } + this._data += this._stream.read(count); + }, + onStartRequest() {}, + onStopRequest(request, status) { + if (Components.isSuccessCode(status)) { + resolve(this._data); + } else { + reject(`Fetch message attachment failed with status=${status}`); + } + }, + QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]), + }; + + msgService.streamMessage( + this._bodyAttachment.url, + streamListener, + null, // msgWindow + null, // urlListener + false, // convertData + "" // additionalHeader + ); + }); + } + + /** + * Fetch the attachment file to get its content type and content. + * + * Previously, we used the Fetch API to download all attachments, but Fetch + * doesn't support url with embedded credentials (imap://name@server). As a + * result, it's unreliable when having two mail accounts on the same IMAP + * server. + * + * @returns {string} + */ + async _fetchAttachment() { + let url = this._bodyAttachment.url; + MsgUtils.sendLogger.debug(`Fetching ${url}`); + + let content = ""; + if (/^[^:]+-message:/i.test(url)) { + content = await this._fetchMsgAttachment(); + if (!content) { + // Message content is empty usually means it's (re)moved. + throw new Error("Message is gone"); + } + this._contentType = "message/rfc822"; + } else { + let channel = Services.io.newChannelFromURI( + Services.io.newURI(url), + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER + ); + content = await new Promise((resolve, reject) => + NetUtil.asyncFetch(channel, (stream, status, request) => { + if (!Components.isSuccessCode(status)) { + reject(`asyncFetch failed with status=${status}`); + return; + } + let data = ""; + try { + data = NetUtil.readInputStreamToString(stream, stream.available()); + } catch (e) { + // stream.available() throws if the file is empty. + } + resolve(data); + }) + ); + this._contentType = + this._bodyAttachment.contentType || channel.contentType; + } + + let parmFolding = Services.prefs.getIntPref( + "mail.strictly_mime.parm_folding", + 2 + ); + // File name can contain non-ASCII chars, encode according to RFC 2231. + let encodedName, encodedFileName; + if (this._bodyAttachment.name) { + encodedName = MsgUtils.rfc2047EncodeParam(this._bodyAttachment.name); + encodedFileName = MsgUtils.rfc2231ParamFolding( + "filename", + this._bodyAttachment.name + ); + } + this._charset = this._contentType.startsWith("text/") + ? MailStringUtils.detectCharset(content) + : ""; + + let contentTypeParams = ""; + if (this._charset) { + contentTypeParams += `; charset=${this._charset}`; + } + if (encodedName && parmFolding != 2) { + contentTypeParams += `; name="${encodedName}"`; + } + this.setHeader("content-type", `${this._contentType}${contentTypeParams}`); + if (encodedFileName) { + this.setHeader( + "content-disposition", + `${this._contentDisposition}; ${encodedFileName}` + ); + } + if (this._contentId) { + this.setHeader("content-id", `<${this._contentId}>`); + } + if (this._contentType == "text/html") { + let contentLocation = MsgUtils.getContentLocation( + this._bodyAttachment.url + ); + this.setHeader("content-location", contentLocation); + } else if (this._contentType == "application/pgp-keys") { + this.setHeader("content-description", "OpenPGP public key"); + } + + return content; + } +} + +/** + * A class to represent a multipart/* part inside a RFC2045 message. + */ +class MimeMultiPart extends MimePart { + /** + * @param {string} subtype - The multipart subtype, e.g. "alternative" or "mixed". + */ + constructor(subtype) { + super(); + this.subtype = subtype; + this._separator = this._makePartSeparator(); + this.setHeader( + "content-type", + `multipart/${subtype}; boundary="${this._separator}"` + ); + } + + /** + * Use 12 hyphen characters and 24 random base64 characters as separator. + */ + _makePartSeparator() { + return "------------" + MsgUtils.randomString(24); + } +} diff --git a/comm/mailnews/compose/src/SMTPProtocolHandler.jsm b/comm/mailnews/compose/src/SMTPProtocolHandler.jsm new file mode 100644 index 0000000000..d139e15784 --- /dev/null +++ b/comm/mailnews/compose/src/SMTPProtocolHandler.jsm @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var EXPORTED_SYMBOLS = ["SMTPProtocolHandler", "SMTPSProtocolHandler"]; + +/** + * @implements {nsIProtocolHandler} + */ +class SMTPProtocolHandler { + QueryInterface = ChromeUtils.generateQI(["nsIProtocolHandler"]); + + scheme = "smtp"; + + newChannel(aURI, aLoadInfo) { + throw Components.Exception( + `${this.constructor.name}.newChannel not implemented`, + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + allowPort(port, scheme) { + return port == Ci.nsISmtpUrl.DEFAULT_SMTP_PORT; + } +} +SMTPProtocolHandler.prototype.classID = Components.ID( + "{b14c2b67-8680-4c11-8d63-9403c7d4f757}" +); + +class SMTPSProtocolHandler extends SMTPProtocolHandler { + scheme = "smtps"; + + allowPort(port, scheme) { + return port == Ci.nsISmtpUrl.DEFAULT_SMTPS_PORT; + } +} +SMTPSProtocolHandler.prototype.classID = Components.ID( + "{057d0997-9e3a-411e-b4ee-2602f53fe05f}" +); diff --git a/comm/mailnews/compose/src/SmtpClient.jsm b/comm/mailnews/compose/src/SmtpClient.jsm new file mode 100644 index 0000000000..2eb13985e3 --- /dev/null +++ b/comm/mailnews/compose/src/SmtpClient.jsm @@ -0,0 +1,1344 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Based on https://github.com/emailjs/emailjs-smtp-client + * + * Copyright (c) 2013 Andris Reinman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +const EXPORTED_SYMBOLS = ["SmtpClient"]; + +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +var { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); +var { MailStringUtils } = ChromeUtils.import( + "resource:///modules/MailStringUtils.jsm" +); +var { SmtpAuthenticator } = ChromeUtils.import( + "resource:///modules/MailAuthenticator.jsm" +); +var { MsgUtils } = ChromeUtils.import( + "resource:///modules/MimeMessageUtils.jsm" +); + +class SmtpClient { + /** + * The number of RCPT TO commands sent on the connection by this client. + * This can count-up over multiple messages. + */ + rcptCount = 0; + + /** + * Set true only when doing a retry. + */ + isRetry = false; + + /** + * Creates a connection object to a SMTP server and allows to send mail through it. + * Call `connect` method to inititate the actual connection, the constructor only + * defines the properties but does not actually connect. + * + * @class + * + * @param {nsISmtpServer} server - The associated nsISmtpServer instance. + */ + constructor(server) { + this.options = { + alwaysSTARTTLS: + server.socketType == Ci.nsMsgSocketType.trySTARTTLS || + server.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS, + requireTLS: server.socketType == Ci.nsMsgSocketType.SSL, + }; + + this.socket = false; // Downstream TCP socket to the SMTP server, created with TCPSocket + this.waitDrain = false; // Keeps track if the downstream socket is currently full and a drain event should be waited for or not + + // Private properties + + this._server = server; + this._authenticator = new SmtpAuthenticator(server); + this._authenticating = false; + // A list of auth methods detected from the EHLO response. + this._supportedAuthMethods = []; + // A list of auth methods that worth a try. + this._possibleAuthMethods = []; + // Auth method set by user preference. + this._preferredAuthMethods = + { + [Ci.nsMsgAuthMethod.passwordCleartext]: ["PLAIN", "LOGIN"], + [Ci.nsMsgAuthMethod.passwordEncrypted]: ["CRAM-MD5"], + [Ci.nsMsgAuthMethod.GSSAPI]: ["GSSAPI"], + [Ci.nsMsgAuthMethod.NTLM]: ["NTLM"], + [Ci.nsMsgAuthMethod.OAuth2]: ["XOAUTH2"], + [Ci.nsMsgAuthMethod.secure]: ["CRAM-MD5", "XOAUTH2"], + }[server.authMethod] || []; + // The next auth method to try if the current failed. + this._nextAuthMethod = null; + + // A list of capabilities detected from the EHLO response. + this._capabilities = []; + + this._dataMode = false; // If true, accepts data from the upstream to be passed directly to the downstream socket. Used after the DATA command + this._lastDataBytes = ""; // Keep track of the last bytes to see how the terminating dot should be placed + this._envelope = null; // Envelope object for tracking who is sending mail to whom + this._currentAction = null; // Stores the function that should be run after a response has been received from the server + + this._parseBlock = { data: [], statusCode: null }; + this._parseRemainder = ""; // If the complete line is not received yet, contains the beginning of it + + this.logger = MsgUtils.smtpLogger; + + // Event placeholders + this.onerror = (e, failedSecInfo) => {}; // Will be run when an error occurs. The `onclose` event will fire subsequently. + this.ondrain = () => {}; // More data can be buffered in the socket. + this.onclose = () => {}; // The connection to the server has been closed + this.onidle = () => {}; // The connection is established and idle, you can send mail now + this.onready = failedRecipients => {}; // Waiting for mail body, lists addresses that were not accepted as recipients + this.ondone = success => {}; // The mail has been sent. Wait for `onidle` next. Indicates if the message was queued by the server. + // Callback when this client is ready to be reused. + this.onFree = () => {}; + } + + /** + * Initiate a connection to the server + */ + connect() { + if (this.socket?.readyState == "open") { + this.logger.debug("Reusing a connection"); + this.onidle(); + } else { + let hostname = this._server.hostname.toLowerCase(); + let port = this._server.port || (this.options.requireTLS ? 465 : 587); + this.logger.debug(`Connecting to smtp://${hostname}:${port}`); + this._secureTransport = this.options.requireTLS; + this.socket = new TCPSocket(hostname, port, { + binaryType: "arraybuffer", + useSecureTransport: this._secureTransport, + }); + + this.socket.onerror = this._onError; + this.socket.onopen = this._onOpen; + } + this._freed = false; + } + + /** + * Sends QUIT + */ + quit() { + this._authenticating = false; + this._freed = true; + this._sendCommand("QUIT"); + this._currentAction = this.close; + } + + /** + * Closes the connection to the server + * + * @param {boolean} [immediately] - Close the socket without waiting for + * unsent data. + */ + close(immediately) { + if (this.socket && this.socket.readyState === "open") { + 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 { + this.logger.debug(`Connection to ${this._server.hostname} closed`); + this._free(); + } + } + + // Mail related methods + + /** + * Initiates a new message by submitting envelope data, starting with + * `MAIL FROM:` command. Use after `onidle` event + * + * @param {object} envelope - The envelope object. + * @param {string} envelope.from - The from address. + * @param {string[]} envelope.to - The to addresses. + * @param {number} envelope.size - The file size. + * @param {boolean} envelope.requestDSN - Whether to request Delivery Status Notifications. + * @param {boolean} envelope.messageId - The message id. + */ + useEnvelope(envelope) { + this._envelope = envelope || {}; + this._envelope.from = [].concat( + this._envelope.from || "anonymous@" + this._getHelloArgument() + )[0]; + + if (!this._capabilities.includes("SMTPUTF8")) { + // If server doesn't support SMTPUTF8, check if addresses contain invalid + // characters. + + let recipients = this._envelope.to; + this._envelope.to = []; + + for (let recipient of recipients) { + let lastAt = null; + let firstInvalid = null; + for (let i = 0; i < recipient.length; i++) { + let ch = recipient[i]; + if (ch == "@") { + lastAt = i; + } else if ((ch < " " || ch > "~") && ch != "\t") { + firstInvalid = i; + break; + } + } + if (!recipient || firstInvalid != null) { + if (!lastAt) { + // Invalid char found in the localpart, throw error until we implement RFC 6532. + this._onNsError(MsgUtils.NS_ERROR_ILLEGAL_LOCALPART, recipient); + return; + } + // Invalid char found in the domainpart, convert it to ACE. + let idnService = Cc["@mozilla.org/network/idn-service;1"].getService( + Ci.nsIIDNService + ); + let domain = idnService.convertUTF8toACE(recipient.slice(lastAt + 1)); + recipient = `${recipient.slice(0, lastAt)}@${domain}`; + } + this._envelope.to.push(recipient); + } + } + + // clone the recipients array for latter manipulation + this._envelope.rcptQueue = [...new Set(this._envelope.to)]; + this._envelope.rcptFailed = []; + this._envelope.responseQueue = []; + + if (!this._envelope.rcptQueue.length) { + this._onNsError(MsgUtils.NS_MSG_NO_RECIPIENTS); + return; + } + + this._currentAction = this._actionMAIL; + let cmd = `MAIL FROM:<${this._envelope.from}>`; + if ( + this._capabilities.includes("8BITMIME") && + !Services.prefs.getBoolPref("mail.strictly_mime", false) + ) { + cmd += " BODY=8BITMIME"; + } + if (this._capabilities.includes("SMTPUTF8")) { + // Should not send SMTPUTF8 if all ascii, see RFC6531. + // eslint-disable-next-line no-control-regex + let ascii = /^[\x00-\x7F]+$/; + if ([envelope.from, ...envelope.to].some(x => !ascii.test(x))) { + cmd += " SMTPUTF8"; + } + } + if (this._capabilities.includes("SIZE")) { + cmd += ` SIZE=${this._envelope.size}`; + } + if (this._capabilities.includes("DSN") && this._envelope.requestDSN) { + let ret = Services.prefs.getBoolPref("mail.dsn.ret_full_on") + ? "FULL" + : "HDRS"; + cmd += ` RET=${ret} ENVID=${envelope.messageId}`; + } + this._sendCommand(cmd); + } + + /** + * Send ASCII data to the server. Works only in data mode (after `onready` event), ignored + * otherwise + * + * @param {string} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server + * @returns {boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more + */ + send(chunk) { + // works only in data mode + if (!this._dataMode) { + // this line should never be reached but if it does, + // act like everything's normal. + return true; + } + + // TODO: if the chunk is an arraybuffer, use a separate function to send the data + return this._sendString(chunk); + } + + /** + * Indicates that a data stream for the socket is ended. Works only in data + * mode (after `onready` event), ignored otherwise. Use it when you are done + * with sending the mail. This method does not close the socket. Once the mail + * has been queued by the server, `ondone` and `onidle` are emitted. + * + * @param {Buffer} [chunk] Chunk of data to be sent to the server + */ + end(chunk) { + // works only in data mode + if (!this._dataMode) { + // this line should never be reached but if it does, + // act like everything's normal. + return true; + } + + if (chunk && chunk.length) { + this.send(chunk); + } + + // redirect output from the server to _actionStream + this._currentAction = this._actionStream; + + // indicate that the stream has ended by sending a single dot on its own line + // if the client already closed the data with \r\n no need to do it again + if (this._lastDataBytes === "\r\n") { + this.waitDrain = this._send(new Uint8Array([0x2e, 0x0d, 0x0a]).buffer); // .\r\n + } else if (this._lastDataBytes.substr(-1) === "\r") { + this.waitDrain = this._send( + new Uint8Array([0x0a, 0x2e, 0x0d, 0x0a]).buffer + ); // \n.\r\n + } else { + this.waitDrain = this._send( + new Uint8Array([0x0d, 0x0a, 0x2e, 0x0d, 0x0a]).buffer + ); // \r\n.\r\n + } + + // End data mode. + this._dataMode = false; + + return this.waitDrain; + } + + // PRIVATE METHODS + + /** + * Queue some data from the server for parsing. + * + * @param {string} chunk Chunk of data received from the server + */ + _parse(chunk) { + // Lines should always end with <CR><LF> but you never know, might be only <LF> as well + var lines = (this._parseRemainder + (chunk || "")).split(/\r?\n/); + this._parseRemainder = lines.pop(); // not sure if the line has completely arrived yet + + for (let i = 0, len = lines.length; i < len; i++) { + if (!lines[i].trim()) { + // nothing to check, empty line + continue; + } + + // possible input strings for the regex: + // 250-MULTILINE REPLY + // 250 LAST LINE OF REPLY + // 250 1.2.3 MESSAGE + + const match = lines[i].match( + /^(\d{3})([- ])(?:(\d+\.\d+\.\d+)(?: ))?(.*)/ + ); + + if (match) { + this._parseBlock.data.push(match[4]); + + if (match[2] === "-") { + // this is a multiline reply + this._parseBlock.statusCode = + this._parseBlock.statusCode || Number(match[1]); + } else { + const statusCode = Number(match[1]) || 0; + const response = { + statusCode, + data: this._parseBlock.data.join("\n"), + // Success means can move to the next step. Though 3xx is not + // failure, we don't consider it success here. + success: statusCode >= 200 && statusCode < 300, + }; + + this._onCommand(response); + this._parseBlock = { + data: [], + statusCode: null, + }; + } + } else { + this._onCommand({ + success: false, + statusCode: this._parseBlock.statusCode || null, + data: [lines[i]].join("\n"), + }); + this._parseBlock = { + data: [], + statusCode: null, + }; + } + } + } + + // EVENT HANDLERS FOR THE SOCKET + + /** + * Connection listener that is run when the connection to the server is opened. + * Sets up different event handlers for the opened socket + */ + _onOpen = () => { + this.logger.debug("Connected"); + + this.socket.ondata = this._onData; + this.socket.onclose = this._onClose; + this.socket.ondrain = this._onDrain; + + this._currentAction = this._actionGreeting; + this.socket.transport.setTimeout( + Ci.nsISocketTransport.TIMEOUT_READ_WRITE, + Services.prefs.getIntPref("mailnews.tcptimeout") + ); + }; + + /** + * Data listener for chunks of data emitted by the server + * + * @param {Event} evt - Event object. See `evt.data` for the chunk received + */ + _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 + // called in time. test_smtpPasswordFailure3 is such a case, the server + // rejects AUTH PLAIN then closes the connection, the client then sends AUTH + // LOGIN. This line guarantees onclose is called before sending AUTH LOGIN. + await new Promise(resolve => setTimeout(resolve)); + this._parse(stringPayload); + }; + + /** + * More data can be buffered in the socket, `waitDrain` is reset to false + */ + _onDrain = () => { + this.waitDrain = false; + this.ondrain(); + }; + + /** + * Error handler. Emits an nsresult value. + * + * @param {Error|TCPSocketErrorEvent} event - An Error or TCPSocketErrorEvent object. + */ + _onError = async event => { + this.logger.error(`${event.name}: a ${event.message} error occurred`); + if (this._freed) { + // Ignore socket errors if already freed. + return; + } + + this._free(); + this.quit(); + + let nsError = Cr.NS_ERROR_FAILURE; + let secInfo = null; + if (TCPSocketErrorEvent.isInstance(event)) { + nsError = event.errorCode; + secInfo = + await event.target.transport?.tlsSocketControl?.asyncGetSecurityInfo(); + if (secInfo) { + this.logger.error(`SecurityError info: ${secInfo.errorCodeString}`); + if (secInfo.failedCertChain.length) { + let chain = secInfo.failedCertChain.map(c => { + return c.commonName + "; serial# " + c.serialNumber; + }); + this.logger.error(`SecurityError cert chain: ${chain.join(" <- ")}`); + } + this._server.closeCachedConnections(); + } + } + + // Use nsresult to integrate with other parts of sending process, e.g. + // MessageSend.jsm will show an error message depending on the nsresult. + this.onerror(nsError, "", secInfo); + }; + + /** + * Error handler. Emits an nsresult value. + * + * @param {nsresult} nsError - A nsresult. + * @param {string} errorParam - Param to form the error message. + * @param {string} [extra] - Some messages take two arguments to format. + * @param {number} [statusCode] - Only needed when checking need to retry. + */ + _onNsError(nsError, errorParam, extra, statusCode) { + // First check if handling an error response that might need a retry. + if ([this._actionMAIL, this._actionRCPT].includes(this._currentAction)) { + if (statusCode >= 400 && statusCode < 500) { + // Possibly too many recipients, too many messages, to much data + // or too much time has elapsed on this connection. + if (!this.isRetry) { + // Now seeing error 4xx meaning that the current message can't be + // accepted. We close the connection and try again to send on a new + // connection using this same client instance. If the retry also + // fails on the new connection, we give up and report the error. + this.logger.debug("Retry send on new connection."); + this.quit(); + this.isRetry = true; // flag that we will retry on new connection + this.close(true); + this.connect(); + return; // return without reporting the error yet + } + } + } + + let errorName = MsgUtils.getErrorStringName(nsError); + let errorMessage = ""; + if ( + [ + MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, + MsgUtils.NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED, + MsgUtils.NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2, + MsgUtils.NS_ERROR_SENDING_FROM_COMMAND, + MsgUtils.NS_ERROR_SENDING_RCPT_COMMAND, + MsgUtils.NS_ERROR_SENDING_DATA_COMMAND, + MsgUtils.NS_ERROR_SENDING_MESSAGE, + MsgUtils.NS_ERROR_ILLEGAL_LOCALPART, + ].includes(nsError) + ) { + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties" + ); + if (nsError == MsgUtils.NS_ERROR_ILLEGAL_LOCALPART) { + errorMessage = bundle + .GetStringFromName(errorName) + .replace("%s", errorParam); + } else { + errorMessage = bundle.formatStringFromName(errorName, [ + errorParam, + extra, + ]); + } + } + this.onerror(nsError, errorMessage); + this.close(); + } + + /** + * Indicates that the socket has been closed + */ + _onClose = () => { + this.logger.debug("Socket closed."); + this._free(); + this.rcptCount = 0; + if (this._authenticating) { + // In some cases, socket is closed for invalid username/password. + this._onAuthFailed({ data: "Socket closed." }); + } + }; + + /** + * This is not a socket data handler but the handler for data emitted by the parser, + * so this data is safe to use as it is always complete (server might send partial chunks) + * + * @param {object} command - Parsed data. + */ + _onCommand(command) { + if (command.statusCode < 200 || command.statusCode >= 400) { + // @see https://datatracker.ietf.org/doc/html/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( + `Command failed: ${command.statusCode} ${command.data}; currentAction=${this._currentAction?.name}` + ); + } + if (typeof this._currentAction === "function") { + this._currentAction(command); + } + } + + /** + * This client has finished the current process and ready to be reused. + */ + _free() { + if (!this._freed) { + this._freed = true; + this.onFree(); + } + } + + /** + * Sends a string to the socket. + * + * @param {string} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server + * @returns {boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more + */ + _sendString(chunk) { + // escape dots + if (!this.options.disableEscaping) { + chunk = chunk.replace(/\n\./g, "\n.."); + if ( + (this._lastDataBytes.substr(-1) === "\n" || !this._lastDataBytes) && + chunk.charAt(0) === "." + ) { + chunk = "." + chunk; + } + } + + // Keeping eye on the last bytes sent, to see if there is a <CR><LF> sequence + // at the end which is needed to end the data stream + if (chunk.length > 2) { + this._lastDataBytes = chunk.substr(-2); + } else if (chunk.length === 1) { + this._lastDataBytes = this._lastDataBytes.substr(-1) + chunk; + } + + this.logger.debug("Sending " + chunk.length + " bytes of payload"); + + // pass the chunk to the socket + this.waitDrain = this._send( + MailStringUtils.byteStringToUint8Array(chunk).buffer + ); + return this.waitDrain; + } + + /** + * Send a string command to the server, also append CRLF if needed. + * + * @param {string} str - String to be sent to the server. + * @param {boolean} [suppressLogging=false] - If true and not in dev mode, + * do not log the str. For non-release builds output won't be suppressed, + * so that debugging auth problems is easier. + */ + _sendCommand(str, suppressLogging = false) { + if (this.socket.readyState !== "open") { + if (str != "QUIT") { + this.logger.warn( + `Failed to send "${str}" because socket state is ${this.socket.readyState}` + ); + } + return; + } + // "C: " is used to denote that this is data from the Client. + if (suppressLogging && AppConstants.MOZ_UPDATE_CHANNEL != "default") { + this.logger.debug( + "C: Logging suppressed (it probably contained auth information)" + ); + } else { + this.logger.debug(`C: ${str}`); + } + this.waitDrain = this._send( + new TextEncoder().encode(str + (str.substr(-2) !== "\r\n" ? "\r\n" : "")) + .buffer + ); + } + + _send(buffer) { + return this.socket.send(buffer); + } + + /** + * Intitiate authentication sequence if needed + * + * @param {boolean} forceNewPassword - Discard cached password. + */ + async _authenticateUser(forceNewPassword) { + if ( + this._preferredAuthMethods.length == 0 || + this._supportedAuthMethods.length == 0 + ) { + // no need to authenticate, at least no data given + this._currentAction = this._actionIdle; + this.onidle(); // ready to take orders + return; + } + + if (!this._nextAuthMethod) { + this._onAuthFailed({ data: "No available auth method." }); + return; + } + + this._authenticating = true; + + this._currentAuthMethod = this._nextAuthMethod; + this._nextAuthMethod = + this._possibleAuthMethods[ + this._possibleAuthMethods.indexOf(this._currentAuthMethod) + 1 + ]; + this.logger.debug(`Current auth method: ${this._currentAuthMethod}`); + + switch (this._currentAuthMethod) { + case "LOGIN": + // LOGIN is a 3 step authentication process + // C: AUTH LOGIN + // C: BASE64(USER) + // C: BASE64(PASS) + this.logger.debug("Authentication via AUTH LOGIN"); + this._currentAction = this._actionAUTH_LOGIN_USER; + this._sendCommand("AUTH LOGIN"); + return; + case "PLAIN": + // AUTH PLAIN is a 1 step authentication process + // C: AUTH PLAIN BASE64(\0 USER \0 PASS) + this.logger.debug("Authentication via AUTH PLAIN"); + this._currentAction = this._actionAUTHComplete; + this._sendCommand( + "AUTH PLAIN " + this._authenticator.getPlainToken(), + true + ); + return; + case "CRAM-MD5": + this.logger.debug("Authentication via AUTH CRAM-MD5"); + this._currentAction = this._actionAUTH_CRAM; + this._sendCommand("AUTH CRAM-MD5"); + return; + case "XOAUTH2": + // See https://developers.google.com/gmail/xoauth2_protocol#smtp_protocol_exchange + this.logger.debug("Authentication via AUTH XOAUTH2"); + this._currentAction = this._actionAUTH_XOAUTH2; + let oauthToken = await this._authenticator.getOAuthToken(); + this._sendCommand("AUTH XOAUTH2 " + oauthToken, true); + return; + case "GSSAPI": { + this.logger.debug("Authentication via AUTH GSSAPI"); + this._currentAction = this._actionAUTH_GSSAPI; + this._authenticator.initGssapiAuth("smtp"); + let token; + try { + token = this._authenticator.getNextGssapiToken(""); + } catch (e) { + this.logger.error(e); + this._actionAUTHComplete({ success: false, data: "AUTH GSSAPI" }); + return; + } + this._sendCommand(`AUTH GSSAPI ${token}`, true); + return; + } + case "NTLM": { + this.logger.debug("Authentication via AUTH NTLM"); + this._currentAction = this._actionAUTH_NTLM; + this._authenticator.initNtlmAuth("smtp"); + let token; + try { + token = this._authenticator.getNextNtlmToken(""); + } catch (e) { + this.logger.error(e); + this._actionAUTHComplete({ success: false, data: "AUTH NTLM" }); + return; + } + this._sendCommand(`AUTH NTLM ${token}`, true); + return; + } + } + + this._onAuthFailed({ + data: `Unknown authentication method ${this._currentAuthMethod}`, + }); + } + + _onAuthFailed(command) { + this.logger.error(`Authentication failed: ${command.data}`); + if (!this._freed) { + if (this._nextAuthMethod) { + // Try the next auth method. + this._authenticateUser(); + return; + } else if (!this._currentAuthMethod) { + // No auth method was even tried. + let err; + if ( + this._server.authMethod == Ci.nsMsgAuthMethod.passwordEncrypted && + (this._supportedAuthMethods.includes("PLAIN") || + this._supportedAuthMethods.includes("LOGIN")) + ) { + // Pref has encrypted password, server claims to support plaintext + // password. + err = [ + Ci.nsMsgSocketType.alwaysSTARTTLS, + Ci.nsMsgSocketType.SSL, + ].includes(this._server.socketType) + ? MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL + : MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL; + } else if ( + this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext && + this._supportedAuthMethods.includes("CRAM-MD5") + ) { + // Pref has plaintext password, server claims to support encrypted + // password. + err = MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT; + } else { + err = MsgUtils.NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED; + } + this._onNsError(err); + return; + } + } + + // Ask user what to do. + let action = this._authenticator.promptAuthFailed(); + if (action == 1) { + // Cancel button pressed. + this.logger.error(`Authentication failed: ${command.data}`); + this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE); + return; + } else if (action == 2) { + // 'New password' button pressed. Forget cached password, new password + // will be asked. + this._authenticator.forgetPassword(); + } + + if (this._freed) { + // If connection is lost, reconnect. + this.connect(); + return; + } + + // Reset _nextAuthMethod to start again. + this._nextAuthMethod = this._possibleAuthMethods[0]; + if (action == 2 || action == 0) { + // action = 0 means retry button pressed. + this._authenticateUser(); + } + } + + _getHelloArgument() { + let helloArgument = this._server.helloArgument; + if (helloArgument) { + return helloArgument; + } + + try { + // The address format follows rfc5321#section-4.1.3. + let netAddr = this.socket?.transport.getScriptableSelfAddr(); + let address = netAddr.address; + if (netAddr.family === Ci.nsINetAddr.FAMILY_INET6) { + return `[IPV6:${address}]`; + } + return `[${address}]`; + } catch (e) {} + + return "[127.0.0.1]"; + } + + // ACTIONS FOR RESPONSES FROM THE SMTP SERVER + + /** + * Initial response from the server, must have a status 220 + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionGreeting(command) { + if (command.statusCode !== 220) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + return; + } + + if (this.options.lmtp) { + this._currentAction = this._actionLHLO; + this._sendCommand("LHLO " + this._getHelloArgument()); + } else { + this._currentAction = this._actionEHLO; + this._sendCommand("EHLO " + this._getHelloArgument()); + } + } + + /** + * Response to LHLO + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionLHLO(command) { + if (!command.success) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + return; + } + + // Process as EHLO response + this._actionEHLO(command); + } + + /** + * Response to EHLO. If the response is an error, try HELO instead + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionEHLO(command) { + if ([500, 502].includes(command.statusCode)) { + // EHLO is not implemented by the server. + if (this.options.alwaysSTARTTLS) { + // If alwaysSTARTTLS is set by the user, EHLO is required to advertise it. + this._onNsError(MsgUtils.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS); + return; + } + + // Try HELO instead + this.logger.warn( + "EHLO not successful, trying HELO " + this._getHelloArgument() + ); + this._currentAction = this._actionHELO; + this._sendCommand("HELO " + this._getHelloArgument()); + return; + } else if (!command.success) { + // 501 Syntax error or some other error. + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + return; + } + + this._supportedAuthMethods = []; + + let lines = command.data.toUpperCase().split("\n"); + // Skip the first greeting line. + for (let line of lines.slice(1)) { + if (line.startsWith("AUTH ")) { + this._supportedAuthMethods = line.slice(5).split(" "); + } else { + this._capabilities.push(line.split(" ")[0]); + } + } + + if (!this._secureTransport && this.options.alwaysSTARTTLS) { + // STARTTLS is required by the user. Detect if the server supports it. + if (this._capabilities.includes("STARTTLS")) { + this._currentAction = this._actionSTARTTLS; + this._sendCommand("STARTTLS"); + return; + } + // STARTTLS is required but not advertised. + this._onNsError(MsgUtils.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS); + return; + } + + // If a preferred method is not supported by the server, no need to try it. + this._possibleAuthMethods = this._preferredAuthMethods.filter(x => + this._supportedAuthMethods.includes(x) + ); + this.logger.debug(`Possible auth methods: ${this._possibleAuthMethods}`); + this._nextAuthMethod = this._possibleAuthMethods[0]; + + if ( + this._capabilities.includes("CLIENTID") && + (this._secureTransport || + // For test purpose. + ["localhost", "127.0.0.1", "::1"].includes(this._server.hostname)) && + this._server.clientidEnabled && + this._server.clientid + ) { + // Client identity extension, still a draft. + this._currentAction = this._actionCLIENTID; + this._sendCommand("CLIENTID UUID " + this._server.clientid, true); + } else { + this._authenticateUser(); + } + } + + /** + * Handles server response for STARTTLS command. If there's an error + * try HELO instead, otherwise initiate TLS upgrade. If the upgrade + * succeeds restart the EHLO + * + * @param {string} command - Message from the server. + */ + _actionSTARTTLS(command) { + if (!command.success) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + return; + } + + this.socket.upgradeToSecure(); + this._secureTransport = true; + + // restart protocol flow + this._currentAction = this._actionEHLO; + this._sendCommand("EHLO " + this._getHelloArgument()); + } + + /** + * Response to HELO + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionHELO(command) { + if (!command.success) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + return; + } + this._authenticateUser(); + } + + /** + * Handles server response for CLIENTID command. If successful then will + * initiate the authenticateUser process. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionCLIENTID(command) { + if (!command.success) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + return; + } + this._authenticateUser(); + } + + /** + * Returns the saved/cached server password, or show a password dialog. If the + * user cancels the dialog, abort sending. + * + * @returns {string} The server password. + */ + _getPassword() { + try { + return this._authenticator.getPassword(); + } catch (e) { + if (e.result == Cr.NS_ERROR_ABORT) { + this.quit(); + this.onerror(e.result); + } else { + throw e; + } + } + return null; + } + + /** + * Response to AUTH LOGIN, if successful expects base64 encoded username + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionAUTH_LOGIN_USER(command) { + if (command.statusCode !== 334 || command.data !== "VXNlcm5hbWU6") { + this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data); + return; + } + this.logger.debug("AUTH LOGIN USER"); + this._currentAction = this._actionAUTH_LOGIN_PASS; + this._sendCommand(btoa(this._authenticator.username), true); + } + + /** + * Process the response to AUTH LOGIN with a username. If successful, expects + * a base64-encoded password. + * + * @param {{statusCode: number, data: string}} command - Parsed command from + * the server. + */ + _actionAUTH_LOGIN_PASS(command) { + if ( + command.statusCode !== 334 || + (command.data !== btoa("Password:") && command.data !== btoa("password:")) + ) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data); + return; + } + this.logger.debug("AUTH LOGIN PASS"); + this._currentAction = this._actionAUTHComplete; + let password = this._getPassword(); + if ( + !Services.prefs.getBoolPref( + "mail.smtp_login_pop3_user_pass_auth_is_latin1", + true + ) || + !/^[\x00-\xFF]+$/.test(password) // eslint-disable-line no-control-regex + ) { + // Unlike PLAIN auth, the payload of LOGIN auth is not standardized. When + // `mail.smtp_login_pop3_user_pass_auth_is_latin1` is true, we apply + // base64 encoding directly. Otherwise, we convert it to UTF-8 + // BinaryString first. + password = MailStringUtils.stringToByteString(password); + } + this._sendCommand(btoa(password), true); + } + + /** + * Response to AUTH CRAM, if successful expects base64 encoded challenge. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + async _actionAUTH_CRAM(command) { + if (command.statusCode !== 334) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data); + return; + } + this._currentAction = this._actionAUTHComplete; + this._sendCommand( + this._authenticator.getCramMd5Token(this._getPassword(), command.data), + true + ); + } + + /** + * Response to AUTH XOAUTH2 token, if error occurs send empty response + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionAUTH_XOAUTH2(command) { + if (!command.success) { + this.logger.warn("Error during AUTH XOAUTH2, sending empty response"); + this._sendCommand(""); + this._currentAction = this._actionAUTHComplete; + } else { + this._actionAUTHComplete(command); + } + } + + /** + * Response to AUTH GSSAPI, if successful expects a base64 encoded challenge. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionAUTH_GSSAPI(command) { + // GSSAPI auth can be multiple steps. We exchange tokens with the server + // until success or failure. + if (command.success) { + this._actionAUTHComplete(command); + return; + } + if (command.statusCode !== 334) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_GSSAPI, command.data); + return; + } + let token = this._authenticator.getNextGssapiToken(command.data); + this._currentAction = this._actionAUTH_GSSAPI; + this._sendCommand(token, true); + } + + /** + * Response to AUTH NTLM, if successful expects a base64 encoded challenge. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionAUTH_NTLM(command) { + // NTLM auth can be multiple steps. We exchange tokens with the server + // until success or failure. + if (command.success) { + this._actionAUTHComplete(command); + return; + } + if (command.statusCode !== 334) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data); + return; + } + let token = this._authenticator.getNextNtlmToken(command.data); + this._currentAction = this._actionAUTH_NTLM; + this._sendCommand(token, true); + } + + /** + * Checks if authentication succeeded or not. If successfully authenticated + * emit `idle` to indicate that an e-mail can be sent using this connection + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionAUTHComplete(command) { + this._authenticating = false; + if (!command.success) { + this._onAuthFailed(command); + return; + } + + this.logger.debug("Authentication successful."); + + this._currentAction = this._actionIdle; + this.onidle(); // ready to take orders + } + + /** + * Used when the connection is idle, not expecting anything from the server. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionIdle(command) { + this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data); + } + + /** + * Response to MAIL FROM command. Proceed to defining RCPT TO list if successful + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionMAIL(command) { + if (!command.success) { + let errorCode = MsgUtils.NS_ERROR_SENDING_FROM_COMMAND; // default code + if (command.statusCode == 552) { + // Too much mail data indicated by "size" parameter of MAIL FROM. + // @see https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.9 + errorCode = MsgUtils.NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2; + } + if (command.statusCode == 452 || command.statusCode == 451) { + // @see https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.10 + errorCode = MsgUtils.NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED; + } + this._onNsError(errorCode, command.data, null, command.statusCode); + return; + } + this.logger.debug( + "MAIL FROM successful, proceeding with " + + this._envelope.rcptQueue.length + + " recipients" + ); + this.logger.debug("Adding recipient..."); + this._envelope.curRecipient = this._envelope.rcptQueue.shift(); + this._currentAction = this._actionRCPT; + this._sendCommand( + `RCPT TO:<${this._envelope.curRecipient}>${this._getRCPTParameters()}` + ); + } + + /** + * Prepare the RCPT params, currently only DSN params. If the server supports + * DSN and sender requested DSN, append DSN params to each RCPT TO command. + */ + _getRCPTParameters() { + if (this._capabilities.includes("DSN") && this._envelope.requestDSN) { + let notify = []; + if (Services.prefs.getBoolPref("mail.dsn.request_never_on")) { + notify.push("NEVER"); + } else { + if (Services.prefs.getBoolPref("mail.dsn.request_on_success_on")) { + notify.push("SUCCESS"); + } + if (Services.prefs.getBoolPref("mail.dsn.request_on_failure_on")) { + notify.push("FAILURE"); + } + if (Services.prefs.getBoolPref("mail.dsn.request_on_delay_on")) { + notify.push("DELAY"); + } + } + if (notify.length > 0) { + return ` NOTIFY=${notify.join(",")}`; + } + } + return ""; + } + + /** + * Response to a RCPT TO command. If the command is unsuccessful, emit an + * error to abort the sending. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionRCPT(command) { + if (!command.success) { + this._onNsError( + MsgUtils.NS_ERROR_SENDING_RCPT_COMMAND, + command.data, + this._envelope.curRecipient, + command.statusCode + ); + return; + } + this.rcptCount++; + this._envelope.responseQueue.push(this._envelope.curRecipient); + + if (this._envelope.rcptQueue.length) { + // Send the next recipient. + this._envelope.curRecipient = this._envelope.rcptQueue.shift(); + this._currentAction = this._actionRCPT; + this._sendCommand( + `RCPT TO:<${this._envelope.curRecipient}>${this._getRCPTParameters()}` + ); + } else { + this.logger.debug( + `Total RCPTs during this connection: ${this.rcptCount}` + ); + this.logger.debug("RCPT TO done. Proceeding with payload."); + this._currentAction = this._actionDATA; + this._sendCommand("DATA"); + } + } + + /** + * Response to the DATA command. Server is now waiting for a message, so emit `onready` + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionDATA(command) { + // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24 + // some servers might use 250 instead + if (![250, 354].includes(command.statusCode)) { + this._onNsError(MsgUtils.NS_ERROR_SENDING_DATA_COMMAND, command.data); + return; + } + + this._dataMode = true; + this._currentAction = this._actionIdle; + this.onready(this._envelope.rcptFailed); + } + + /** + * Response from the server, once the message stream has ended with <CR><LF>.<CR><LF> + * Emits `ondone`. + * + * @param {object} command Parsed command from the server {statusCode, data} + */ + _actionStream(command) { + var rcpt; + + if (this.options.lmtp) { + // LMTP returns a response code for *every* successfully set recipient + // For every recipient the message might succeed or fail individually + + rcpt = this._envelope.responseQueue.shift(); + if (!command.success) { + this.logger.error("Local delivery to " + rcpt + " failed."); + this._envelope.rcptFailed.push(rcpt); + } else { + this.logger.error("Local delivery to " + rcpt + " succeeded."); + } + + if (this._envelope.responseQueue.length) { + this._currentAction = this._actionStream; + return; + } + + this._currentAction = this._actionIdle; + this.ondone(0); + } else { + // For SMTP the message either fails or succeeds, there is no information + // about individual recipients + + if (!command.success) { + this.logger.error("Message sending failed."); + } else { + this.logger.debug("Message sent successfully."); + this.isRetry = false; + } + + this._currentAction = this._actionIdle; + if (command.success) { + this.ondone(0); + } else { + this._onNsError(MsgUtils.NS_ERROR_SENDING_MESSAGE, command.data); + } + } + + this._freed = true; + this.onFree(); + } +} diff --git a/comm/mailnews/compose/src/SmtpServer.jsm b/comm/mailnews/compose/src/SmtpServer.jsm new file mode 100644 index 0000000000..3ce81ff936 --- /dev/null +++ b/comm/mailnews/compose/src/SmtpServer.jsm @@ -0,0 +1,519 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["SmtpServer"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +const lazy = {}; + +XPCOMUtils.defineLazyModuleGetters(lazy, { + SmtpClient: "resource:///modules/SmtpClient.jsm", +}); + +/** + * This class represents a single SMTP server. + * + * @implements {nsISmtpServer} + * @implements {nsIObserver} + */ + +class SmtpServer { + QueryInterface = ChromeUtils.generateQI(["nsISmtpServer", "nsIObserver"]); + + constructor() { + this._key = ""; + this._loadPrefs(); + + Services.obs.addObserver(this, "passwordmgr-storage-changed"); + } + + /** + * Observe() receives notifications for all accounts, not just this SMTP + * server's * account. So we ignore all notifications not intended for this + * server. When the state of the password manager changes we need to clear the + * this server's password from the cache in case the user just changed or + * removed the password or username. + * OAuth2 servers often automatically change the password manager's stored + * password (the token). + */ + observe(subject, topic, data) { + if (topic == "passwordmgr-storage-changed") { + // Check that the notification is for this server and user. + let otherFullName = ""; + let otherUsername = ""; + if (subject instanceof Ci.nsILoginInfo) { + // The login info for a server has been removed with aData being + // "removeLogin" or "removeAllLogins". + otherFullName = subject.origin; + otherUsername = subject.username; + } else if (subject instanceof Ci.nsIArray) { + // Probably a 2 element array containing old and new login info due to + // aData being "modifyLogin". E.g., a user has modified the password or + // username in the password manager or an OAuth2 token string has + // automatically changed. Only need to look at names in first array + // element (login info before any modification) since the user might + // have changed the username as found in the 2nd elements. (The + // hostname can't be modified in the password manager. + otherFullName = subject.queryElementAt(0, Ci.nsISupports).origin; + otherUsername = subject.queryElementAt(0, Ci.nsISupports).username; + } + if (otherFullName) { + if ( + otherFullName != "smtp://" + this.hostname || + otherUsername != this.username + ) { + // Not for this account; keep this account's password. + return; + } + } else if (data != "hostSavingDisabled") { + // "hostSavingDisabled" only occurs during test_smtpServer.js and + // expects the password to be removed from memory cache. Otherwise, we + // don't have enough information to decide to remove the cached + // password, so keep it. + return; + } + // Remove the password for this server cached in memory. + this.password = ""; + } + } + + get key() { + return this._key; + } + + set key(key) { + this._key = key; + this._loadPrefs(); + } + + get UID() { + let uid = this._prefs.getStringPref("uid", ""); + if (uid) { + return uid; + } + return (this.UID = Services.uuid + .generateUUID() + .toString() + .substring(1, 37)); + } + + set UID(uid) { + if (this._prefs.prefHasUserValue("uid")) { + throw new Components.Exception("uid is already set", Cr.NS_ERROR_ABORT); + } + this._prefs.setStringPref("uid", uid); + } + + get description() { + return this._prefs.getStringPref("description", ""); + } + + set description(value) { + this._prefs.setStringPref("description", value); + } + + get hostname() { + return this._prefs.getStringPref("hostname", ""); + } + + set hostname(value) { + if (value.toLowerCase() != this.hostname.toLowerCase()) { + // Reset password so that users are prompted for new password for the new + // host. + this.forgetPassword(); + } + this._prefs.setStringPref("hostname", value); + } + + get port() { + return this._prefs.getIntPref("port", 0); + } + + set port(value) { + if (value) { + this._prefs.setIntPref("port", value); + } else { + this._prefs.clearUserPref("port"); + } + } + + get displayname() { + return `${this.hostname}` + (this.port ? `:${this.port}` : ""); + } + + get username() { + return this._prefs.getCharPref("username", ""); + } + + set username(value) { + if (value != this.username) { + // Reset password so that users are prompted for new password for the new + // username. + this.forgetPassword(); + } + this._setCharPref("username", value); + } + + get clientid() { + return this._getCharPrefWithDefault("clientid"); + } + + set clientid(value) { + this._setCharPref("clientid", value); + } + + get clientidEnabled() { + try { + return this._prefs.getBoolPref("clientidEnabled"); + } catch (e) { + return this._defaultPrefs.getBoolPref("clientidEnabled", false); + } + } + + set clientidEnabled(value) { + this._prefs.setBoolPref("clientidEnabled", value); + } + + get authMethod() { + return this._getIntPrefWithDefault("authMethod", 3); + } + + set authMethod(value) { + this._prefs.setIntPref("authMethod", value); + } + + get socketType() { + return this._getIntPrefWithDefault("try_ssl", 0); + } + + set socketType(value) { + this._prefs.setIntPref("try_ssl", value); + } + + get helloArgument() { + return this._getCharPrefWithDefault("hello_argument"); + } + + get serverURI() { + return this._getServerURI(true); + } + + /** + * If pref max_cached_connection is set to less than 1, allow only one + * connection and one message to be sent on that connection. Otherwise, allow + * up to max_cached_connection (default to 3) with each connection allowed to + * send multiple messages. + */ + get maximumConnectionsNumber() { + let maxConnections = this._getIntPrefWithDefault( + "max_cached_connections", + 3 + ); + // Always return a value >= 0. + return maxConnections > 0 ? maxConnections : 0; + } + + set maximumConnectionsNumber(value) { + this._prefs.setIntPref("max_cached_connections", value); + } + + get password() { + if (this._password) { + return this._password; + } + let incomingAccountKey = this._prefs.getCharPref("incomingAccount", ""); + let incomingServer; + if (incomingAccountKey) { + incomingServer = + MailServices.accounts.getIncomingServer(incomingAccountKey); + } else { + let useMatchingHostNameServer = Services.prefs.getBoolPref( + "mail.smtp.useMatchingHostNameServer" + ); + let useMatchingDomainServer = Services.prefs.getBoolPref( + "mail.smtp.useMatchingDomainServer" + ); + if (useMatchingHostNameServer || useMatchingDomainServer) { + if (useMatchingHostNameServer) { + // Pass in empty type and port=0, to match imap and pop3. + incomingServer = MailServices.accounts.findServer( + this.username, + this.hostname, + "", + 0 + ); + } + if ( + !incomingServer && + useMatchingDomainServer && + this.hostname.includes(".") + ) { + let newHostname = this.hostname.slice(0, this.hostname.indexOf(".")); + for (let server of MailServices.accounts.allServers) { + if (server.username == this.username) { + let serverHostName = server.hostName; + if ( + serverHostName.includes(".") && + serverHostName.slice(0, serverHostName.indexOf(".")) == + newHostname + ) { + incomingServer = server; + break; + } + } + } + } + } + } + return incomingServer?.password || ""; + } + + set password(password) { + this._password = password; + } + + getPasswordWithUI(promptMessage, promptTitle) { + let authPrompt; + try { + // This prompt has a checkbox for saving password. + authPrompt = Cc["@mozilla.org/messenger/msgAuthPrompt;1"].getService( + Ci.nsIAuthPrompt + ); + } catch (e) { + // Often happens in tests. This prompt has no checkbox for saving password. + authPrompt = Services.ww.getNewAuthPrompter(null); + } + let password = this._getPasswordWithoutUI(); + if (password) { + this.password = password; + return this.password; + } + let outUsername = {}; + let outPassword = {}; + let ok; + if (this.username) { + ok = authPrompt.promptPassword( + promptTitle, + promptMessage, + this.serverURI, + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, + outPassword + ); + } else { + ok = authPrompt.promptUsernameAndPassword( + promptTitle, + promptMessage, + this.serverURI, + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, + outUsername, + outPassword + ); + } + if (ok) { + if (outUsername.value) { + this.username = outUsername.value; + } + this.password = outPassword.value; + } else { + throw Components.Exception("Password dialog canceled", Cr.NS_ERROR_ABORT); + } + return this.password; + } + + forgetPassword() { + let serverURI = this._getServerURI(); + let logins = Services.logins.findLogins(serverURI, "", serverURI); + for (let login of logins) { + if (login.username == this.username) { + Services.logins.removeLogin(login); + } + } + this.password = ""; + } + + verifyLogon(urlListener, msgWindow) { + return MailServices.smtp.verifyLogon(this, urlListener, msgWindow); + } + + clearAllValues() { + for (let prefName of this._prefs.getChildList("")) { + this._prefs.clearUserPref(prefName); + } + } + + /** + * @returns {string} + */ + _getPasswordWithoutUI() { + let serverURI = this._getServerURI(); + let logins = Services.logins.findLogins(serverURI, "", serverURI); + for (let login of logins) { + if (login.username == this.username) { + return login.password; + } + } + return null; + } + + /** + * Get server URI in the form of smtp://[user@]hostname. + * + * @param {boolean} includeUsername - Whether to include the username. + * @returns {string} + */ + _getServerURI(includeUsername) { + // When constructing nsIURI, need to wrap IPv6 address in []. + let hostname = this.hostname.includes(":") + ? `[${this.hostname}]` + : this.hostname; + return ( + "smtp://" + + (includeUsername && this.username + ? `${encodeURIComponent(this.username)}@` + : "") + + hostname + ); + } + + /** + * Get the associated pref branch and the default SMTP server branch. + */ + _loadPrefs() { + this._prefs = Services.prefs.getBranch(`mail.smtpserver.${this._key}.`); + this._defaultPrefs = Services.prefs.getBranch("mail.smtpserver.default."); + } + + /** + * Set or clear a string preference. + * + * @param {string} name - The preference name. + * @param {string} value - The preference value. + */ + _setCharPref(name, value) { + if (value) { + this._prefs.setCharPref(name, value); + } else { + this._prefs.clearUserPref(name); + } + } + + /** + * Get the value of a char preference from this or default SMTP server. + * + * @param {string} name - The preference name. + * @param {number} [defaultValue=""] - The default value to return. + * @returns {string} + */ + _getCharPrefWithDefault(name, defaultValue = "") { + try { + return this._prefs.getCharPref(name); + } catch (e) { + return this._defaultPrefs.getCharPref(name, defaultValue); + } + } + + /** + * Get the value of an integer preference from this or default SMTP server. + * + * @param {string} name - The preference name. + * @param {number} defaultValue - The default value to return. + * @returns {number} + */ + _getIntPrefWithDefault(name, defaultValue) { + try { + return this._prefs.getIntPref(name); + } catch (e) { + return this._defaultPrefs.getIntPref(name, defaultValue); + } + } + + get wrappedJSObject() { + return this; + } + + // @type {SmtpClient[]} - An array of connections can be used. + _freeConnections = []; + // @type {SmtpClient[]} - An array of connections in use. + _busyConnections = []; + // @type {Function[]} - An array of Promise.resolve functions. + _connectionWaitingQueue = []; + + closeCachedConnections() { + // Close all connections. + for (let client of [...this._freeConnections, ...this._busyConnections]) { + client.quit(); + } + // Cancel all waitings in queue. + for (let resolve of this._connectionWaitingQueue) { + resolve(false); + } + this._freeConnections = []; + this._busyConnections = []; + } + + /** + * Get an idle connection that can be used. + * + * @returns {SmtpClient} + */ + async _getNextClient() { + // The newest connection is the least likely to have timed out. + let client = this._freeConnections.pop(); + if (client) { + this._busyConnections.push(client); + return client; + } + const maxConns = this.maximumConnectionsNumber + ? this.maximumConnectionsNumber + : 1; + if ( + this._freeConnections.length + this._busyConnections.length < + maxConns + ) { + // Create a new client if the pool is not full. + client = new lazy.SmtpClient(this); + this._busyConnections.push(client); + return client; + } + // Wait until a connection is available. + await new Promise(resolve => this._connectionWaitingQueue.push(resolve)); + return this._getNextClient(); + } + /** + * Do some actions with a connection. + * + * @param {Function} handler - A callback function to take a SmtpClient + * instance, and do some actions. + */ + async withClient(handler) { + let client = await this._getNextClient(); + client.onFree = () => { + this._busyConnections = this._busyConnections.filter(c => c != client); + // Per RFC, the minimum total number of recipients that MUST be buffered + // is 100 recipients. + // @see https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.8 + // So use a new connection for the next message to avoid running into + // recipient limits. + // If user has set SMTP pref max_cached_connection to less than 1, + // use a new connection for each message. + if (this.maximumConnectionsNumber == 0 || client.rcptCount > 99) { + // Send QUIT, server will then terminate the connection + client.quit(); + } else { + // Keep using this connection + this._freeConnections.push(client); + // Resolve the first waiting in queue. + this._connectionWaitingQueue.shift()?.(); + } + }; + handler(client); + client.connect(); + } +} diff --git a/comm/mailnews/compose/src/SmtpService.jsm b/comm/mailnews/compose/src/SmtpService.jsm new file mode 100644 index 0000000000..d90983a213 --- /dev/null +++ b/comm/mailnews/compose/src/SmtpService.jsm @@ -0,0 +1,350 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const EXPORTED_SYMBOLS = ["SmtpService"]; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; +XPCOMUtils.defineLazyModuleGetters(lazy, { + SmtpClient: "resource:///modules/SmtpClient.jsm", + MsgUtils: "resource:///modules/MimeMessageUtils.jsm", +}); + +/** + * The SMTP service. + * + * @implements {nsISmtpService} + */ +class SmtpService { + QueryInterface = ChromeUtils.generateQI(["nsISmtpService"]); + + constructor() { + this._servers = []; + this._logger = lazy.MsgUtils.smtpLogger; + } + + /** + * @see nsISmtpService + */ + get defaultServer() { + let defaultServerKey = Services.prefs.getCharPref( + "mail.smtp.defaultserver", + "" + ); + if (defaultServerKey) { + // Get it from the prefs. + return this.getServerByKey(defaultServerKey); + } + + // No pref set, so set the first one as default, and return it. + if (this.servers.length > 0) { + this.defaultServer = this.servers[0]; + return this.servers[0]; + } + return null; + } + + set defaultServer(server) { + Services.prefs.setCharPref("mail.smtp.defaultserver", server.key); + } + + get servers() { + if (!this._servers.length) { + // Load SMTP servers from prefs. + this._servers = this._getSmtpServerKeys().map(key => + this._keyToServer(key) + ); + } + return this._servers; + } + + get wrappedJSObject() { + return this; + } + + /** + * @see nsISmtpService + */ + async sendMailMessage( + messageFile, + recipients, + userIdentity, + sender, + password, + deliveryListener, + statusListener, + notificationCallbacks, + requestDSN, + messageId, + outURI, + outRequest + ) { + this._logger.debug(`Sending message ${messageId}`); + let server = this.getServerByIdentity(userIdentity); + if (!server) { + // Occurs for at least one unit test, but test does not fail if return + // here. This check for "server" can be removed if tests are fixed. + console.log( + `No server found for identity with email ${userIdentity.email} and ` + + `smtpServerKey ${userIdentity.smtpServerKey}` + ); + return; + } + if (password) { + server.password = password; + } + let runningUrl = this._getRunningUri(server); + await server.wrappedJSObject.withClient(client => { + deliveryListener?.OnStartRunningUrl(runningUrl, 0); + let fresh = true; + client.onidle = () => { + // onidle can occur multiple times, but we should only init sending + // when sending a new message(fresh is true) or when a new connection + // replaces the original connection due to error 4xx response + // (client.isRetry is true). + if (!fresh && !client.isRetry) { + return; + } + // Init when fresh==true OR re-init sending when client.isRetry==true. + fresh = false; + let from = sender; + let to = MailServices.headerParser + .parseEncodedHeaderW(decodeURIComponent(recipients)) + .map(rec => rec.email); + + if ( + !Services.prefs.getBoolPref( + "mail.smtp.useSenderForSmtpMailFrom", + false + ) + ) { + from = userIdentity.email; + } + if (!messageId) { + messageId = Cc["@mozilla.org/messengercompose/computils;1"] + .createInstance(Ci.nsIMsgCompUtils) + .msgGenerateMessageId(userIdentity, null); + } + client.useEnvelope({ + from: MailServices.headerParser.parseEncodedHeaderW( + decodeURIComponent(from) + )[0].email, + to, + size: messageFile.fileSize, + requestDSN, + messageId, + }); + }; + let socketOnDrain; + client.onready = async () => { + let fstream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + // PR_RDONLY + fstream.init(messageFile, 0x01, 0, 0); + + let sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sstream.init(fstream); + + let sentSize = 0; + let totalSize = messageFile.fileSize; + let progressListener = statusListener?.QueryInterface( + Ci.nsIWebProgressListener + ); + + while (sstream.available()) { + let chunk = sstream.read(65536); + let canSendMore = client.send(chunk); + if (!canSendMore) { + // Socket buffer is full, wait for the ondrain event. + await new Promise(resolve => (socketOnDrain = resolve)); + } + // In practice, chunks are buffered by TCPSocket, progress reaches 100% + // almost immediately unless message is larger than chunk size. + sentSize += chunk.length; + progressListener?.onProgressChange( + null, + null, + sentSize, + totalSize, + sentSize, + totalSize + ); + } + sstream.close(); + fstream.close(); + client.end(); + + // Set progress to indeterminate. + progressListener?.onProgressChange(null, null, 0, -1, 0, -1); + }; + client.ondrain = () => { + // Socket buffer is empty, safe to continue sending. + socketOnDrain(); + }; + client.ondone = exitCode => { + if (!AppConstants.MOZ_SUITE) { + Services.telemetry.scalarAdd("tb.mails.sent", 1); + } + deliveryListener?.OnStopRunningUrl(runningUrl, exitCode); + }; + client.onerror = (nsError, errorMessage, secInfo) => { + runningUrl.QueryInterface(Ci.nsIMsgMailNewsUrl); + if (secInfo) { + // TODO(emilio): Passing the failed security info as part of the URI is + // quite a smell, but monkey see monkey do... + runningUrl.failedSecInfo = secInfo; + } + runningUrl.errorMessage = errorMessage; + deliveryListener?.OnStopRunningUrl(runningUrl, nsError); + }; + + outRequest.value = { + cancel() { + client.close(true); + }, + }; + }); + } + + /** + * @see nsISmtpService + */ + verifyLogon(server, urlListener, msgWindow) { + let client = new lazy.SmtpClient(server); + client.connect(); + let runningUrl = this._getRunningUri(server); + client.onerror = (nsError, errorMessage, secInfo) => { + runningUrl.QueryInterface(Ci.nsIMsgMailNewsUrl); + if (secInfo) { + runningUrl.failedSecInfo = secInfo; + } + runningUrl.errorMessage = errorMessage; + urlListener.OnStopRunningUrl(runningUrl, nsError); + }; + client.onready = () => { + urlListener.OnStopRunningUrl(runningUrl, 0); + client.close(); + }; + return runningUrl; + } + + /** + * @see nsISmtpService + */ + getServerByIdentity(userIdentity) { + return userIdentity.smtpServerKey + ? this.getServerByKey(userIdentity.smtpServerKey) + : this.defaultServer; + } + + /** + * @see nsISmtpService + */ + getServerByKey(key) { + return this.servers.find(s => s.key == key); + } + + /** + * @see nsISmtpService + */ + createServer() { + let serverKeys = this._getSmtpServerKeys(); + let i = 1; + let key; + do { + key = `smtp${i++}`; + } while (serverKeys.includes(key)); + + serverKeys.push(key); + this._saveSmtpServerKeys(serverKeys); + this._servers = []; // Reset to force repopulation of this.servers. + return this.servers.at(-1); + } + + /** + * @see nsISmtpService + */ + deleteServer(server) { + let serverKeys = this._getSmtpServerKeys().filter(k => k != server.key); + this._servers = this.servers.filter(s => s.key != server.key); + this._saveSmtpServerKeys(serverKeys); + } + + /** + * @see nsISmtpService + */ + findServer(username, hostname) { + username = username?.toLowerCase(); + hostname = hostname?.toLowerCase(); + return this.servers.find(server => { + if ( + (username && server.username.toLowerCase() != username) || + (hostname && server.hostname.toLowerCase() != hostname) + ) { + return false; + } + return true; + }); + } + + /** + * Get all SMTP server keys from prefs. + * + * @returns {string[]} + */ + _getSmtpServerKeys() { + return Services.prefs + .getCharPref("mail.smtpservers", "") + .split(",") + .filter(Boolean); + } + + /** + * Save SMTP server keys to prefs. + * + * @param {string[]} keys - The key list to save. + */ + _saveSmtpServerKeys(keys) { + return Services.prefs.setCharPref("mail.smtpservers", keys.join(",")); + } + + /** + * Create an nsISmtpServer from a key. + * + * @param {string} key - The key for the SmtpServer. + * @returns {nsISmtpServer} + */ + _keyToServer(key) { + let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance( + Ci.nsISmtpServer + ); + // Setting the server key will set up all of its other properties by + // reading them from the prefs. + server.key = key; + return server; + } + + /** + * Get the server URI in the form of smtp://user@hostname:port. + * + * @param {nsISmtpServer} server - The SMTP server. + * @returns {nsIURI} + */ + _getRunningUri(server) { + let spec = server.serverURI + (server.port ? `:${server.port}` : ""); + return Services.io.newURI(spec); + } +} diff --git a/comm/mailnews/compose/src/components.conf b/comm/mailnews/compose/src/components.conf new file mode 100644 index 0000000000..44754289f5 --- /dev/null +++ b/comm/mailnews/compose/src/components.conf @@ -0,0 +1,197 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + "cid": "{588595fe-1ada-11d3-a715-0060b0eb39b5}", + "contract_ids": [ + "@mozilla.org/messengercompose;1", + "@mozilla.org/commandlinehandler/general-startup;1?type=compose", + ], + "type": "nsMsgComposeService", + "init_method": "Init", + "headers": ["/comm/mailnews/compose/src/nsMsgComposeService.h"], + "categories": {"command-line-handler": "m-compose"}, + "name": "Compose", + "interfaces": ["nsIMsgComposeService"], + }, + { + "cid": "{0b63fb80-bbba-11d4-9daa-91b657eb313c}", + "contract_ids": [ + "@mozilla.org/uriloader/content-handler;1?type=application/x-mailto" + ], + "type": "nsMsgComposeContentHandler", + "headers": ["/comm/mailnews/compose/src/nsMsgComposeContentHandler.h"], + }, + { + "cid": "{eb5bdaf8-bbc6-11d2-a6ec-0060b0eb39b5}", + "contract_ids": ["@mozilla.org/messengercompose/compose;1"], + "type": "nsMsgCompose", + "headers": ["/comm/mailnews/compose/src/nsMsgCompose.h"], + }, + { + "cid": "{cb998a00-c079-11d4-9daa-8df64bab2efc}", + "contract_ids": ["@mozilla.org/messengercompose/composeparams;1"], + "type": "nsMsgComposeParams", + "headers": ["/comm/mailnews/compose/src/nsMsgComposeParams.h"], + }, + { + "cid": "{acc72781-2cea-11d5-9daa-bacdeac1eefc}", + "contract_ids": ["@mozilla.org/messengercompose/composesendlistener;1"], + "type": "nsMsgComposeSendListener", + "headers": ["/comm/mailnews/compose/src/nsMsgCompose.h"], + }, + { + "cid": "{1e0e7c01-3e4c-11d5-9daa-f88d288130fc}", + "contract_ids": ["@mozilla.org/messengercompose/composeprogressparameters;1"], + "type": "nsMsgComposeProgressParams", + "headers": ["/comm/mailnews/compose/src/nsMsgComposeProgressParams.h"], + }, + { + "cid": "{e64b0f51-0d7b-4e2f-8c60-3862ee8c174f}", + "contract_ids": ["@mozilla.org/messengercompose/composefields;1"], + "type": "nsMsgCompFields", + "headers": ["/comm/mailnews/compose/src/nsMsgCompFields.h"], + }, + { + "cid": "{27b8d045-8d9f-4fa8-bfb6-8a0f8d09ce89}", + "contract_ids": ["@mozilla.org/messengercompose/attachment;1"], + "type": "nsMsgAttachment", + "headers": ["/comm/mailnews/compose/src/nsMsgAttachment.h"], + }, + { + "cid": "{9e16958d-d9e9-4cae-b723-a5bccf104998}", + "contract_ids": ["@mozilla.org/messengercompose/attachmentdata;1"], + "type": "nsMsgAttachmentData", + "headers": ["/comm/mailnews/compose/src/nsMsgAttachmentData.h"], + }, + { + "cid": "{ef173501-4e14-42b9-ae1f-7770de235c29}", + "contract_ids": ["@mozilla.org/messengercompose/attachedfile;1"], + "type": "nsMsgAttachedFile", + "headers": ["/comm/mailnews/compose/src/nsMsgAttachedFile.h"], + }, + { + "cid": "{e15c83f1-1cf4-11d3-8ef0-00a024a7d144}", + "contract_ids": ["@mozilla.org/messengercompose/sendlater;1"], + "type": "nsMsgSendLater", + "init_method": "Init", + "headers": ["/comm/mailnews/compose/src/nsMsgSendLater.h"], + }, + { + "cid": "{be59dbf0-2812-11d3-80a3-006008128c4e}", + "contract_ids": ["@mozilla.org/messengercompose/smtpurl;1"], + "type": "nsSmtpUrl", + "headers": ["/comm/mailnews/compose/src/nsSmtpUrl.h"], + }, + { + "cid": "{05bab5e7-9c7d-11d3-98a3-001083010e9b}", + "contract_ids": ["@mozilla.org/messengercompose/mailtourl;1"], + "type": "nsMailtoUrl", + "headers": ["/comm/mailnews/compose/src/nsSmtpUrl.h"], + }, + { + "cid": "{1c7abf0c-21e5-11d3-8ef1-00a024a7d144}", + "contract_ids": ["@mozilla.org/messengercompose/quoting;1"], + "type": "nsMsgQuote", + "headers": ["/comm/mailnews/compose/src/nsMsgQuote.h"], + }, + { + "cid": "{683728ac-88df-11d3-989d-001083010e9b}", + "contract_ids": ["@mozilla.org/messengercompose/quotinglistener;1"], + "type": "nsMsgQuoteListener", + "headers": ["/comm/mailnews/compose/src/nsMsgQuote.h"], + }, + { + "cid": "{ceb0dca2-5e7d-4204-94d4-2ab925921fae}", + "contract_ids": ["@mozilla.org/messengercompose/computils;1"], + "type": "nsMsgCompUtils", + "headers": ["/comm/mailnews/compose/src/nsMsgCompUtils.h"], + }, + { + "cid": "{0874c3b5-317d-11d3-8efb-00a024a7d144}", + "contract_ids": ["@mozilla.org/messengercompose/msgcopy;1"], + "type": "nsMsgCopy", + "headers": ["/comm/mailnews/compose/src/nsMsgCopy.h"], + }, + { + "cid": "{e5872045-a87b-4ea0-b366-45ebd7dc89d9}", + "contract_ids": ["@mozilla.org/messengercompose/sendreport;1"], + "type": "nsMsgSendReport", + "headers": ["/comm/mailnews/compose/src/nsMsgSendReport.h"], + }, + { + "cid": "{028b9c1e-8d0a-4518-80c2-842e07846eaa}", + "contract_ids": ["@mozilla.org/messengercompose/send;1"], + "jsm": "resource:///modules/MessageSend.jsm", + "constructor": "MessageSend", + }, + { + "cid": "{b14c2b67-8680-4c11-8d63-9403c7d4f757}", + "contract_ids": ["@mozilla.org/network/protocol;1?name=smtp"], + "jsm": "resource:///modules/SMTPProtocolHandler.jsm", + "constructor": "SMTPProtocolHandler", + "protocol_config": { + "scheme": "smtp", + "flags": [ + "URI_NORELATIVE", + "URI_DANGEROUS_TO_LOAD", + "ALLOWS_PROXY", + "URI_NON_PERSISTABLE", + "URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT", + ], + "default_port": 25, + }, + }, + { + "cid": "{057d0997-9e3a-411e-b4ee-2602f53fe05f}", + "contract_ids": ["@mozilla.org/network/protocol;1?name=smtps"], + "jsm": "resource:///modules/SMTPProtocolHandler.jsm", + "constructor": "SMTPSProtocolHandler", + "protocol_config": { + "scheme": "smtps", + "flags": [ + "URI_NORELATIVE", + "URI_DANGEROUS_TO_LOAD", + "ALLOWS_PROXY", + "URI_NON_PERSISTABLE", + "URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT", + ], + "default_port": 465, + }, + }, + { + "cid": "{af314bd9-0b28-4f69-9bea-592ab4dc6811}", + "contract_ids": ["@mozilla.org/network/protocol;1?name=mailto"], + "jsm": "resource:///modules/MailtoProtocolHandler.jsm", + "constructor": "MailtoProtocolHandler", + "protocol_config": { + "scheme": "mailto", + "flags": [ + "URI_NORELATIVE", + "ALLOWS_PROXY", + "URI_LOADABLE_BY_ANYONE", + "URI_NON_PERSISTABLE", + "URI_DOES_NOT_RETURN_DATA", + "URI_FORBIDS_COOKIE_ACCESS", + ], + }, + }, + { + "cid": "{acda6039-8b17-46c1-a8ed-ad50aa80f412}", + "contract_ids": ["@mozilla.org/messengercompose/smtp;1"], + "jsm": "resource:///modules/SmtpService.jsm", + "constructor": "SmtpService", + "name": "Smtp", + "interfaces": ["nsISmtpService"], + }, + { + "cid": "{3a75f5ea-651e-4696-9813-848c03da8bbd}", + "contract_ids": ["@mozilla.org/messenger/smtp/server;1"], + "jsm": "resource:///modules/SmtpServer.jsm", + "constructor": "SmtpServer", + }, +] diff --git a/comm/mailnews/compose/src/moz.build b/comm/mailnews/compose/src/moz.build new file mode 100644 index 0000000000..60ff148540 --- /dev/null +++ b/comm/mailnews/compose/src/moz.build @@ -0,0 +1,60 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + "nsComposeStrings.h", + "nsMsgAttachmentData.h", + "nsMsgCompFields.h", + "nsMsgCompose.h", +] + +SOURCES += [ + "nsComposeStrings.cpp", + "nsMsgAttachedFile.cpp", + "nsMsgAttachment.cpp", + "nsMsgAttachmentData.cpp", + "nsMsgCompFields.cpp", + "nsMsgCompose.cpp", + "nsMsgComposeContentHandler.cpp", + "nsMsgComposeParams.cpp", + "nsMsgComposeProgressParams.cpp", + "nsMsgComposeService.cpp", + "nsMsgCompUtils.cpp", + "nsMsgCopy.cpp", + "nsMsgPrompts.cpp", + "nsMsgQuote.cpp", + "nsMsgSendLater.cpp", + "nsMsgSendReport.cpp", + "nsSmtpUrl.cpp", +] + +LOCAL_INCLUDES += [ + "/dom/base", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "mail" + +# clang-cl rightly complains about switch on nsresult. +if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += ["-Wno-switch"] + +EXTRA_JS_MODULES += [ + "MailtoProtocolHandler.jsm", + "MessageSend.jsm", + "MimeEncoder.jsm", + "MimeMessage.jsm", + "MimeMessageUtils.jsm", + "MimePart.jsm", + "SmtpClient.jsm", + "SMTPProtocolHandler.jsm", + "SmtpServer.jsm", + "SmtpService.jsm", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/comm/mailnews/compose/src/nsComposeStrings.cpp b/comm/mailnews/compose/src/nsComposeStrings.cpp new file mode 100644 index 0000000000..666cd14e07 --- /dev/null +++ b/comm/mailnews/compose/src/nsComposeStrings.cpp @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsComposeStrings.h" + +const char* errorStringNameForErrorCode(nsresult aCode) { +#ifdef __GNUC__ +// Temporary workaround until bug 783526 is fixed. +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wswitch" +#endif + switch (aCode) { + case NS_MSG_UNABLE_TO_OPEN_FILE: + return "unableToOpenFile"; + case NS_MSG_UNABLE_TO_OPEN_TMP_FILE: + return "unableToOpenTmpFile"; + case NS_MSG_UNABLE_TO_SAVE_TEMPLATE: + return "unableToSaveTemplate"; + case NS_MSG_UNABLE_TO_SAVE_DRAFT: + return "unableToSaveDraft"; + case NS_MSG_COULDNT_OPEN_FCC_FOLDER: + return "couldntOpenFccFolder"; + case NS_MSG_NO_SENDER: + return "noSender"; + case NS_MSG_NO_RECIPIENTS: + return "noRecipients"; + case NS_MSG_ERROR_WRITING_FILE: + return "errorWritingFile"; + case NS_ERROR_SENDING_FROM_COMMAND: + return "errorSendingFromCommand"; + case NS_ERROR_SENDING_DATA_COMMAND: + return "errorSendingDataCommand"; + case NS_ERROR_SENDING_MESSAGE: + return "errorSendingMessage"; + case NS_ERROR_POST_FAILED: + return "postFailed"; + case NS_ERROR_SMTP_SERVER_ERROR: + return "smtpServerError"; + case NS_MSG_UNABLE_TO_SEND_LATER: + return "unableToSendLater"; + case NS_ERROR_COMMUNICATIONS_ERROR: + return "communicationsError"; + case NS_ERROR_BUT_DONT_SHOW_ALERT: + return "dontShowAlert"; + case NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS: + return "couldNotGetUsersMailAddress2"; + case NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY: + return "couldNotGetSendersIdentity"; + case NS_ERROR_MIME_MPART_ATTACHMENT_ERROR: + return "mimeMpartAttachmentError"; + case NS_ERROR_NNTP_NO_CROSS_POSTING: + return "nntpNoCrossPosting"; + case NS_MSG_ERROR_READING_FILE: + return "errorReadingFile"; + case NS_MSG_ERROR_ATTACHING_FILE: + return "errorAttachingFile"; + case NS_ERROR_SMTP_GREETING: + return "incorrectSmtpGreeting"; + case NS_ERROR_SENDING_RCPT_COMMAND: + return "errorSendingRcptCommand"; + case NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS: + return "startTlsFailed"; + case NS_ERROR_SMTP_PASSWORD_UNDEFINED: + return "smtpPasswordUndefined"; + case NS_ERROR_SMTP_SEND_NOT_ALLOWED: + return "smtpSendNotAllowed"; + case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED: + return "smtpTooManyRecipients"; + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2: + return "smtpPermSizeExceeded2"; + case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER: + return "smtpSendFailedUnknownServer"; + case NS_ERROR_SMTP_SEND_FAILED_REFUSED: + return "smtpSendRequestRefused"; + case NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED: + return "smtpSendInterrupted"; + case NS_ERROR_SMTP_SEND_FAILED_TIMEOUT: + return "smtpSendTimeout"; + case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON: + return "smtpSendFailedUnknownReason"; + case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL: + return "smtpHintAuthEncryptToPlainNoSsl"; + case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL: + return "smtpHintAuthEncryptToPlainSsl"; + case NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT: + return "smtpHintAuthPlainToEncrypt"; + case NS_ERROR_SMTP_AUTH_FAILURE: + return "smtpAuthFailure"; + case NS_ERROR_SMTP_AUTH_GSSAPI: + return "smtpAuthGssapi"; + case NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED: + return "smtpAuthMechNotSupported"; + case NS_ERROR_ILLEGAL_LOCALPART: + return "errorIllegalLocalPart2"; + case NS_ERROR_CLIENTID: + return "smtpClientid"; + case NS_ERROR_CLIENTID_PERMISSION: + return "smtpClientidPermission"; + default: + return "sendFailed"; + } +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif +} diff --git a/comm/mailnews/compose/src/nsComposeStrings.h b/comm/mailnews/compose/src/nsComposeStrings.h new file mode 100644 index 0000000000..3d6a24516f --- /dev/null +++ b/comm/mailnews/compose/src/nsComposeStrings.h @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// clang-format off +/** + String Ids used by mailnews\compose + To Do: Convert the callers to use names instead of ids and then make this file obsolete. + */ + +#ifndef _nsComposeStrings_H__ +#define _nsComposeStrings_H__ + +#include "msgCore.h" + +#define NS_MSG_UNABLE_TO_OPEN_FILE NS_MSG_GENERATE_FAILURE(12500) +#define NS_MSG_UNABLE_TO_OPEN_TMP_FILE NS_MSG_GENERATE_FAILURE(12501) +#define NS_MSG_UNABLE_TO_SAVE_TEMPLATE NS_MSG_GENERATE_FAILURE(12502) +#define NS_MSG_UNABLE_TO_SAVE_DRAFT NS_MSG_GENERATE_FAILURE(12503) +#define NS_MSG_COULDNT_OPEN_FCC_FOLDER NS_MSG_GENERATE_FAILURE(12506) +#define NS_MSG_NO_SENDER NS_MSG_GENERATE_FAILURE(12510) +#define NS_MSG_NO_RECIPIENTS NS_MSG_GENERATE_FAILURE(12511) +#define NS_MSG_ERROR_WRITING_FILE NS_MSG_GENERATE_FAILURE(12512) +#define NS_ERROR_SENDING_FROM_COMMAND NS_MSG_GENERATE_FAILURE(12514) +#define NS_ERROR_SENDING_DATA_COMMAND NS_MSG_GENERATE_FAILURE(12516) +#define NS_ERROR_SENDING_MESSAGE NS_MSG_GENERATE_FAILURE(12517) +#define NS_ERROR_POST_FAILED NS_MSG_GENERATE_FAILURE(12518) +#define NS_ERROR_SMTP_SERVER_ERROR NS_MSG_GENERATE_FAILURE(12524) +#define NS_MSG_UNABLE_TO_SEND_LATER NS_MSG_GENERATE_FAILURE(12525) +#define NS_ERROR_COMMUNICATIONS_ERROR NS_MSG_GENERATE_FAILURE(12526) +#define NS_ERROR_BUT_DONT_SHOW_ALERT NS_MSG_GENERATE_FAILURE(12527) +#define NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS NS_MSG_GENERATE_FAILURE(12529) +#define NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY NS_MSG_GENERATE_FAILURE(12530) +#define NS_ERROR_MIME_MPART_ATTACHMENT_ERROR NS_MSG_GENERATE_FAILURE(12531) + +/* 12554 is taken by NS_ERROR_NNTP_NO_CROSS_POSTING. use 12555 as the next one */ + +// For message sending report +#define NS_MSG_ERROR_READING_FILE NS_MSG_GENERATE_FAILURE(12563) + +#define NS_MSG_ERROR_ATTACHING_FILE NS_MSG_GENERATE_FAILURE(12570) + +#define NS_ERROR_SMTP_GREETING NS_MSG_GENERATE_FAILURE(12572) + +#define NS_ERROR_SENDING_RCPT_COMMAND NS_MSG_GENERATE_FAILURE(12575) + +#define NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS NS_MSG_GENERATE_FAILURE(12582) + +#define NS_ERROR_SMTP_PASSWORD_UNDEFINED NS_MSG_GENERATE_FAILURE(12584) +#define NS_ERROR_SMTP_SEND_NOT_ALLOWED NS_MSG_GENERATE_FAILURE(12585) +#define NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED NS_MSG_GENERATE_FAILURE(12586) +#define NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 NS_MSG_GENERATE_FAILURE(12588) + +#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER NS_MSG_GENERATE_FAILURE(12589) +#define NS_ERROR_SMTP_SEND_FAILED_REFUSED NS_MSG_GENERATE_FAILURE(12590) +#define NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED NS_MSG_GENERATE_FAILURE(12591) +#define NS_ERROR_SMTP_SEND_FAILED_TIMEOUT NS_MSG_GENERATE_FAILURE(12592) +#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON NS_MSG_GENERATE_FAILURE(12593) + +#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL NS_MSG_GENERATE_FAILURE(12594) +#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL NS_MSG_GENERATE_FAILURE(12595) +#define NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT NS_MSG_GENERATE_FAILURE(12596) +#define NS_ERROR_SMTP_AUTH_FAILURE NS_MSG_GENERATE_FAILURE(12597) +#define NS_ERROR_SMTP_AUTH_GSSAPI NS_MSG_GENERATE_FAILURE(12598) +#define NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED NS_MSG_GENERATE_FAILURE(12599) + +#define NS_ERROR_ILLEGAL_LOCALPART NS_MSG_GENERATE_FAILURE(12601) + +#define NS_ERROR_CLIENTID NS_MSG_GENERATE_FAILURE(12610) +#define NS_ERROR_CLIENTID_PERMISSION NS_MSG_GENERATE_FAILURE(12611) + +const char* errorStringNameForErrorCode(nsresult aCode); + +#endif /* _nsComposeStrings_H__ */ + +// clang-format on diff --git a/comm/mailnews/compose/src/nsMsgAttachedFile.cpp b/comm/mailnews/compose/src/nsMsgAttachedFile.cpp new file mode 100644 index 0000000000..cb293ee964 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgAttachedFile.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsMsgAttachedFile.h" + +NS_IMPL_ISUPPORTS(nsMsgAttachedFile, nsIMsgAttachedFile) + +nsMsgAttachedFile::nsMsgAttachedFile() + : m_size(0), + m_unprintableCount(0), + m_highbitCount(0), + m_ctlCount(0), + m_nullCount(0), + m_maxLineLength(0) {} + +nsMsgAttachedFile::~nsMsgAttachedFile() {} + +NS_IMETHODIMP nsMsgAttachedFile::GetOrigUrl(nsIURI** aOrigUrl) { + NS_ENSURE_ARG_POINTER(aOrigUrl); + NS_IF_ADDREF(*aOrigUrl = m_origUrl); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetOrigUrl(nsIURI* aOrigUrl) { + m_origUrl = aOrigUrl; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetTmpFile(nsIFile** aTmpFile) { + NS_ENSURE_ARG_POINTER(aTmpFile); + NS_IF_ADDREF(*aTmpFile = m_tmpFile); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetTmpFile(nsIFile* aTmpFile) { + m_tmpFile = aTmpFile; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetType(nsACString& aType) { + aType = m_type; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetType(const nsACString& aType) { + m_type = aType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetEncoding(nsACString& aEncoding) { + aEncoding = m_encoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetEncoding(const nsACString& aEncoding) { + m_encoding = aEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetDescription(nsACString& aDescription) { + aDescription = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetDescription( + const nsACString& aDescription) { + m_description = aDescription; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetCloudPartInfo(nsACString& aCloudPartInfo) { + aCloudPartInfo = m_cloudPartInfo; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetCloudPartInfo( + const nsACString& aCloudPartInfo) { + m_cloudPartInfo = aCloudPartInfo; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetXMacType(nsACString& aXMacType) { + aXMacType = m_xMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetXMacType(const nsACString& aXMacType) { + m_xMacType = aXMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetXMacCreator(nsACString& aXMacCreator) { + aXMacCreator = m_xMacCreator; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetXMacCreator( + const nsACString& aXMacCreator) { + m_xMacCreator = aXMacCreator; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetRealName(nsACString& aRealName) { + aRealName = m_realName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetRealName(const nsACString& aRealName) { + m_realName = aRealName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetSize(uint32_t* aSize) { + NS_ENSURE_ARG_POINTER(aSize); + *aSize = m_size; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetSize(uint32_t aSize) { + m_size = aSize; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetUnprintableCount( + uint32_t* aUnprintableCount) { + NS_ENSURE_ARG_POINTER(aUnprintableCount); + *aUnprintableCount = m_unprintableCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetUnprintableCount( + uint32_t aUnprintableCount) { + m_unprintableCount = aUnprintableCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetHighbitCount(uint32_t* aHighbitCount) { + NS_ENSURE_ARG_POINTER(aHighbitCount); + *aHighbitCount = m_highbitCount; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachedFile::SetHighbitCount(uint32_t aHighbitCount) { + m_highbitCount = aHighbitCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetCtlCount(uint32_t* aCtlCount) { + NS_ENSURE_ARG_POINTER(aCtlCount); + *aCtlCount = m_ctlCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetCtlCount(uint32_t aCtlCount) { + m_ctlCount = aCtlCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetNullCount(uint32_t* aNullCount) { + NS_ENSURE_ARG_POINTER(aNullCount); + *aNullCount = m_nullCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetNullCount(uint32_t aNullCount) { + m_nullCount = aNullCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetMaxLineLength(uint32_t* aMaxLineLength) { + NS_ENSURE_ARG_POINTER(aMaxLineLength); + *aMaxLineLength = m_maxLineLength; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetMaxLineLength(uint32_t aMaxLineLength) { + m_maxLineLength = aMaxLineLength; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgAttachedFile.h b/comm/mailnews/compose/src/nsMsgAttachedFile.h new file mode 100644 index 0000000000..5f68731927 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgAttachedFile.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgAttachedFile_H_ +#define _nsMsgAttachedFile_H_ + +#include "nsCOMPtr.h" +#include "nsIMsgSend.h" +#include "nsMsgAttachmentData.h" + +#endif /* _nsMsgAttachedFile_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgAttachment.cpp b/comm/mailnews/compose/src/nsMsgAttachment.cpp new file mode 100644 index 0000000000..55edd348ca --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgAttachment.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgAttachment.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsMsgCompUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgAttachment, nsIMsgAttachment) + +nsMsgAttachment::nsMsgAttachment() { + mTemporary = false; + mSendViaCloud = false; + mSize = -1; +} + +nsMsgAttachment::~nsMsgAttachment() { + MOZ_LOG(Compose, mozilla::LogLevel::Debug, ("~nsMsgAttachment()")); +} + +/* attribute wstring name; */ +NS_IMETHODIMP nsMsgAttachment::GetName(nsAString& aName) { + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetName(const nsAString& aName) { + mName = aName; + return NS_OK; +} + +/* attribute string url; */ +NS_IMETHODIMP nsMsgAttachment::GetUrl(nsACString& aUrl) { + aUrl = mUrl; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetUrl(const nsACString& aUrl) { + mUrl = aUrl; + return NS_OK; +} + +/* attribute string msgUri; */ +NS_IMETHODIMP nsMsgAttachment::GetMsgUri(nsACString& aMsgUri) { + aMsgUri = mMsgUri; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetMsgUri(const nsACString& aMsgUri) { + mMsgUri = aMsgUri; + return NS_OK; +} + +/* attribute string urlCharset; */ +NS_IMETHODIMP nsMsgAttachment::GetUrlCharset(nsACString& aUrlCharset) { + aUrlCharset = mUrlCharset; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetUrlCharset(const nsACString& aUrlCharset) { + mUrlCharset = aUrlCharset; + return NS_OK; +} + +/* attribute boolean temporary; */ +NS_IMETHODIMP nsMsgAttachment::GetTemporary(bool* aTemporary) { + NS_ENSURE_ARG_POINTER(aTemporary); + + *aTemporary = mTemporary; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetTemporary(bool aTemporary) { + mTemporary = aTemporary; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetSendViaCloud(bool* aSendViaCloud) { + NS_ENSURE_ARG_POINTER(aSendViaCloud); + + *aSendViaCloud = mSendViaCloud; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetSendViaCloud(bool aSendViaCloud) { + mSendViaCloud = aSendViaCloud; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetHtmlAnnotation( + const nsACString& aAnnotation) { + mHtmlAnnotation = aAnnotation; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetHtmlAnnotation(nsACString& aAnnotation) { + aAnnotation = mHtmlAnnotation; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAttachment::SetCloudFileAccountKey( + const nsACString& aCloudFileAccountKey) { + mCloudFileAccountKey = aCloudFileAccountKey; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAttachment::GetCloudFileAccountKey(nsACString& aCloudFileAccountKey) { + aCloudFileAccountKey = mCloudFileAccountKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetCloudPartHeaderData( + nsACString& aCloudPartHeaderData) { + aCloudPartHeaderData = mCloudPartHeaderData; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetCloudPartHeaderData( + const nsACString& aCloudPartHeaderData) { + mCloudPartHeaderData = aCloudPartHeaderData; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentLocation( + nsACString& aContentLocation) { + aContentLocation = mContentLocation; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetContentLocation( + const nsACString& aContentLocation) { + mContentLocation = aContentLocation; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentType(char** aContentType) { + NS_ENSURE_ARG_POINTER(aContentType); + + *aContentType = ToNewCString(mContentType); + return (*aContentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} + +NS_IMETHODIMP nsMsgAttachment::SetContentType(const char* aContentType) { + mContentType = aContentType; + // a full content type could also contains parameters but we need to + // keep only the content type alone. Therefore we need to cleanup it. + int32_t offset = mContentType.FindChar(';'); + if (offset >= 0) mContentType.SetLength(offset); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentTypeParam(char** aContentTypeParam) { + NS_ENSURE_ARG_POINTER(aContentTypeParam); + + *aContentTypeParam = ToNewCString(mContentTypeParam); + return (*aContentTypeParam ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} + +NS_IMETHODIMP nsMsgAttachment::SetContentTypeParam( + const char* aContentTypeParam) { + if (aContentTypeParam) + while (*aContentTypeParam == ';' || *aContentTypeParam == ' ') + aContentTypeParam++; + mContentTypeParam = aContentTypeParam; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentId(nsACString& aContentId) { + aContentId = mContentId; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetContentId(const nsACString& aContentId) { + mContentId = aContentId; + return NS_OK; +} + +/* attribute string charset; */ +NS_IMETHODIMP nsMsgAttachment::GetCharset(char** aCharset) { + NS_ENSURE_ARG_POINTER(aCharset); + + *aCharset = ToNewCString(mCharset); + return (*aCharset ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} +NS_IMETHODIMP nsMsgAttachment::SetCharset(const char* aCharset) { + mCharset = aCharset; + return NS_OK; +} + +/* attribute string macType; */ +NS_IMETHODIMP nsMsgAttachment::GetMacType(char** aMacType) { + NS_ENSURE_ARG_POINTER(aMacType); + + *aMacType = ToNewCString(mMacType); + return (*aMacType ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} +NS_IMETHODIMP nsMsgAttachment::SetMacType(const char* aMacType) { + mMacType = aMacType; + return NS_OK; +} + +/* attribute string macCreator; */ +NS_IMETHODIMP nsMsgAttachment::GetMacCreator(char** aMacCreator) { + NS_ENSURE_ARG_POINTER(aMacCreator); + + *aMacCreator = ToNewCString(mMacCreator); + return (*aMacCreator ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} +NS_IMETHODIMP nsMsgAttachment::SetMacCreator(const char* aMacCreator) { + mMacCreator = aMacCreator; + return NS_OK; +} + +/* attribute int64_t size; */ +NS_IMETHODIMP nsMsgAttachment::GetSize(int64_t* aSize) { + NS_ENSURE_ARG_POINTER(aSize); + + *aSize = mSize; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetSize(int64_t aSize) { + mSize = aSize; + return NS_OK; +} + +/* boolean equalsUrl (in nsIMsgAttachment attachment); */ +NS_IMETHODIMP nsMsgAttachment::EqualsUrl(nsIMsgAttachment* attachment, + bool* _retval) { + NS_ENSURE_ARG_POINTER(attachment); + NS_ENSURE_ARG_POINTER(_retval); + + nsAutoCString url; + attachment->GetUrl(url); + + *_retval = mUrl.Equals(url); + return NS_OK; +} + +nsresult nsMsgAttachment::DeleteAttachment() { + nsresult rv; + bool isAFile = false; + + nsCOMPtr<nsIFile> urlFile; + rv = NS_GetFileFromURLSpec(mUrl, getter_AddRefs(urlFile)); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't nsIFile from URL string"); + if (NS_SUCCEEDED(rv)) { + bool bExists = false; + rv = urlFile->Exists(&bExists); + NS_ASSERTION(NS_SUCCEEDED(rv), "Exists() call failed!"); + if (NS_SUCCEEDED(rv) && bExists) { + rv = urlFile->IsFile(&isAFile); + NS_ASSERTION(NS_SUCCEEDED(rv), "IsFile() call failed!"); + } + } + + // remove it if it's a valid file + if (isAFile) rv = urlFile->Remove(false); + + return rv; +} diff --git a/comm/mailnews/compose/src/nsMsgAttachment.h b/comm/mailnews/compose/src/nsMsgAttachment.h new file mode 100644 index 0000000000..d9e0de696e --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgAttachment.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgAttachment_H_ +#define _nsMsgAttachment_H_ + +#include "nsIMsgAttachment.h" +#include "nsString.h" + +class nsMsgAttachment : public nsIMsgAttachment { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGATTACHMENT + + nsMsgAttachment(); + + private: + virtual ~nsMsgAttachment(); + nsresult DeleteAttachment(); + + nsString mName; + nsCString mUrl; + nsCString mMsgUri; + nsCString mUrlCharset; + bool mTemporary; + bool mSendViaCloud; + nsCString mCloudFileAccountKey; + nsCString mCloudPartHeaderData; + nsCString mContentLocation; + nsCString mContentType; + nsCString mContentTypeParam; + nsCString mContentId; + nsCString mCharset; + nsCString mMacType; + nsCString mMacCreator; + nsCString mHtmlAnnotation; + int64_t mSize; +}; + +#endif /* _nsMsgAttachment_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgAttachmentData.cpp b/comm/mailnews/compose/src/nsMsgAttachmentData.cpp new file mode 100644 index 0000000000..c32b31d0c1 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgAttachmentData.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsMsgAttachmentData.h" + +NS_IMPL_ISUPPORTS(nsMsgAttachmentData, nsIMsgAttachmentData) + +nsMsgAttachmentData::nsMsgAttachmentData() + : m_size(0), + m_sizeExternalStr("-1"), + m_isExternalAttachment(false), + m_isExternalLinkAttachment(false), + m_isDownloaded(false), + m_hasFilename(false), + m_displayableInline(false) {} + +nsMsgAttachmentData::~nsMsgAttachmentData() {} + +NS_IMETHODIMP nsMsgAttachmentData::GetUrl(nsIURI** aUrl) { + NS_ENSURE_ARG_POINTER(aUrl); + NS_IF_ADDREF(*aUrl = m_url); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetUrl(nsIURI* aUrl) { + m_url = aUrl; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetDesiredType(nsACString& aDesiredType) { + aDesiredType = m_desiredType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetDesiredType( + const nsACString& aDesiredType) { + m_desiredType = aDesiredType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetRealType(nsACString& aRealType) { + aRealType = m_realType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetRealType(const nsACString& aRealType) { + m_realType = aRealType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetRealEncoding(nsACString& aRealEncoding) { + aRealEncoding = m_realEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetRealEncoding( + const nsACString& aRealEncoding) { + m_realEncoding = aRealEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetRealName(nsACString& aRealName) { + aRealName = m_realName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetRealName(const nsACString& aRealName) { + m_realName = aRealName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetDescription(nsACString& aDescription) { + aDescription = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetDescription( + const nsACString& aDescription) { + m_description = aDescription; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetXMacType(nsACString& aXMacType) { + aXMacType = m_xMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetXMacType(const nsACString& aXMacType) { + m_xMacType = aXMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetXMacCreator(nsACString& aXMacCreator) { + aXMacCreator = m_xMacCreator; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetXMacCreator( + const nsACString& aXMacCreator) { + m_xMacCreator = aXMacCreator; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgAttachmentData.h b/comm/mailnews/compose/src/nsMsgAttachmentData.h new file mode 100644 index 0000000000..763a4377e9 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgAttachmentData.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __MSGATTACHMENTDATA_H__ +#define __MSGATTACHMENTDATA_H__ + +#include "nsIURL.h" +#include "nsString.h" +#include "nsIMsgSend.h" + +// Attachment file/URL structures - we're letting libmime use this directly +class nsMsgAttachmentData final : public nsIMsgAttachmentData { + public: + NS_DECL_NSIMSGATTACHMENTDATA + NS_DECL_ISUPPORTS + + nsMsgAttachmentData(); + virtual ~nsMsgAttachmentData(); + + nsCOMPtr<nsIURI> m_url; // The URL to attach. + + nsCString m_desiredType; // The type to which this document should be + // converted. Legal values are NULL, TEXT_PLAIN + // and APPLICATION_POSTSCRIPT (which are macros + // defined in net.h); other values are ignored. + + nsCString + m_realType; // The type of the URL if known, otherwise NULL. For example, + // if you were attaching a temp file which was known to + // contain HTML data, you would pass in TEXT_HTML as the + // real_type, to override whatever type the name of the tmp + // file might otherwise indicate. + + nsCString m_realEncoding; // Goes along with real_type + + nsCString + m_realName; // The original name of this document, which will eventually + // show up in the Content-Disposition header. For example, if + // you had copied a document to a tmp file, this would be the + // original, human-readable name of the document. + + nsCString m_description; // If you put a string here, it will show up as the + // Content-Description header. This can be any + // explanatory text; it's not a file name. + + nsCString m_disposition; // The Content-Disposition header (if any). a + // nsMsgAttachmentData can very well have + // Content-Disposition: inline value, instead of + // "attachment". + nsCString m_cloudPartInfo; // For X-Mozilla-Cloud-Part header, if any + + // Mac-specific data that should show up as optional parameters + // to the content-type header. + nsCString m_xMacType; + nsCString m_xMacCreator; + + int32_t m_size; // The size of the attachment. May be 0. + nsCString + m_sizeExternalStr; // The reported size of an external attachment. + // Originally set at "-1" to mean an unknown value. + bool m_isExternalAttachment; // Flag for determining if the attachment is + // external + bool m_isExternalLinkAttachment; // Flag for determining if the attachment is + // external and an http link. + bool m_isDownloaded; // Flag for determining if the attachment has already + // been downloaded + bool m_hasFilename; // Tells whether the name is provided by us or if it's a + // Part 1.2-like attachment + bool m_displayableInline; // Tells whether the attachment could be displayed + // inline +}; + +class nsMsgAttachedFile final : public nsIMsgAttachedFile { + public: + NS_DECL_NSIMSGATTACHEDFILE + NS_DECL_ISUPPORTS + + nsMsgAttachedFile(); + virtual ~nsMsgAttachedFile(); + + nsCOMPtr<nsIURI> m_origUrl; // Where it came from on the network (or even + // elsewhere on the local disk.) + + nsCOMPtr<nsIFile> m_tmpFile; // The tmp file in which the (possibly + // converted) data now resides. + + nsCString m_type; // The type of the data in file_name (not necessarily the + // same as the type of orig_url.) + + nsCString + m_encoding; // Likewise, the encoding of the tmp file. This will be set + // only if the original document had an encoding already; we + // don't do base64 encoding and so forth until it's time to + // assemble a full MIME message of all parts. + + nsCString m_description; // For Content-Description header + nsCString m_cloudPartInfo; // For X-Mozilla-Cloud-Part header, if any + nsCString m_xMacType; // mac-specific info + nsCString m_xMacCreator; // mac-specific info + nsCString m_realName; // The real name of the file. + + // Some statistics about the data that was written to the file, so that when + // it comes time to compose a MIME message, we can make an informed decision + // about what Content-Transfer-Encoding would be best for this attachment. + // (If it's encoded already, we ignore this information and ship it as-is.) + uint32_t m_size; + uint32_t m_unprintableCount; + uint32_t m_highbitCount; + uint32_t m_ctlCount; + uint32_t m_nullCount; + uint32_t m_maxLineLength; +}; + +#undef MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING +#ifdef MOZ_IS_DESTRUCTIBLE +# define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \ + static_assert( \ + !MOZ_IS_DESTRUCTIBLE(X) || \ + mozilla::IsSame<X, nsMsgAttachmentData>::value || \ + mozilla::IsSame<X, nsMsgAttachedFile>::value, \ + "Reference-counted class " #X \ + " should not have a public destructor. " \ + "Try to make this class's destructor non-public. If that is really " \ + "not possible, you can whitelist this class by providing a " \ + "HasDangerousPublicDestructor specialization for it."); +#else +# define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) +#endif +#endif diff --git a/comm/mailnews/compose/src/nsMsgCompFields.cpp b/comm/mailnews/compose/src/nsMsgCompFields.cpp new file mode 100644 index 0000000000..9bfb66a253 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCompFields.cpp @@ -0,0 +1,559 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgCompose.h" +#include "nsMsgCompFields.h" +#include "nsMsgI18N.h" +#include "nsMsgCompUtils.h" +#include "nsMsgUtils.h" +#include "prmem.h" +#include "nsIFileChannel.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgMdnGenerator.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +struct HeaderInfo { + /// Header name + const char* mName; + /// If true, nsMsgCompFields should reflect the raw header value instead of + /// the unstructured header value. + bool mStructured; +}; + +// This is a mapping of the m_headers local set to the actual header name we +// store on the structured header object. +static HeaderInfo kHeaders[] = { + {"From", true}, + {"Reply-To", true}, + {"To", true}, + {"Cc", true}, + {"Bcc", true}, + {nullptr, false}, // FCC + {nullptr, false}, // FCC2 + {"Newsgroups", true}, + {"Followup-To", true}, + {"Subject", false}, + {"Organization", false}, + {"References", true}, + {"X-Mozilla-News-Host", false}, + {"X-Priority", false}, + {nullptr, false}, // CHARACTER_SET + {"Message-Id", true}, + {"X-Template", true}, + {nullptr, false}, // DRAFT_ID + {nullptr, false}, // TEMPLATE_ID + {"Content-Language", true}, + {nullptr, false} // CREATOR IDENTITY KEY +}; + +static_assert( + MOZ_ARRAY_LENGTH(kHeaders) == nsMsgCompFields::MSG_MAX_HEADERS, + "These two arrays need to be kept in sync or bad things will happen!"); + +NS_IMPL_ISUPPORTS(nsMsgCompFields, nsIMsgCompFields, msgIStructuredHeaders, + msgIWritableStructuredHeaders) + +nsMsgCompFields::nsMsgCompFields() + : mStructuredHeaders(do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID)) { + m_body.Truncate(); + + m_attachVCard = false; + m_forcePlainText = false; + m_useMultipartAlternative = false; + m_returnReceipt = false; + m_receiptHeaderType = nsIMsgMdnGenerator::eDntType; + m_DSN = false; + m_bodyIsAsciiOnly = false; + m_forceMsgEncoding = false; + m_needToCheckCharset = true; + m_attachmentReminder = false; + m_deliveryFormat = nsIMsgCompSendFormat::Unset; +} + +nsMsgCompFields::~nsMsgCompFields() { + MOZ_LOG(Compose, mozilla::LogLevel::Debug, ("~nsMsgCompFields()")); +} + +nsresult nsMsgCompFields::SetAsciiHeader(MsgHeaderID header, + const char* value) { + NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS, + "Invalid message header index!"); + + // If we are storing this on the structured header object, we need to set the + // value on that object as well. Note that the value may be null, which we'll + // take as an attempt to delete the header. + const char* headerName = kHeaders[header].mName; + if (headerName) { + if (!value || !*value) return mStructuredHeaders->DeleteHeader(headerName); + + return mStructuredHeaders->SetRawHeader(headerName, + nsDependentCString(value)); + } + + // Not on the structurd header object, so save it locally. + m_headers[header] = value; + + return NS_OK; +} + +const char* nsMsgCompFields::GetAsciiHeader(MsgHeaderID header) { + NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS, + "Invalid message header index!"); + + const char* headerName = kHeaders[header].mName; + if (headerName) { + // We may be out of sync with the structured header object. Retrieve the + // header value. + if (kHeaders[header].mStructured) { + mStructuredHeaders->GetRawHeader(headerName, m_headers[header]); + } else { + nsString value; + mStructuredHeaders->GetUnstructuredHeader(headerName, value); + CopyUTF16toUTF8(value, m_headers[header]); + } + } + + return m_headers[header].get(); +} + +nsresult nsMsgCompFields::SetUnicodeHeader(MsgHeaderID header, + const nsAString& value) { + return SetAsciiHeader(header, NS_ConvertUTF16toUTF8(value).get()); +} + +nsresult nsMsgCompFields::GetUnicodeHeader(MsgHeaderID header, + nsAString& aResult) { + CopyUTF8toUTF16(nsDependentCString(GetAsciiHeader(header)), aResult); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetFrom(const nsAString& value) { + return SetUnicodeHeader(MSG_FROM_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetFrom(nsAString& _retval) { + return GetUnicodeHeader(MSG_FROM_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetReplyTo(const nsAString& value) { + return SetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetReplyTo(nsAString& _retval) { + return GetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetTo(const nsAString& value) { + return SetUnicodeHeader(MSG_TO_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetTo(nsAString& _retval) { + return GetUnicodeHeader(MSG_TO_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetCc(const nsAString& value) { + return SetUnicodeHeader(MSG_CC_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetCc(nsAString& _retval) { + return GetUnicodeHeader(MSG_CC_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetBcc(const nsAString& value) { + return SetUnicodeHeader(MSG_BCC_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetBcc(nsAString& _retval) { + return GetUnicodeHeader(MSG_BCC_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetFcc(const nsAString& value) { + return SetUnicodeHeader(MSG_FCC_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetFcc(nsAString& _retval) { + return GetUnicodeHeader(MSG_FCC_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetFcc2(const nsAString& value) { + return SetUnicodeHeader(MSG_FCC2_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetFcc2(nsAString& _retval) { + return GetUnicodeHeader(MSG_FCC2_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetNewsgroups(const nsAString& aValue) { + return SetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aValue); +} + +NS_IMETHODIMP nsMsgCompFields::GetNewsgroups(nsAString& aGroup) { + return GetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aGroup); +} + +NS_IMETHODIMP nsMsgCompFields::SetFollowupTo(const nsAString& aValue) { + return SetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue); +} + +NS_IMETHODIMP nsMsgCompFields::GetFollowupTo(nsAString& _retval) { + return GetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::GetHasRecipients(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = NS_SUCCEEDED(mime_sanity_check_fields_recipients( + GetTo(), GetCc(), GetBcc(), GetNewsgroups())); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetCreatorIdentityKey(const char* value) { + return SetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetCreatorIdentityKey(char** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = strdup(GetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetSubject(const nsAString& value) { + return SetUnicodeHeader(MSG_SUBJECT_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetSubject(nsAString& _retval) { + return GetUnicodeHeader(MSG_SUBJECT_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetOrganization(const nsAString& value) { + return SetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetOrganization(nsAString& _retval) { + return GetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetReferences(const char* value) { + return SetAsciiHeader(MSG_REFERENCES_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetReferences(char** _retval) { + *_retval = strdup(GetAsciiHeader(MSG_REFERENCES_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetNewspostUrl(const char* value) { + return SetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetNewspostUrl(char** _retval) { + *_retval = strdup(GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetPriority(const char* value) { + return SetAsciiHeader(MSG_PRIORITY_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetPriority(char** _retval) { + *_retval = strdup(GetAsciiHeader(MSG_PRIORITY_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetMessageId(const char* value) { + return SetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetMessageId(char** _retval) { + *_retval = strdup(GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetTemplateName(const nsAString& value) { + return SetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetTemplateName(nsAString& _retval) { + return GetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetDraftId(const nsACString& value) { + return SetAsciiHeader(MSG_DRAFT_ID_HEADER_ID, + PromiseFlatCString(value).get()); +} + +NS_IMETHODIMP nsMsgCompFields::GetDraftId(nsACString& _retval) { + _retval.Assign(GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetTemplateId(const nsACString& value) { + return SetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID, + PromiseFlatCString(value).get()); +} + +NS_IMETHODIMP nsMsgCompFields::GetTemplateId(nsACString& _retval) { + _retval.Assign(GetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetReturnReceipt(bool value) { + m_returnReceipt = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetReturnReceipt(bool* _retval) { + *_retval = m_returnReceipt; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetReceiptHeaderType(int32_t value) { + m_receiptHeaderType = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetReceiptHeaderType(int32_t* _retval) { + *_retval = m_receiptHeaderType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetDSN(bool value) { + m_DSN = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetDSN(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_DSN; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetAttachVCard(bool value) { + m_attachVCard = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetAttachVCard(bool* _retval) { + *_retval = m_attachVCard; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetAttachmentReminder(bool* _retval) { + *_retval = m_attachmentReminder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetAttachmentReminder(bool value) { + m_attachmentReminder = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetDeliveryFormat(int32_t value) { + switch (value) { + case nsIMsgCompSendFormat::Auto: + case nsIMsgCompSendFormat::PlainText: + case nsIMsgCompSendFormat::HTML: + case nsIMsgCompSendFormat::Both: + m_deliveryFormat = value; + break; + case nsIMsgCompSendFormat::Unset: + default: + m_deliveryFormat = nsIMsgCompSendFormat::Unset; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetDeliveryFormat(int32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_deliveryFormat; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetContentLanguage(const char* value) { + return SetAsciiHeader(MSG_CONTENT_LANGUAGE_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetContentLanguage(char** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = strdup(GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetForcePlainText(bool value) { + m_forcePlainText = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetForcePlainText(bool* _retval) { + *_retval = m_forcePlainText; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetForceMsgEncoding(bool value) { + m_forceMsgEncoding = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetForceMsgEncoding(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_forceMsgEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetUseMultipartAlternative(bool value) { + m_useMultipartAlternative = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetUseMultipartAlternative(bool* _retval) { + *_retval = m_useMultipartAlternative; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetBodyIsAsciiOnly(bool value) { + m_bodyIsAsciiOnly = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetBodyIsAsciiOnly(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = m_bodyIsAsciiOnly; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetBody(const nsAString& value) { + m_body = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetBody(nsAString& _retval) { + _retval = m_body; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetAttachments( + nsTArray<RefPtr<nsIMsgAttachment>>& attachments) { + attachments = m_attachments.Clone(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::AddAttachment(nsIMsgAttachment* attachment) { + // Don't add the same attachment twice. + for (nsIMsgAttachment* a : m_attachments) { + bool sameUrl; + a->EqualsUrl(attachment, &sameUrl); + if (sameUrl) return NS_OK; + } + m_attachments.AppendElement(attachment); + return NS_OK; +} + +/* void removeAttachment (in nsIMsgAttachment attachment); */ +NS_IMETHODIMP nsMsgCompFields::RemoveAttachment(nsIMsgAttachment* attachment) { + for (uint32_t i = 0; i < m_attachments.Length(); i++) { + bool sameUrl; + m_attachments[i]->EqualsUrl(attachment, &sameUrl); + if (sameUrl) { + m_attachments.RemoveElementAt(i); + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetOtherHeaders( + const nsTArray<nsString>& headers) { + m_otherHeaders = headers.Clone(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetOtherHeaders(nsTArray<nsString>& headers) { + headers = m_otherHeaders.Clone(); + return NS_OK; +} + +/* void removeAttachments (); */ +NS_IMETHODIMP nsMsgCompFields::RemoveAttachments() { + m_attachments.Clear(); + return NS_OK; +} + +// This method is called during the creation of a new window. +NS_IMETHODIMP +nsMsgCompFields::SplitRecipients(const nsAString& aRecipients, + bool aEmailAddressOnly, + nsTArray<nsString>& aResult) { + nsCOMArray<msgIAddressObject> header(EncodedHeaderW(aRecipients)); + if (aEmailAddressOnly) + ExtractEmails(header, aResult); + else + ExtractDisplayAddresses(header, aResult); + + return NS_OK; +} + +// This method is called during the sending of message from +// nsMsgCompose::CheckAndPopulateRecipients() +nsresult nsMsgCompFields::SplitRecipientsEx(const nsAString& recipients, + nsTArray<nsMsgRecipient>& aResult) { + nsTArray<nsString> names, addresses; + ExtractAllAddresses(EncodedHeaderW(recipients), names, addresses); + + uint32_t numAddresses = names.Length(); + for (uint32_t i = 0; i < numAddresses; ++i) { + nsMsgRecipient msgRecipient; + msgRecipient.mEmail = addresses[i]; + msgRecipient.mName = names[i]; + aResult.AppendElement(msgRecipient); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::ConvertBodyToPlainText() { + nsresult rv = NS_OK; + + if (!m_body.IsEmpty()) { + if (NS_SUCCEEDED(rv)) { + bool flowed, formatted; + GetSerialiserFlags(&flowed, &formatted); + rv = ConvertBufToPlainText(m_body, flowed, formatted, true); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgCompFields::GetComposeSecure( + nsIMsgComposeSecure** aComposeSecure) { + NS_ENSURE_ARG_POINTER(aComposeSecure); + NS_IF_ADDREF(*aComposeSecure = mSecureCompFields); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetComposeSecure( + nsIMsgComposeSecure* aComposeSecure) { + mSecureCompFields = aComposeSecure; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetNeedToCheckCharset(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_needToCheckCharset; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetNeedToCheckCharset(bool aCheck) { + m_needToCheckCharset = aCheck; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgCompFields.h b/comm/mailnews/compose/src/nsMsgCompFields.h new file mode 100644 index 0000000000..312d19192c --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCompFields.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MsgCompFields_H_ +#define _MsgCompFields_H_ + +#include "nsIMsgCompFields.h" +#include "msgCore.h" +#include "nsIAbCard.h" +#include "nsIAbDirectory.h" +#include "nsTArray.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIMsgComposeSecure.h" + +struct nsMsgRecipient { + nsString mName; + nsString mEmail; + nsCOMPtr<nsIAbCard> mCard; + nsCOMPtr<nsIAbDirectory> mDirectory; +}; + +/* Note that all the "Get" methods never return NULL (except in case of serious + error, like an illegal parameter); rather, they return "" if things were set + to NULL. This makes it real handy for the callers. */ + +class nsMsgCompFields : public nsIMsgCompFields { + public: + nsMsgCompFields(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_MSGISTRUCTUREDHEADERS(mStructuredHeaders->) + NS_FORWARD_MSGIWRITABLESTRUCTUREDHEADERS(mStructuredHeaders->) + NS_DECL_NSIMSGCOMPFIELDS + + typedef enum MsgHeaderID { + MSG_FROM_HEADER_ID = 0, + MSG_REPLY_TO_HEADER_ID, + MSG_TO_HEADER_ID, + MSG_CC_HEADER_ID, + MSG_BCC_HEADER_ID, + MSG_FCC_HEADER_ID, + MSG_FCC2_HEADER_ID, + MSG_NEWSGROUPS_HEADER_ID, + MSG_FOLLOWUP_TO_HEADER_ID, + MSG_SUBJECT_HEADER_ID, + MSG_ORGANIZATION_HEADER_ID, + MSG_REFERENCES_HEADER_ID, + MSG_NEWSPOSTURL_HEADER_ID, + MSG_PRIORITY_HEADER_ID, + MSG_CHARACTER_SET_HEADER_ID, + MSG_MESSAGE_ID_HEADER_ID, + MSG_X_TEMPLATE_HEADER_ID, + MSG_DRAFT_ID_HEADER_ID, + MSG_TEMPLATE_ID_HEADER_ID, + MSG_CONTENT_LANGUAGE_ID, + MSG_CREATOR_IDENTITY_KEY_ID, + + MSG_MAX_HEADERS // Must be the last one. + } MsgHeaderID; + + nsresult SetAsciiHeader(MsgHeaderID header, const char* value); + const char* GetAsciiHeader( + MsgHeaderID header); // just return the address of the internal header + // variable, don't dispose it + + nsresult SetUnicodeHeader(MsgHeaderID header, const nsAString& value); + nsresult GetUnicodeHeader(MsgHeaderID header, nsAString& _retval); + + /* Convenience routines to get and set header's value... + + IMPORTANT: + all routines const char* GetXxx(void) will return a pointer to the header, + please don't free it. + */ + + nsresult SetFrom(const char* value) { + return SetAsciiHeader(MSG_FROM_HEADER_ID, value); + } + const char* GetFrom(void) { return GetAsciiHeader(MSG_FROM_HEADER_ID); } + + nsresult SetReplyTo(const char* value) { + return SetAsciiHeader(MSG_REPLY_TO_HEADER_ID, value); + } + const char* GetReplyTo() { return GetAsciiHeader(MSG_REPLY_TO_HEADER_ID); } + + nsresult SetTo(const char* value) { + return SetAsciiHeader(MSG_TO_HEADER_ID, value); + } + const char* GetTo() { return GetAsciiHeader(MSG_TO_HEADER_ID); } + + nsresult SetCc(const char* value) { + return SetAsciiHeader(MSG_CC_HEADER_ID, value); + } + const char* GetCc() { return GetAsciiHeader(MSG_CC_HEADER_ID); } + + nsresult SetBcc(const char* value) { + return SetAsciiHeader(MSG_BCC_HEADER_ID, value); + } + const char* GetBcc() { return GetAsciiHeader(MSG_BCC_HEADER_ID); } + + nsresult SetFcc(const char* value) { + return SetAsciiHeader(MSG_FCC_HEADER_ID, value); + } + const char* GetFcc() { return GetAsciiHeader(MSG_FCC_HEADER_ID); } + + nsresult SetFcc2(const char* value) { + return SetAsciiHeader(MSG_FCC2_HEADER_ID, value); + } + const char* GetFcc2() { return GetAsciiHeader(MSG_FCC2_HEADER_ID); } + + nsresult SetNewsgroups(const char* aValue) { + return SetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID, aValue); + } + const char* GetNewsgroups() { + return GetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID); + } + + nsresult SetFollowupTo(const char* aValue) { + return SetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue); + } + const char* GetFollowupTo() { + return GetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID); + } + + nsresult SetSubject(const char* value) { + return SetAsciiHeader(MSG_SUBJECT_HEADER_ID, value); + } + const char* GetSubject() { return GetAsciiHeader(MSG_SUBJECT_HEADER_ID); } + + nsresult SetOrganization(const char* value) { + return SetAsciiHeader(MSG_ORGANIZATION_HEADER_ID, value); + } + const char* GetOrganization() { + return GetAsciiHeader(MSG_ORGANIZATION_HEADER_ID); + } + + const char* GetReferences() { + return GetAsciiHeader(MSG_REFERENCES_HEADER_ID); + } + + const char* GetNewspostUrl() { + return GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID); + } + + const char* GetPriority() { return GetAsciiHeader(MSG_PRIORITY_HEADER_ID); } + + const char* GetCharacterSet() { + return GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID); + } + + const char* GetMessageId() { + return GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID); + } + + nsresult SetTemplateName(const char* value) { + return SetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID, value); + } + const char* GetTemplateName() { + return GetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID); + } + + const char* GetDraftId() { return GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID); } + const char* GetTemplateId() { + return GetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID); + } + + const char* GetContentLanguage() { + return GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID); + } + + bool GetReturnReceipt() { return m_returnReceipt; } + bool GetDSN() { return m_DSN; } + bool GetAttachVCard() { return m_attachVCard; } + bool GetAttachmentReminder() { return m_attachmentReminder; } + int32_t GetDeliveryFormat() { return m_deliveryFormat; } + bool GetForcePlainText() { return m_forcePlainText; } + bool GetUseMultipartAlternative() { return m_useMultipartAlternative; } + bool GetBodyIsAsciiOnly() { return m_bodyIsAsciiOnly; } + bool GetForceMsgEncoding() { return m_forceMsgEncoding; } + + nsresult SplitRecipientsEx(const nsAString& recipients, + nsTArray<nsMsgRecipient>& aResult); + + protected: + virtual ~nsMsgCompFields(); + nsCString m_headers[MSG_MAX_HEADERS]; + nsString m_body; + nsTArray<RefPtr<nsIMsgAttachment>> m_attachments; + nsTArray<nsString> m_otherHeaders; + bool m_attachVCard; + bool m_attachmentReminder; + int32_t m_deliveryFormat; + bool m_forcePlainText; + bool m_useMultipartAlternative; + bool m_returnReceipt; + bool m_DSN; + bool m_bodyIsAsciiOnly; + bool m_forceMsgEncoding; + int32_t m_receiptHeaderType; /* receipt header type */ + nsCString m_DefaultCharacterSet; + bool m_needToCheckCharset; + + nsCOMPtr<nsIMsgComposeSecure> mSecureCompFields; + nsCOMPtr<msgIWritableStructuredHeaders> mStructuredHeaders; +}; + +#endif /* _MsgCompFields_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgCompUtils.cpp b/comm/mailnews/compose/src/nsMsgCompUtils.cpp new file mode 100644 index 0000000000..50ce86497b --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCompUtils.cpp @@ -0,0 +1,1164 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsMsgCompUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsStringFwd.h" +#include "prmem.h" +#include "nsIStringBundle.h" +#include "nsIIOService.h" +#include "nsIHttpProtocolHandler.h" +#include "nsMailHeaders.h" +#include "nsMsgI18N.h" +#include "nsINntpService.h" +#include "nsMimeTypes.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsMsgPrompts.h" +#include "nsMsgUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsComposeStrings.h" +#include "nsIMsgCompUtils.h" +#include "nsIMsgMdnGenerator.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "nsCRTGlue.h" +#include <ctype.h> +#include "mozilla/dom/Element.h" +#include "mozilla/EncodingDetector.h" +#include "mozilla/Components.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/ContentIterator.h" +#include "mozilla/dom/Document.h" +#include "nsIMIMEInfo.h" +#include "nsIMsgHeaderParser.h" +#include "nsIMutableArray.h" +#include "nsIRandomGenerator.h" +#include "nsID.h" + +void msg_generate_message_id(nsIMsgIdentity* identity, + const nsACString& customHost, + nsACString& messageID); + +NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils) + +nsMsgCompUtils::nsMsgCompUtils() {} + +nsMsgCompUtils::~nsMsgCompUtils() {} + +NS_IMETHODIMP nsMsgCompUtils::MimeMakeSeparator(const char* prefix, + char** _retval) { + NS_ENSURE_ARG_POINTER(prefix); + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mime_make_separator(prefix); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompUtils::MsgGenerateMessageId(nsIMsgIdentity* identity, + const nsACString& host, + nsACString& messageID) { + // We don't check `host` because it's allowed to be a null pointer (which + // means we should ignore it for message ID generation). + NS_ENSURE_ARG_POINTER(identity); + msg_generate_message_id(identity, host, messageID); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompUtils::GetMsgMimeConformToStandard(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nsMsgMIMEGetConformToStandard(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompUtils::DetectCharset(const nsACString& aContent, + nsACString& aCharset) { + mozilla::UniquePtr<mozilla::EncodingDetector> detector = + mozilla::EncodingDetector::Create(); + mozilla::Span<const uint8_t> src = mozilla::AsBytes( + mozilla::Span(ToNewCString(aContent), aContent.Length())); + mozilla::Unused << detector->Feed(src, true); + auto encoding = detector->Guess(nullptr, true); + encoding->Name(aCharset); + return NS_OK; +} + +// +// Create a file for the a unique temp file +// on the local machine. Caller must free memory +// +nsresult nsMsgCreateTempFile(const char* tFileName, nsIFile** tFile) { + if ((!tFileName) || (!*tFileName)) tFileName = "nsmail.tmp"; + + nsresult rv = + GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, tFileName, tFile); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) NS_RELEASE(*tFile); + + return rv; +} + +// This is the value a caller will Get if they don't Set first (like MDN +// sending a return receipt), so init to the default value of the +// mail.strictly_mime_headers preference. +static bool mime_headers_use_quoted_printable_p = true; + +bool nsMsgMIMEGetConformToStandard(void) { + return mime_headers_use_quoted_printable_p; +} + +void nsMsgMIMESetConformToStandard(bool conform_p) { + /* + * If we are conforming to mime standard no matter what we set + * for the headers preference when generating mime headers we should + * also conform to the standard. Otherwise, depends the preference + * we set. For now, the headers preference is not accessible from UI. + */ + if (conform_p) + mime_headers_use_quoted_printable_p = true; + else { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + prefs->GetBoolPref("mail.strictly_mime_headers", + &mime_headers_use_quoted_printable_p); + } + } +} + +/** + * Checks if the recipient fields have sane values for message send. + */ +nsresult mime_sanity_check_fields_recipients(const char* to, const char* cc, + const char* bcc, + const char* newsgroups) { + if (to) + while (IS_SPACE(*to)) to++; + if (cc) + while (IS_SPACE(*cc)) cc++; + if (bcc) + while (IS_SPACE(*bcc)) bcc++; + if (newsgroups) + while (IS_SPACE(*newsgroups)) newsgroups++; + + if ((!to || !*to) && (!cc || !*cc) && (!bcc || !*bcc) && + (!newsgroups || !*newsgroups)) + return NS_MSG_NO_RECIPIENTS; + + return NS_OK; +} + +/** + * Checks if the compose fields have sane values for message send. + */ +nsresult mime_sanity_check_fields( + const char* from, const char* reply_to, const char* to, const char* cc, + const char* bcc, const char* fcc, const char* newsgroups, + const char* followup_to, const char* /*subject*/, + const char* /*references*/, const char* /*organization*/, + const char* /*other_random_headers*/) { + if (from) + while (IS_SPACE(*from)) from++; + if (reply_to) + while (IS_SPACE(*reply_to)) reply_to++; + if (fcc) + while (IS_SPACE(*fcc)) fcc++; + if (followup_to) + while (IS_SPACE(*followup_to)) followup_to++; + + // TODO: sanity check other_random_headers for newline conventions + if (!from || !*from) return NS_MSG_NO_SENDER; + + return mime_sanity_check_fields_recipients(to, cc, bcc, newsgroups); +} + +// Helper macro for generating the X-Mozilla-Draft-Info header. +#define APPEND_BOOL(method, param) \ + do { \ + bool val = false; \ + fields->Get##method(&val); \ + if (val) \ + draftInfo.AppendLiteral(param "=1"); \ + else \ + draftInfo.AppendLiteral(param "=0"); \ + } while (false) + +nsresult mime_generate_headers(nsIMsgCompFields* fields, + nsMsgDeliverMode deliver_mode, + msgIWritableStructuredHeaders* finalHeaders) { + nsresult rv = NS_OK; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDraft = deliver_mode == nsIMsgSend::nsMsgSaveAsDraft || + deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate || + deliver_mode == nsIMsgSend::nsMsgQueueForLater || + deliver_mode == nsIMsgSend::nsMsgDeliverBackground; + + bool hasDisclosedRecipient = false; + + MOZ_ASSERT(fields, "null fields"); + NS_ENSURE_ARG_POINTER(fields); + + nsTArray<RefPtr<msgIAddressObject>> from; + fields->GetAddressingHeader("From", true, from); + + // Copy all headers from the original compose field. + rv = finalHeaders->AddAllHeaders(fields); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMessageId = false; + if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) && + hasMessageId) { + /* MDN request header requires to have MessageID header presented + * in the message in order to + * coorelate the MDN reports to the original message. Here will be + * the right place + */ + + bool returnReceipt = false; + fields->GetReturnReceipt(&returnReceipt); + if (returnReceipt && (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft && + deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate)) { + int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType; + fields->GetReceiptHeaderType(&receipt_header_type); + + // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ; + // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ; + // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers . + if (receipt_header_type != nsIMsgMdnGenerator::eRrtType) + finalHeaders->SetAddressingHeader("Disposition-Notification-To", from); + if (receipt_header_type != nsIMsgMdnGenerator::eDntType) + finalHeaders->SetAddressingHeader("Return-Receipt-To", from); + } + } + + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); + int gmtoffset = + (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60; + + // Use PR_FormatTimeUSEnglish() to format the date in US English format, + // then figure out what our local GMT offset is, and append it (since + // PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as + // per RFC 1123 (superseding RFC 822.) + char dateString[130]; + PR_FormatTimeUSEnglish(dateString, sizeof(dateString), + "%a, %d %b %Y %H:%M:%S ", &now); + + char* entryPoint = dateString + strlen(dateString); + PR_snprintf(entryPoint, sizeof(dateString) - (entryPoint - dateString), + "%c%02d%02d" CRLF, (gmtoffset >= 0 ? '+' : '-'), + ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60), + ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60)); + finalHeaders->SetRawHeader("Date", nsDependentCString(dateString)); + + // X-Mozilla-Draft-Info + if (isDraft) { + nsAutoCString draftInfo; + draftInfo.AppendLiteral("internal/draft; "); + APPEND_BOOL(AttachVCard, "vcard"); + draftInfo.AppendLiteral("; "); + bool hasReturnReceipt = false; + fields->GetReturnReceipt(&hasReturnReceipt); + if (hasReturnReceipt) { + // slight change compared to 4.x; we used to use receipt= to tell + // whether the draft/template has request for either MDN or DNS or both + // return receipt; since the DNS is out of the picture we now use the + // header type + 1 to tell whether user has requested the return receipt + int32_t headerType = 0; + fields->GetReceiptHeaderType(&headerType); + draftInfo.AppendLiteral("receipt="); + draftInfo.AppendInt(headerType + 1); + } else + draftInfo.AppendLiteral("receipt=0"); + draftInfo.AppendLiteral("; "); + APPEND_BOOL(DSN, "DSN"); + draftInfo.AppendLiteral("; "); + draftInfo.AppendLiteral("uuencode=0"); + draftInfo.AppendLiteral("; "); + APPEND_BOOL(AttachmentReminder, "attachmentreminder"); + draftInfo.AppendLiteral("; "); + int32_t deliveryFormat; + fields->GetDeliveryFormat(&deliveryFormat); + draftInfo.AppendLiteral("deliveryformat="); + draftInfo.AppendInt(deliveryFormat); + + finalHeaders->SetRawHeader(HEADER_X_MOZILLA_DRAFT_INFO, draftInfo); + } + + bool sendUserAgent = false; + if (prefs) { + prefs->GetBoolPref("mailnews.headers.sendUserAgent", &sendUserAgent); + } + if (sendUserAgent) { + bool useMinimalUserAgent = false; + if (prefs) { + prefs->GetBoolPref("mailnews.headers.useMinimalUserAgent", + &useMinimalUserAgent); + } + if (useMinimalUserAgent) { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + if (bundleService) { + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle( + "chrome://branding/locale/brand.properties", + getter_AddRefs(brandBundle)); + if (NS_SUCCEEDED(rv)) { + nsString brandName; + brandBundle->GetStringFromName("brandFullName", brandName); + if (!brandName.IsEmpty()) + finalHeaders->SetUnstructuredHeader("User-Agent", brandName); + } + } + } else { + nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler = + do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv); + if (NS_SUCCEEDED(rv) && pHTTPHandler) { + nsAutoCString userAgentString; + // Ignore error since we're testing the return value. + mozilla::Unused << pHTTPHandler->GetUserAgent(userAgentString); + + if (!userAgentString.IsEmpty()) + finalHeaders->SetUnstructuredHeader( + "User-Agent", NS_ConvertUTF8toUTF16(userAgentString)); + } + } + } + + finalHeaders->SetUnstructuredHeader("MIME-Version", u"1.0"_ns); + + nsAutoCString newsgroups; + finalHeaders->GetRawHeader("Newsgroups", newsgroups); + if (!newsgroups.IsEmpty()) { + // Since the newsgroup header can contain data in the form of: + // "news://news.mozilla.org/netscape.test,news://news.mozilla.org/netscape.junk" + // we need to turn that into: "netscape.test,netscape.junk" + // (XXX: can it really?) + nsCOMPtr<nsINntpService> nntpService = + do_GetService("@mozilla.org/messenger/nntpservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString newsgroupsHeaderVal; + nsCString newshostHeaderVal; + rv = nntpService->GenerateNewsHeaderValsForPosting( + newsgroups, getter_Copies(newsgroupsHeaderVal), + getter_Copies(newshostHeaderVal)); + NS_ENSURE_SUCCESS(rv, rv); + finalHeaders->SetRawHeader("Newsgroups", newsgroupsHeaderVal); + + // If we are here, we are NOT going to send this now. (i.e. it is a Draft, + // Send Later file, etc...). Because of that, we need to store what the user + // typed in on the original composition window for use later when rebuilding + // the headers + if (deliver_mode != nsIMsgSend::nsMsgDeliverNow && + deliver_mode != nsIMsgSend::nsMsgSendUnsent) { + // This is going to be saved for later, that means we should just store + // what the user typed into the "Newsgroup" line in the + // HEADER_X_MOZILLA_NEWSHOST header for later use by "Send Unsent + // Messages", "Drafts" or "Templates" + finalHeaders->SetRawHeader(HEADER_X_MOZILLA_NEWSHOST, newshostHeaderVal); + } + + // Newsgroups are a recipient... + hasDisclosedRecipient = true; + } + + nsTArray<RefPtr<msgIAddressObject>> recipients; + finalHeaders->GetAddressingHeader("To", false, recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); + finalHeaders->GetAddressingHeader("Cc", false, recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); + + // If we don't have disclosed recipient (only Bcc), address the message to + // undisclosed-recipients to prevent problem with some servers + + // If we are saving the message as a draft, don't bother inserting the + // undisclosed recipients field. We'll take care of that when we really send + // the message. + if (!hasDisclosedRecipient && + (!isDraft || deliver_mode == nsIMsgSend::nsMsgQueueForLater)) { + bool bAddUndisclosedRecipients = true; + prefs->GetBoolPref("mail.compose.add_undisclosed_recipients", + &bAddUndisclosedRecipients); + if (bAddUndisclosedRecipients) { + bool hasBcc = false; + fields->HasHeader("Bcc", &hasBcc); + if (hasBcc) { + nsCOMPtr<nsIStringBundleService> stringService = + mozilla::components::StringBundle::Service(); + if (stringService) { + nsCOMPtr<nsIStringBundle> composeStringBundle; + rv = stringService->CreateBundle( + "chrome://messenger/locale/messengercompose/" + "composeMsgs.properties", + getter_AddRefs(composeStringBundle)); + if (NS_SUCCEEDED(rv)) { + nsString undisclosedRecipients; + rv = composeStringBundle->GetStringFromName("undisclosedRecipients", + undisclosedRecipients); + if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty()) { + nsCOMPtr<nsIMsgHeaderParser> headerParser( + mozilla::components::HeaderParser::Service()); + nsCOMPtr<msgIAddressObject> group; + nsTArray<RefPtr<msgIAddressObject>> noRecipients; + headerParser->MakeGroupObject(undisclosedRecipients, noRecipients, + getter_AddRefs(group)); + recipients.AppendElement(group); + finalHeaders->SetAddressingHeader("To", recipients); + } + } + } + } + } + } + + // We don't want to emit a Bcc header to the output. If we are saving this to + // Drafts/Sent, this is re-added later in nsMsgSend.cpp. + finalHeaders->DeleteHeader("bcc"); + + // Skip no or empty priority. + nsAutoCString priority; + rv = fields->GetRawHeader("X-Priority", priority); + if (NS_SUCCEEDED(rv) && !priority.IsEmpty()) { + nsMsgPriorityValue priorityValue; + + NS_MsgGetPriorityFromString(priority.get(), priorityValue); + + // Skip default priority. + if (priorityValue != nsMsgPriority::Default) { + nsAutoCString priorityName; + nsAutoCString priorityValueString; + + NS_MsgGetPriorityValueString(priorityValue, priorityValueString); + NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName); + + // Output format: [X-Priority: <pValue> (<pName>)] + priorityValueString.AppendLiteral(" ("); + priorityValueString += priorityName; + priorityValueString.Append(')'); + finalHeaders->SetRawHeader("X-Priority", priorityValueString); + } + } + + nsAutoCString references; + finalHeaders->GetRawHeader("References", references); + if (!references.IsEmpty()) { + // The References header should be kept under 998 characters: if it's too + // long, trim out the earliest references to make it smaller. + if (references.Length() > 986) { + int32_t firstRef = references.FindChar('<'); + int32_t secondRef = references.FindChar('<', firstRef + 1); + if (secondRef > 0) { + nsAutoCString newReferences(StringHead(references, secondRef)); + int32_t bracket = references.FindChar( + '<', references.Length() + newReferences.Length() - 986); + if (bracket > 0) { + newReferences.Append(Substring(references, bracket)); + finalHeaders->SetRawHeader("References", newReferences); + } + } + } + // The In-Reply-To header is the last entry in the references header... + int32_t bracket = references.RFind("<"); + if (bracket >= 0) + finalHeaders->SetRawHeader("In-Reply-To", Substring(references, bracket)); + } + + return NS_OK; +} + +#undef APPEND_BOOL // X-Mozilla-Draft-Info helper macro + +static void GenerateGlobalRandomBytes(unsigned char* buf, int32_t len) { + // Attempt to generate bytes from system entropy-based RNG. + nsCOMPtr<nsIRandomGenerator> randomGenerator( + do_GetService("@mozilla.org/security/random-generator;1")); + MOZ_ASSERT(randomGenerator, "nsIRandomGenerator service not retrievable"); + uint8_t* tempBuffer; + nsresult rv = randomGenerator->GenerateRandomBytes(len, &tempBuffer); + if (NS_SUCCEEDED(rv)) { + memcpy(buf, tempBuffer, len); + free(tempBuffer); + return; + } + // nsIRandomGenerator failed -- fall back to low entropy PRNG. + static bool firstTime = true; + if (firstTime) { + // Seed the random-number generator with current time so that + // the numbers will be different every time we run. + srand((unsigned)PR_Now()); + firstTime = false; + } + + for (int32_t i = 0; i < len; i++) buf[i] = rand() % 256; +} + +char* mime_make_separator(const char* prefix) { + unsigned char rand_buf[13]; + GenerateGlobalRandomBytes(rand_buf, 12); + + return PR_smprintf( + "------------%s" + "%02X%02X%02X%02X" + "%02X%02X%02X%02X" + "%02X%02X%02X%02X", + prefix, rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3], rand_buf[4], + rand_buf[5], rand_buf[6], rand_buf[7], rand_buf[8], rand_buf[9], + rand_buf[10], rand_buf[11]); +} + +// Tests if the content of a string is a valid host name. +// In this case, a valid host name is any non-empty string that only contains +// letters (a-z + A-Z), numbers (0-9) and the characters '-', '_' and '.'. +static bool isValidHost(const nsCString& host) { + if (host.IsEmpty()) { + return false; + } + + const auto* cur = host.BeginReading(); + const auto* end = host.EndReading(); + for (; cur < end; ++cur) { + if (!isalpha(*cur) && !isdigit(*cur) && *cur != '-' && *cur != '_' && + *cur != '.') { + return false; + } + } + + return true; +} + +// Extract the domain name from an address. +// If none could be found (i.e. the address does not contain an '@' sign, or +// the value following it is not a valid domain), then nullptr is returned. +void msg_domain_name_from_address(const nsACString& address, nsACString& host) { + auto atIndex = address.FindChar('@'); + + if (address.IsEmpty() || atIndex == kNotFound) { + return; + } + + // Substring() should handle cases where we would go out of bounds (by + // preventing the index from exceeding the length of the source string), so we + // don't need to handle this here. + host = Substring(address, atIndex + 1); +} + +// Generate a value for a Message-Id header using the identity and optional +// hostname provided. +void msg_generate_message_id(nsIMsgIdentity* identity, + const nsACString& customHost, + nsACString& messageID) { + nsCString host; + + // Check if the identity forces host name. This is sometimes the case when + // using newsgroup. + nsCString forcedFQDN; + nsresult rv = identity->GetCharAttribute("FQDN", forcedFQDN); + if (NS_SUCCEEDED(rv) && !forcedFQDN.IsEmpty()) { + host = forcedFQDN; + } + + // If no valid host name has been set, try using the value defined by the + // caller, if any. + if (!isValidHost(host)) { + host = customHost; + } + + // If no valid host name has been set, try extracting one from the email + // address associated with the identity. + if (!isValidHost(host)) { + nsCString from; + rv = identity->GetEmail(from); + if (NS_SUCCEEDED(rv) && !from.IsEmpty()) { + msg_domain_name_from_address(from, host); + } + } + + // If we still couldn't find a valid host name to use, we can't generate a + // valid message ID, so bail, and let NNTP and SMTP generate them. + if (!isValidHost(host)) { + return; + } + + // Generate 128-bit UUID for the local part of the ID. `nsID` provides us with + // cryptographically-secure generation. + nsID uuid = nsID::GenerateUUID(); + char uuidString[NSID_LENGTH]; + uuid.ToProvidedString(uuidString); + // Drop first and last characters (curly braces). + uuidString[NSID_LENGTH - 2] = 0; + + messageID.AppendPrintf("<%s@%s>", uuidString + 1, host.get()); +} + +// this is to guarantee the folded line will never be greater +// than 78 = 75 + CRLFLWSP +#define PR_MAX_FOLDING_LEN 75 + +/*static */ char* RFC2231ParmFolding(const char* parmName, + const char* parmValue) { + NS_ENSURE_TRUE(parmName && *parmName && parmValue && *parmValue, nullptr); + + bool needEscape; + nsCString dupParm; + nsCString charset("UTF-8"); + + if (!mozilla::IsAsciiNullTerminated(parmValue)) { + needEscape = true; + dupParm.Assign(parmValue); + MsgEscapeString(dupParm, nsINetUtil::ESCAPE_ALL, dupParm); + } else { + needEscape = false; + dupParm.Adopt(msg_make_filename_qtext(parmValue, true)); + } + + int32_t parmNameLen = PL_strlen(parmName); + int32_t parmValueLen = dupParm.Length(); + + parmNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___" + + char* foldedParm = nullptr; + + if ((parmValueLen + parmNameLen + strlen("UTF-8")) < PR_MAX_FOLDING_LEN) { + foldedParm = PL_strdup(parmName); + if (needEscape) { + NS_MsgSACat(&foldedParm, "*="); + NS_MsgSACat(&foldedParm, "UTF-8"); + NS_MsgSACat(&foldedParm, "''"); // We don't support language. + } else + NS_MsgSACat(&foldedParm, "=\""); + NS_MsgSACat(&foldedParm, dupParm.get()); + if (!needEscape) NS_MsgSACat(&foldedParm, "\""); + } else { + int curLineLen = 0; + int counter = 0; + char digits[32]; + char* start = dupParm.BeginWriting(); + char* end = NULL; + char tmp = 0; + + while (parmValueLen > 0) { + curLineLen = 0; + if (counter == 0) { + PR_FREEIF(foldedParm) + foldedParm = PL_strdup(parmName); + } else { + NS_MsgSACat(&foldedParm, ";\r\n "); + NS_MsgSACat(&foldedParm, parmName); + } + PR_snprintf(digits, sizeof(digits), "*%d", counter); + NS_MsgSACat(&foldedParm, digits); + curLineLen += PL_strlen(digits); + if (needEscape) { + NS_MsgSACat(&foldedParm, "*="); + if (counter == 0) { + NS_MsgSACat(&foldedParm, "UTF-8"); + NS_MsgSACat(&foldedParm, "''"); // We don't support language. + curLineLen += strlen("UTF-8"); + } + } else { + NS_MsgSACat(&foldedParm, "=\""); + } + counter++; + curLineLen += parmNameLen; + if (parmValueLen <= PR_MAX_FOLDING_LEN - curLineLen) + end = start + parmValueLen; + else + end = start + (PR_MAX_FOLDING_LEN - curLineLen); + + tmp = 0; + if (*end && needEscape) { + // Check to see if we are in the middle of escaped char. + // We use ESCAPE_ALL, so every third character is a '%'. + if (end - 1 > start && *(end - 1) == '%') { + end -= 1; + } else if (end - 2 > start && *(end - 2) == '%') { + end -= 2; + } + // *end is now a '%'. + // Check if the following UTF-8 octet is a continuation. + while (end - 3 > start && (*(end + 1) == '8' || *(end + 1) == '9' || + *(end + 1) == 'A' || *(end + 1) == 'B')) { + end -= 3; + } + tmp = *end; + *end = 0; + } else { + tmp = *end; + *end = 0; + } + NS_MsgSACat(&foldedParm, start); + if (!needEscape) NS_MsgSACat(&foldedParm, "\""); + + parmValueLen -= (end - start); + if (tmp) *end = tmp; + start = end; + } + } + + return foldedParm; +} + +bool mime_7bit_data_p(const char* string, uint32_t size) { + if ((!string) || (!*string)) return true; + + char* ptr = (char*)string; + for (uint32_t i = 0; i < size; i++) { + if ((unsigned char)ptr[i] > 0x7F) return false; + } + return true; +} + +// Strips whitespace, and expands newlines into newline-tab for use in +// mail headers. Returns a new string or 0 (if it would have been empty.) +// If addr_p is true, the addresses will be parsed and reemitted as +// rfc822 mailboxes. +char* mime_fix_header_1(const char* string, bool addr_p, bool news_p) { + char* new_string; + const char* in; + char* out; + int32_t i, old_size, new_size; + + if (!string || !*string) return 0; + + if (addr_p) { + return strdup(string); + } + + old_size = PL_strlen(string); + new_size = old_size; + for (i = 0; i < old_size; i++) + if (string[i] == '\r' || string[i] == '\n') new_size += 2; + + new_string = (char*)PR_Malloc(new_size + 1); + if (!new_string) return 0; + + in = string; + out = new_string; + + /* strip leading whitespace. */ + while (IS_SPACE(*in)) in++; + + /* replace CR, LF, or CRLF with CRLF-TAB. */ + while (*in) { + if (*in == '\r' || *in == '\n') { + if (*in == '\r' && in[1] == '\n') in++; + in++; + *out++ = '\r'; + *out++ = '\n'; + *out++ = '\t'; + } else if (news_p && *in == ',') { + *out++ = *in++; + /* skip over all whitespace after a comma. */ + while (IS_SPACE(*in)) in++; + } else + *out++ = *in++; + } + *out = 0; + + /* strip trailing whitespace. */ + while (out > in && IS_SPACE(out[-1])) *out-- = 0; + + /* If we ended up throwing it all away, use 0 instead of "". */ + if (!*new_string) { + PR_Free(new_string); + new_string = 0; + } + + return new_string; +} + +char* mime_fix_header(const char* string) { + return mime_fix_header_1(string, false, false); +} + +char* mime_fix_addr_header(const char* string) { + return mime_fix_header_1(string, true, false); +} + +char* mime_fix_news_header(const char* string) { + return mime_fix_header_1(string, false, true); +} + +bool mime_type_requires_b64_p(const char* type) { + if (!type || !PL_strcasecmp(type, UNKNOWN_CONTENT_TYPE)) + // Unknown types don't necessarily require encoding. (Note that + // "unknown" and "application/octet-stream" aren't the same.) + return false; + + else if (!PL_strncasecmp(type, "image/", 6) || + !PL_strncasecmp(type, "audio/", 6) || + !PL_strncasecmp(type, "video/", 6) || + !PL_strncasecmp(type, "application/", 12)) { + // The following types are application/ or image/ types that are actually + // known to contain textual data (meaning line-based, not binary, where + // CRLF conversion is desired rather than disastrous.) So, if the type + // is any of these, it does not *require* base64, and if we do need to + // encode it for other reasons, we'll probably use quoted-printable. + // But, if it's not one of these types, then we assume that any subtypes + // of the non-"text/" types are binary data, where CRLF conversion would + // corrupt it, so we use base64 right off the bat. + + // The reason it's desirable to ship these as text instead of just using + // base64 all the time is mainly to preserve the readability of them for + // non-MIME users: if I mail a /bin/sh script to someone, it might not + // need to be encoded at all, so we should leave it readable if we can. + + // This list of types was derived from the comp.mail.mime FAQ, section + // 10.2.2, "List of known unregistered MIME types" on 2-Feb-96. + static const char* app_and_image_types_which_are_really_text[] = { + "application/mac-binhex40", /* APPLICATION_BINHEX */ + "application/pgp", /* APPLICATION_PGP */ + "application/pgp-keys", + "application/x-pgp-message", /* APPLICATION_PGP2 */ + "application/postscript", /* APPLICATION_POSTSCRIPT */ + "application/x-uuencode", /* APPLICATION_UUENCODE */ + "application/x-uue", /* APPLICATION_UUENCODE2 */ + "application/uue", /* APPLICATION_UUENCODE4 */ + "application/uuencode", /* APPLICATION_UUENCODE3 */ + "application/sgml", + "application/x-csh", + "application/javascript", + "application/ecmascript", + "application/x-javascript", + "application/x-latex", + "application/x-macbinhex40", + "application/x-ns-proxy-autoconfig", + "application/x-www-form-urlencoded", + "application/x-perl", + "application/x-sh", + "application/x-shar", + "application/x-tcl", + "application/x-tex", + "application/x-texinfo", + "application/x-troff", + "application/x-troff-man", + "application/x-troff-me", + "application/x-troff-ms", + "application/x-troff-ms", + "application/x-wais-source", + "image/x-bitmap", + "image/x-pbm", + "image/x-pgm", + "image/x-portable-anymap", + "image/x-portable-bitmap", + "image/x-portable-graymap", + "image/x-portable-pixmap", /* IMAGE_PPM */ + "image/x-ppm", + "image/x-xbitmap", /* IMAGE_XBM */ + "image/x-xbm", /* IMAGE_XBM2 */ + "image/xbm", /* IMAGE_XBM3 */ + "image/x-xpixmap", + "image/x-xpm", + 0}; + const char** s; + for (s = app_and_image_types_which_are_really_text; *s; s++) + if (!PL_strcasecmp(type, *s)) return false; + + /* All others must be assumed to be binary formats, and need Base64. */ + return true; + } + + else + return false; +} + +// +// Some types should have a "charset=" parameter, and some shouldn't. +// This is what decides. +// +bool mime_type_needs_charset(const char* type) { + /* Only text types should have charset. */ + if (!type || !*type) + return false; + else if (!PL_strncasecmp(type, "text", 4)) + return true; + else + return false; +} + +// Given a string, convert it to 'qtext' (quoted text) for RFC822 header +// purposes. +char* msg_make_filename_qtext(const char* srcText, bool stripCRLFs) { + /* newString can be at most twice the original string (every char quoted). */ + char* newString = (char*)PR_Malloc(PL_strlen(srcText) * 2 + 1); + if (!newString) return NULL; + + const char* s = srcText; + const char* end = srcText + PL_strlen(srcText); + char* d = newString; + + while (*s) { + // Put backslashes in front of existing backslashes, or double quote + // characters. + // If stripCRLFs is true, don't write out CRs or LFs. Otherwise, + // write out a backslash followed by the CR but not + // linear-white-space. + // We might already have quoted pair of "\ " or "\\t" skip it. + if (*s == '\\' || *s == '"' || + (!stripCRLFs && + (*s == '\r' && (s[1] != '\n' || + (s[1] == '\n' && (s + 2) < end && !IS_SPACE(s[2])))))) + *d++ = '\\'; + + if (stripCRLFs && *s == '\r' && s[1] == '\n' && (s + 2) < end && + IS_SPACE(s[2])) { + s += 3; // skip CRLFLWSP + } else { + *d++ = *s++; + } + } + *d = 0; + + return newString; +} + +// Utility to create a nsIURI object... +nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const nsCString& aSpec) { + nsresult rv = NS_OK; + if (nullptr == aInstancePtrResult) return NS_ERROR_NULL_POINTER; + nsCOMPtr<nsIIOService> pNetService = mozilla::components::IO::Service(); + NS_ENSURE_TRUE(pNetService, NS_ERROR_UNEXPECTED); + if (aSpec.Find("://") == kNotFound && !StringBeginsWith(aSpec, "data:"_ns)) { + // XXXjag Temporary fix for bug 139362 until the real problem(bug 70083) get + // fixed + nsAutoCString uri("http://"_ns); + uri.Append(aSpec); + rv = pNetService->NewURI(uri, nullptr, nullptr, aInstancePtrResult); + } else + rv = pNetService->NewURI(aSpec, nullptr, nullptr, aInstancePtrResult); + return rv; +} + +char* nsMsgGetLocalFileFromURL(const char* url) { + char* finalPath; + NS_ASSERTION(PL_strncasecmp(url, "file://", 7) == 0, "invalid url"); + finalPath = (char*)PR_Malloc(strlen(url)); + if (finalPath == NULL) return NULL; + strcpy(finalPath, url + 6 + 1); + return finalPath; +} + +char* nsMsgParseURLHost(const char* url) { + nsIURI* workURI = nullptr; + nsresult rv; + + rv = nsMsgNewURL(&workURI, nsDependentCString(url)); + if (NS_FAILED(rv) || !workURI) return nullptr; + + nsAutoCString host; + rv = workURI->GetHost(host); + NS_IF_RELEASE(workURI); + if (NS_FAILED(rv)) return nullptr; + + return ToNewCString(host); +} + +char* GenerateFileNameFromURI(nsIURI* aURL) { + nsresult rv; + nsCString file; + nsCString spec; + char* returnString; + char* cp = nullptr; + char* cp1 = nullptr; + + rv = aURL->GetPathQueryRef(file); + if (NS_SUCCEEDED(rv) && !file.IsEmpty()) { + char* newFile = ToNewCString(file); + if (!newFile) return nullptr; + + // strip '/' + cp = PL_strrchr(newFile, '/'); + if (cp) + ++cp; + else + cp = newFile; + + if (*cp) { + if ((cp1 = PL_strchr(cp, '/'))) *cp1 = 0; + if ((cp1 = PL_strchr(cp, '?'))) *cp1 = 0; + if ((cp1 = PL_strchr(cp, '>'))) *cp1 = 0; + if (*cp != '\0') { + returnString = PL_strdup(cp); + PR_FREEIF(newFile); + return returnString; + } + } else + return nullptr; + } + + cp = nullptr; + cp1 = nullptr; + + rv = aURL->GetSpec(spec); + if (NS_SUCCEEDED(rv) && !spec.IsEmpty()) { + char* newSpec = ToNewCString(spec); + if (!newSpec) return nullptr; + + char *cp2 = NULL, *cp3 = NULL; + + // strip '"' + cp2 = newSpec; + while (*cp2 == '"') cp2++; + if ((cp3 = PL_strchr(cp2, '"'))) *cp3 = 0; + + char* hostStr = nsMsgParseURLHost(cp2); + if (!hostStr) hostStr = PL_strdup(cp2); + + bool isHTTP = false; + if (NS_SUCCEEDED(aURL->SchemeIs("http", &isHTTP)) && isHTTP) { + returnString = PR_smprintf("%s.html", hostStr); + PR_FREEIF(hostStr); + } else + returnString = hostStr; + + PR_FREEIF(newSpec); + return returnString; + } + + return nullptr; +} + +// +// This routine will generate a content id for use in a mail part. +// It will take the part number passed in as well as the email +// address. If the email address is null or invalid, we will simply +// use netscape.com for the interesting part. The content ID's will +// look like the following: +// +// Content-ID: <part1.36DF1DCE.73B5A330@netscape.com> +// +char* mime_gen_content_id(uint32_t aPartNum, const char* aEmailAddress) { + int32_t randLen = 5; + unsigned char rand_buf1[5]; + unsigned char rand_buf2[5]; + const char* domain = nullptr; + const char* defaultDomain = "@netscape.com"; + + memset(rand_buf1, 0, randLen - 1); + memset(rand_buf2, 0, randLen - 1); + + GenerateGlobalRandomBytes(rand_buf1, randLen); + GenerateGlobalRandomBytes(rand_buf2, randLen); + + // Find the @domain.com string... + if (aEmailAddress && *aEmailAddress) + domain = const_cast<const char*>(PL_strchr(aEmailAddress, '@')); + + if (!domain) domain = defaultDomain; + + char* retVal = PR_smprintf( + "part%d." + "%02X%02X%02X%02X" + "." + "%02X%02X%02X%02X" + "%s", + aPartNum, rand_buf1[0], rand_buf1[1], rand_buf1[2], rand_buf1[3], + rand_buf2[0], rand_buf2[1], rand_buf2[2], rand_buf2[3], domain); + + return retVal; +} + +void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity, + nsCString& uri) { + nsresult rv; + uri.Truncate(); + + // QueueForLater (Outbox) + if (aMode == nsIMsgSend::nsMsgQueueForLater || + aMode == nsIMsgSend::nsMsgDeliverBackground) { + nsCOMPtr<nsIPrefBranch> prefs( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return; + rv = prefs->GetCharPref("mail.default_sendlater_uri", uri); + if (NS_FAILED(rv) || uri.IsEmpty()) + uri.AssignLiteral(ANY_SERVER); + else { + // check if uri is unescaped, and if so, escape it and reset the pef. + if (uri.FindChar(' ') != kNotFound) { + uri.ReplaceSubstring(" ", "%20"); + prefs->SetCharPref("mail.default_sendlater_uri", uri); + } + } + return; + } + + if (!identity) return; + + if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts) + rv = identity->GetDraftFolder(uri); + else if (aMode == + nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates) + rv = identity->GetStationeryFolder(uri); + else { + bool doFcc = false; + rv = identity->GetDoFcc(&doFcc); + if (doFcc) rv = identity->GetFccFolder(uri); + } + return; +} + +/** + * Check if we should use format=flowed (RFC 2646) for a mail. + * We will use format=flowed unless the preference tells us not to do so. + * In this function we set all the serialiser flags. + * 'formatted' is always 'true'. + */ +void GetSerialiserFlags(bool* flowed, bool* formatted) { + *flowed = false; + *formatted = true; + + // Set format=flowed as in RFC 2646 according to the preference. + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + prefs->GetBoolPref("mailnews.send_plaintext_flowed", flowed); + } +} + +already_AddRefed<nsIArray> GetEmbeddedObjects( + mozilla::dom::Document* aDocument) { + nsCOMPtr<nsIMutableArray> nodes = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (NS_WARN_IF(!nodes)) { + return nullptr; + } + + mozilla::PostContentIterator iter; + nsresult rv = iter.Init(aDocument->GetRootElement()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // Loop through the content iterator for each content node. + while (!iter.IsDone()) { + nsINode* node = iter.GetCurrentNode(); + if (node->IsElement()) { + mozilla::dom::Element* element = node->AsElement(); + + // See if it's an image or also include all links. + // Let mail decide which link to send or not + if (element->IsAnyOfHTMLElements(nsGkAtoms::img, nsGkAtoms::a) || + (element->IsHTMLElement(nsGkAtoms::body) && + element->HasAttr(kNameSpaceID_None, nsGkAtoms::background))) { + nodes->AppendElement(node); + } + } + iter.Next(); + } + + return nodes.forget(); +} diff --git a/comm/mailnews/compose/src/nsMsgCompUtils.h b/comm/mailnews/compose/src/nsMsgCompUtils.h new file mode 100644 index 0000000000..7197cde459 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCompUtils.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgCompUtils_H_ +#define _nsMsgCompUtils_H_ + +#include "nscore.h" +#include "mozilla/dom/Document.h" +#include "nsMsgCompFields.h" +#include "nsIMsgSend.h" +#include "nsIMsgCompUtils.h" + +class nsIArray; +class nsIDocument; +class nsIPrompt; + +#define ANY_SERVER "anyfolder://" + +// these are msg hdr property names for storing the original +// msg uri's and disposition(replied/forwarded) when queuing +// messages to send later. +#define ORIG_URI_PROPERTY "origURIs" +#define QUEUED_DISPOSITION_PROPERTY "queuedDisposition" + +extern mozilla::LazyLogModule Compose; + +class nsMsgCompUtils : public nsIMsgCompUtils { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPUTILS + + nsMsgCompUtils(); + + private: + virtual ~nsMsgCompUtils(); +}; + +already_AddRefed<nsIArray> GetEmbeddedObjects( + mozilla::dom::Document* aDocument); + +PR_BEGIN_EXTERN_C + +// +// Create a file spec or file name using the name passed +// in as a template +// +nsresult nsMsgCreateTempFile(const char* tFileName, nsIFile** tFile); +char* nsMsgCreateTempFileName(const char* tFileName); + +// +// Various utilities for building parts of MIME encoded +// messages during message composition +// + +nsresult mime_sanity_check_fields_recipients(const char* to, const char* cc, + const char* bcc, + const char* newsgroups); + +nsresult mime_sanity_check_fields( + const char* from, const char* reply_to, const char* to, const char* cc, + const char* bcc, const char* fcc, const char* newsgroups, + const char* followup_to, const char* /*subject*/, + const char* /*references*/, const char* /*organization*/, + const char* /*other_random_headers*/); + +nsresult mime_generate_headers(nsIMsgCompFields* fields, + nsMsgDeliverMode deliver_mode, + msgIWritableStructuredHeaders* headers); + +char* mime_make_separator(const char* prefix); +char* mime_gen_content_id(uint32_t aPartNum, const char* aEmailAddress); + +bool mime_7bit_data_p(const char* string, uint32_t size); + +char* mime_fix_header_1(const char* string, bool addr_p, bool news_p); +char* mime_fix_header(const char* string); +char* mime_fix_addr_header(const char* string); +char* mime_fix_news_header(const char* string); + +bool mime_type_requires_b64_p(const char* type); +bool mime_type_needs_charset(const char* type); + +char* msg_make_filename_qtext(const char* srcText, bool stripCRLFs); + +char* RFC2231ParmFolding(const char* parmName, const char* parmValue); + +// +// Informational calls... +// +void nsMsgMIMESetConformToStandard(bool conform_p); +bool nsMsgMIMEGetConformToStandard(void); + +// +// network service type calls... +// +nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const nsCString& aSpec); +char* nsMsgGetLocalFileFromURL(const char* url); + +char* nsMsgParseURLHost(const char* url); + +char* GenerateFileNameFromURI(nsIURI* aURL); + +// +// Folder calls... +// +void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity, + nsCString& uri); + +// Check if we should use format=flowed +void GetSerialiserFlags(bool* flowed, bool* formatted); + +PR_END_EXTERN_C + +#endif /* _nsMsgCompUtils_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgCompose.cpp b/comm/mailnews/compose/src/nsMsgCompose.cpp new file mode 100644 index 0000000000..2630852f5a --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCompose.cpp @@ -0,0 +1,5094 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgCompose.h" +#include "mozilla/dom/Document.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsISelectionController.h" +#include "nsMsgI18N.h" +#include "nsMsgQuote.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIDocumentEncoder.h" // for editor output flags +#include "nsMsgCompUtils.h" +#include "nsComposeStrings.h" +#include "nsIMsgSend.h" +#include "nsMailHeaders.h" +#include "nsMsgPrompts.h" +#include "nsMimeTypes.h" +#include "nsICharsetConverterManager.h" +#include "nsTextFormatter.h" +#include "nsIHTMLEditor.h" +#include "nsIEditor.h" +#include "plstr.h" +#include "prmem.h" +#include "nsIDocShell.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIWindowMediator.h" +#include "nsIURL.h" +#include "mozilla/intl/AppDateTimeFormat.h" +#include "nsIMsgComposeService.h" +#include "nsIMsgComposeProgressParams.h" +#include "nsMsgUtils.h" +#include "nsIMsgImapMailFolder.h" +#include "nsImapCore.h" +#include "nsUnicharUtils.h" +#include "nsNetUtil.h" +#include "nsIContentViewer.h" +#include "nsIMsgMdnGenerator.h" +#include "plbase64.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgProgress.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgDatabase.h" +#include "nsStringStream.h" +#include "nsArrayUtils.h" +#include "nsIMsgWindow.h" +#include "nsITextToSubURI.h" +#include "nsIAbManager.h" +#include "nsCRT.h" +#include "mozilla/HTMLEditor.h" +#include "mozilla/Components.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Preferences.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Telemetry.h" +#include "mozilla/dom/HTMLAnchorElement.h" +#include "mozilla/dom/HTMLImageElement.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/Utf8.h" +#include "nsStreamConverter.h" +#include "nsIObserverService.h" +#include "nsIProtocolHandler.h" +#include "nsContentUtils.h" +#include "nsStreamUtils.h" +#include "nsIFileURL.h" +#include "nsTextNode.h" // from dom/base +#include "nsIParserUtils.h" +#include "nsIStringBundle.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::mailnews; + +LazyLogModule Compose("Compose"); + +static nsresult GetReplyHeaderInfo(int32_t* reply_header_type, + nsString& reply_header_authorwrote, + nsString& reply_header_ondateauthorwrote, + nsString& reply_header_authorwroteondate, + nsString& reply_header_originalmessage) { + nsresult rv; + *reply_header_type = 0; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // If fetching any of the preferences fails, + // we return early with header_type = 0 meaning "no header". + rv = NS_GetLocalizedUnicharPreference( + prefBranch, "mailnews.reply_header_authorwrotesingle", + reply_header_authorwrote); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference( + prefBranch, "mailnews.reply_header_ondateauthorwrote", + reply_header_ondateauthorwrote); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference( + prefBranch, "mailnews.reply_header_authorwroteondate", + reply_header_authorwroteondate); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference(prefBranch, + "mailnews.reply_header_originalmessage", + reply_header_originalmessage); + NS_ENSURE_SUCCESS(rv, rv); + + return prefBranch->GetIntPref("mailnews.reply_header_type", + reply_header_type); +} + +static void TranslateLineEnding(nsString& data) { + char16_t* rPtr; // Read pointer + char16_t* wPtr; // Write pointer + char16_t* sPtr; // Start data pointer + char16_t* ePtr; // End data pointer + + rPtr = wPtr = sPtr = data.BeginWriting(); + ePtr = rPtr + data.Length(); + + while (rPtr < ePtr) { + if (*rPtr == nsCRT::CR) { + *wPtr = nsCRT::LF; + if (rPtr + 1 < ePtr && *(rPtr + 1) == nsCRT::LF) rPtr++; + } else + *wPtr = *rPtr; + + rPtr++; + wPtr++; + } + + data.SetLength(wPtr - sPtr); +} + +nsMsgCompose::nsMsgCompose() { + mQuotingToFollow = false; + mAllowRemoteContent = false; + mWhatHolder = 1; + m_window = nullptr; + m_editor = nullptr; + mQuoteStreamListener = nullptr; + mAutodetectCharset = false; + mDeleteDraft = false; + m_compFields = + nullptr; // m_compFields will be set during nsMsgCompose::Initialize + mType = nsIMsgCompType::New; + + // For TagConvertible + // Read and cache pref + mConvertStructs = false; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) + prefBranch->GetBoolPref("converter.html2txt.structs", &mConvertStructs); + + m_composeHTML = false; + + mTmpAttachmentsDeleted = false; + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None; + mDeliverMode = 0; +} + +nsMsgCompose::~nsMsgCompose() { + MOZ_LOG(Compose, LogLevel::Debug, ("~nsMsgCompose()")); + if (!m_compFields) { + // Uhoh. We're in an uninitialized state. Maybe initialize() failed, or + // was never even called. + return; + } + m_window = nullptr; + if (!mMsgSend) { + // This dtor can be called before mMsgSend->CreateAndSendMessage returns, + // tmp attachments are needed to create the message, so don't delete them. + DeleteTmpAttachments(); + } +} + +/* the following macro actually implement addref, release and query interface + * for our component. */ +NS_IMPL_ISUPPORTS(nsMsgCompose, nsIMsgCompose, nsIMsgSendListener, + nsISupportsWeakReference) + +// +// Once we are here, convert the data which we know to be UTF-8 to UTF-16 +// for insertion into the editor +// +nsresult GetChildOffset(nsINode* aChild, nsINode* aParent, int32_t& aOffset) { + NS_ASSERTION((aChild && aParent), "bad args"); + + if (!aChild || !aParent) return NS_ERROR_NULL_POINTER; + + nsINodeList* childNodes = aParent->ChildNodes(); + for (uint32_t i = 0; i < childNodes->Length(); i++) { + nsINode* childNode = childNodes->Item(i); + if (childNode == aChild) { + aOffset = i; + return NS_OK; + } + } + + return NS_ERROR_NULL_POINTER; +} + +nsresult GetNodeLocation(nsINode* inChild, nsCOMPtr<nsINode>* outParent, + int32_t* outOffset) { + NS_ASSERTION((outParent && outOffset), "bad args"); + nsresult result = NS_ERROR_NULL_POINTER; + if (inChild && outParent && outOffset) { + nsCOMPtr<nsINode> inChild2 = inChild; + *outParent = inChild2->GetParentNode(); + if (*outParent) { + result = GetChildOffset(inChild2, *outParent, *outOffset); + } + } + + return result; +} + +bool nsMsgCompose::IsEmbeddedObjectSafe(const char* originalScheme, + const char* originalHost, + const char* originalPath, + Element* element) { + nsresult rv; + + nsAutoString objURL; + + if (!originalScheme || !originalPath) // Having a null host is OK. + return false; + + RefPtr<HTMLImageElement> image = HTMLImageElement::FromNode(element); + RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(element); + + if (image) + image->GetSrc(objURL); + else if (anchor) + anchor->GetHref(objURL); + else + return false; + + if (!objURL.IsEmpty()) { + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), objURL); + if (NS_SUCCEEDED(rv) && uri) { + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + if (NS_SUCCEEDED(rv) && + scheme.Equals(originalScheme, nsCaseInsensitiveCStringComparator)) { + nsAutoCString host; + rv = uri->GetAsciiHost(host); + // mailbox url don't have a host therefore don't be too strict. + if (NS_SUCCEEDED(rv) && + (host.IsEmpty() || originalHost || + host.Equals(originalHost, nsCaseInsensitiveCStringComparator))) { + nsAutoCString path; + rv = uri->GetPathQueryRef(path); + if (NS_SUCCEEDED(rv)) { + nsAutoCString orgPath(originalPath); + MsgRemoveQueryPart(orgPath); + MsgRemoveQueryPart(path); + // mailbox: and JS Account URLs have a message number in + // the query part of "path query ref". We removed this so + // we're not comparing down to the message but down to the folder. + // Code in the frontend (in the "error" event listener in + // MsgComposeCommands.js that deals with unblocking images) will + // prompt if a part of another message is referenced. + // A saved message opened for reply or forwarding has a + // mailbox: URL. + // imap: URLs don't have the message number in the query, so we do + // compare it here. + // news: URLs use group and key in the query, but it's OK to compare + // without them. + return path.Equals(orgPath, nsCaseInsensitiveCStringComparator); + } + } + } + } + } + + return false; +} + +/* The purpose of this function is to mark any embedded object that wasn't a + RFC822 part of the original message as moz-do-not-send. That will prevent us + to attach data not specified by the user or not present in the original + message. +*/ +nsresult nsMsgCompose::TagEmbeddedObjects(nsIEditor* aEditor) { + nsresult rv = NS_OK; + uint32_t count; + uint32_t i; + + if (!aEditor) return NS_ERROR_FAILURE; + + nsCOMPtr<Document> document; + aEditor->GetDocument(getter_AddRefs(document)); + if (!document) return NS_ERROR_FAILURE; + nsCOMPtr<nsIArray> aNodeList = GetEmbeddedObjects(document); + if (!aNodeList) return NS_ERROR_FAILURE; + + if (NS_FAILED(aNodeList->GetLength(&count))) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURI> originalUrl; + nsCString originalScheme; + nsCString originalHost; + nsCString originalPath; + + // first, convert the rdf original msg uri into a url that represents the + // message... + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(mOriginalMsgURI, getter_AddRefs(msgService)); + if (NS_SUCCEEDED(rv)) { + rv = msgService->GetUrlForUri(mOriginalMsgURI, nullptr, + getter_AddRefs(originalUrl)); + if (NS_SUCCEEDED(rv) && originalUrl) { + originalUrl->GetScheme(originalScheme); + originalUrl->GetAsciiHost(originalHost); + originalUrl->GetPathQueryRef(originalPath); + } + } + + // Then compare the url of each embedded objects with the original message. + // If they a not coming from the original message, they should not be sent + // with the message. + for (i = 0; i < count; i++) { + nsCOMPtr<Element> domElement = do_QueryElementAt(aNodeList, i); + if (!domElement) continue; + if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(), + originalPath.get(), domElement)) + continue; // Don't need to tag this object, it's safe to send it. + + // The source of this object should not be sent with the message. + IgnoredErrorResult rv2; + domElement->SetAttribute(u"moz-do-not-send"_ns, u"true"_ns, rv2); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::GetAllowRemoteContent(bool* aAllowRemoteContent) { + NS_ENSURE_ARG_POINTER(aAllowRemoteContent); + *aAllowRemoteContent = mAllowRemoteContent; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::SetAllowRemoteContent(bool aAllowRemoteContent) { + mAllowRemoteContent = aAllowRemoteContent; + return NS_OK; +} + +void nsMsgCompose::InsertDivWrappedTextAtSelection(const nsAString& aText, + const nsAString& classStr) { + NS_ASSERTION(m_editor, + "InsertDivWrappedTextAtSelection called, but no editor exists"); + if (!m_editor) return; + + RefPtr<Element> divElem; + nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor)); + + nsresult rv = + htmlEditor->CreateElementWithDefaults(u"div"_ns, getter_AddRefs(divElem)); + + NS_ENSURE_SUCCESS_VOID(rv); + + // We need the document + nsCOMPtr<Document> doc; + rv = m_editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS_VOID(rv); + + // Break up the text by newlines, and then insert text nodes followed + // by <br> nodes. + int32_t start = 0; + int32_t end = aText.Length(); + + for (;;) { + int32_t delimiter = aText.FindChar('\n', start); + if (delimiter == kNotFound) delimiter = end; + + RefPtr<nsTextNode> textNode = + doc->CreateTextNode(Substring(aText, start, delimiter - start)); + + IgnoredErrorResult rv2; + divElem->AppendChild(*textNode, rv2); + if (rv2.Failed()) { + return; + } + + // Now create and insert a BR + RefPtr<Element> brElem; + rv = + htmlEditor->CreateElementWithDefaults(u"br"_ns, getter_AddRefs(brElem)); + NS_ENSURE_SUCCESS_VOID(rv); + divElem->AppendChild(*brElem, rv2); + if (rv2.Failed()) { + return; + } + + if (delimiter == end) break; + start = ++delimiter; + if (start == end) break; + } + + htmlEditor->InsertElementAtSelection(divElem, true); + nsCOMPtr<nsINode> parent; + int32_t offset; + + rv = GetNodeLocation(divElem, address_of(parent), &offset); + if (NS_SUCCEEDED(rv)) { + RefPtr<Selection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + + if (selection) selection->CollapseInLimiter(parent, offset + 1); + } + if (divElem) { + RefPtr<Element> divElem2 = divElem; + IgnoredErrorResult rv2; + divElem2->SetAttribute(u"class"_ns, classStr, rv2); + } +} + +/* + * The following function replaces <plaintext> tags with <x-plaintext>. + * <plaintext> is a funny beast: It leads to everything following it + * being displayed verbatim, even a </plaintext> tag is ignored. + */ +static void remove_plaintext_tag(nsString& body) { + // Replace all <plaintext> and </plaintext> tags. + int32_t index = 0; + bool replaced = false; + while ((index = body.LowerCaseFindASCII("<plaintext", index)) != kNotFound) { + body.Insert(u"x-", index + 1); + index += 12; + replaced = true; + } + if (replaced) { + index = 0; + while ((index = body.LowerCaseFindASCII("</plaintext", index)) != + kNotFound) { + body.Insert(u"x-", index + 2); + index += 13; + } + } +} + +static void remove_conditional_CSS(const nsAString& in, nsAString& out) { + nsCOMPtr<nsIParserUtils> parserUtils = + do_GetService(NS_PARSERUTILS_CONTRACTID); + parserUtils->RemoveConditionalCSS(in, out); +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix, nsString& aBuf, + nsString& aSignature, bool aQuoted, + bool aHTMLEditor) { + NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor"); + NS_ENSURE_TRUE(m_editor && m_identity, NS_ERROR_NOT_INITIALIZED); + + // First, get the nsIEditor interface for future use + nsCOMPtr<nsINode> nodeInserted; + + TranslateLineEnding(aPrefix); + TranslateLineEnding(aBuf); + TranslateLineEnding(aSignature); + + m_editor->EnableUndo(false); + + // Ok - now we need to figure out the charset of the aBuf we are going to send + // into the editor shell. There are I18N calls to sniff the data and then we + // need to call the new routine in the editor that will allow us to send in + // the charset + // + + // Now, insert it into the editor... + RefPtr<HTMLEditor> htmlEditor = m_editor->AsHTMLEditor(); + int32_t reply_on_top = 0; + bool sig_bottom = true; + m_identity->GetReplyOnTop(&reply_on_top); + m_identity->GetSigBottom(&sig_bottom); + bool sigOnTop = (reply_on_top == 1 && !sig_bottom); + bool isForwarded = (mType == nsIMsgCompType::ForwardInline); + + // When in paragraph mode, don't call InsertLineBreak() since that inserts + // a full paragraph instead of just a line break since we switched + // the default paragraph separator to "p". + bool paragraphMode = + mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false); + + if (aQuoted) { + if (!aPrefix.IsEmpty()) { + if (!aHTMLEditor) aPrefix.AppendLiteral("\n"); + + int32_t reply_on_top = 0; + m_identity->GetReplyOnTop(&reply_on_top); + if (reply_on_top == 1) { + // HTML editor eats one line break but not a whole paragraph. + if (aHTMLEditor && !paragraphMode) htmlEditor->InsertLineBreak(); + + // add one newline if a signature comes before the quote, two otherwise + bool includeSignature = true; + bool sig_bottom = true; + bool attachFile = false; + nsString prefSigText; + + m_identity->GetSigOnReply(&includeSignature); + m_identity->GetSigBottom(&sig_bottom); + m_identity->GetHtmlSigText(prefSigText); + nsresult rv = m_identity->GetAttachSignature(&attachFile); + if (!paragraphMode || !aHTMLEditor) { + if (includeSignature && !sig_bottom && + ((NS_SUCCEEDED(rv) && attachFile) || !prefSigText.IsEmpty())) + htmlEditor->InsertLineBreak(); + else { + htmlEditor->InsertLineBreak(); + htmlEditor->InsertLineBreak(); + } + } + } + + InsertDivWrappedTextAtSelection(aPrefix, u"moz-cite-prefix"_ns); + } + + if (!aBuf.IsEmpty()) { + // This leaves the caret at the right place to insert a bottom signature. + if (aHTMLEditor) { + nsAutoString body(aBuf); + remove_plaintext_tag(body); + htmlEditor->InsertAsCitedQuotation(body, mCiteReference, true, + getter_AddRefs(nodeInserted)); + } else { + htmlEditor->InsertAsQuotation(aBuf, getter_AddRefs(nodeInserted)); + } + } + + (void)TagEmbeddedObjects(htmlEditor); + + if (!aSignature.IsEmpty()) { + // we cannot add it on top earlier, because TagEmbeddedObjects will mark + // all images in the signature as "moz-do-not-send" + if (sigOnTop) MoveToBeginningOfDocument(); + + if (aHTMLEditor) { + bool oldAllow; + GetAllowRemoteContent(&oldAllow); + SetAllowRemoteContent(true); + htmlEditor->InsertHTML(aSignature); + SetAllowRemoteContent(oldAllow); + } else { + htmlEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns); + } + + if (sigOnTop) htmlEditor->EndOfDocument(); + } + } else { + if (aHTMLEditor) { + if (isForwarded && + Substring(aBuf, 0, sizeof(MIME_FORWARD_HTML_PREFIX) - 1) + .EqualsLiteral(MIME_FORWARD_HTML_PREFIX)) { + // We assign the opening tag inside "<HTML><BODY><BR><BR>" before the + // two <br> elements. + // This is a bit hacky but we know that the MIME code prepares the + // forwarded content like this: + // <HTML><BODY><BR><BR> + forwarded header + header table. + // Note: We only do this when we prepare the message to be forwarded, + // a re-opened saved draft of a forwarded message does not repeat this. + nsString divTag; + divTag.AssignLiteral("<div class=\"moz-forward-container\">"); + aBuf.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX) - 1 - 8); + } + remove_plaintext_tag(aBuf); + + bool stripConditionalCSS = mozilla::Preferences::GetBool( + "mail.html_sanitize.drop_conditional_css", true); + + if (stripConditionalCSS) { + nsString newBody; + remove_conditional_CSS(aBuf, newBody); + htmlEditor->RebuildDocumentFromSource(newBody); + } else { + htmlEditor->RebuildDocumentFromSource(aBuf); + } + + // When forwarding a message as inline, or editing as new (which could + // contain unsanitized remote content), tag any embedded objects + // with moz-do-not-send=true so they don't get attached upon send. + if (isForwarded || mType == nsIMsgCompType::EditAsNew) + (void)TagEmbeddedObjects(htmlEditor); + + if (!aSignature.IsEmpty()) { + if (isForwarded && sigOnTop) { + // Use our own function, nsEditor::BeginningOfDocument() would + // position into the <div class="moz-forward-container"> we've just + // created. + MoveToBeginningOfDocument(); + } else { + // Use our own function, nsEditor::EndOfDocument() would position + // into the <div class="moz-forward-container"> we've just created. + MoveToEndOfDocument(); + } + + bool oldAllow; + GetAllowRemoteContent(&oldAllow); + SetAllowRemoteContent(true); + htmlEditor->InsertHTML(aSignature); + SetAllowRemoteContent(oldAllow); + + if (isForwarded && sigOnTop) htmlEditor->EndOfDocument(); + } else + htmlEditor->EndOfDocument(); + } else { + bool sigOnTopInserted = false; + if (isForwarded && sigOnTop && !aSignature.IsEmpty()) { + htmlEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns); + htmlEditor->EndOfDocument(); + sigOnTopInserted = true; + } + + if (!aBuf.IsEmpty()) { + nsresult rv; + RefPtr<Element> divElem; + RefPtr<Element> extraBr; + + if (isForwarded) { + // Special treatment for forwarded messages: Part 1. + // Create a <div> of the required class. + rv = htmlEditor->CreateElementWithDefaults(u"div"_ns, + getter_AddRefs(divElem)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString attributeName; + nsAutoString attributeValue; + attributeName.AssignLiteral("class"); + attributeValue.AssignLiteral("moz-forward-container"); + IgnoredErrorResult rv1; + divElem->SetAttribute(attributeName, attributeValue, rv1); + + // We can't insert an empty <div>, so fill it with something. + rv = htmlEditor->CreateElementWithDefaults(u"br"_ns, + getter_AddRefs(extraBr)); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult rv2; + divElem->AppendChild(*extraBr, rv2); + if (rv2.Failed()) { + return rv2.StealNSResult(); + } + + // Insert the non-empty <div> into the DOM. + rv = htmlEditor->InsertElementAtSelection(divElem, false); + NS_ENSURE_SUCCESS(rv, rv); + + // Position into the div, so out content goes there. + RefPtr<Selection> selection; + htmlEditor->GetSelection(getter_AddRefs(selection)); + rv = selection->CollapseInLimiter(divElem, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = htmlEditor->InsertTextWithQuotations(aBuf); + NS_ENSURE_SUCCESS(rv, rv); + + if (isForwarded) { + // Special treatment for forwarded messages: Part 2. + if (sigOnTopInserted) { + // Sadly the M-C editor inserts a <br> between the <div> for the + // signature and this <div>, so remove the <br> we don't want. + nsCOMPtr<nsINode> brBeforeDiv; + nsAutoString tagLocalName; + brBeforeDiv = divElem->GetPreviousSibling(); + if (brBeforeDiv) { + tagLocalName = brBeforeDiv->LocalName(); + if (tagLocalName.EqualsLiteral("br")) { + rv = htmlEditor->DeleteNode(brBeforeDiv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + // Clean up the <br> we inserted. + rv = htmlEditor->DeleteNode(extraBr); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Use our own function instead of nsEditor::EndOfDocument() because + // we don't want to position at the end of the div we've just created. + // It's OK to use, even if we're not forwarding and didn't create a + // <div>. + rv = MoveToEndOfDocument(); + NS_ENSURE_SUCCESS(rv, rv); + } + + if ((!isForwarded || !sigOnTop) && !aSignature.IsEmpty()) { + htmlEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns); + } + } + } + + if (aBuf.IsEmpty()) + htmlEditor->BeginningOfDocument(); + else { + switch (reply_on_top) { + // This should set the cursor after the body but before the sig + case 0: { + if (!htmlEditor) { + htmlEditor->BeginningOfDocument(); + break; + } + + RefPtr<Selection> selection; + nsCOMPtr<nsINode> parent; + int32_t offset; + nsresult rv; + + // get parent and offset of mailcite + rv = GetNodeLocation(nodeInserted, address_of(parent), &offset); + if (NS_FAILED(rv) || (!parent)) { + htmlEditor->BeginningOfDocument(); + break; + } + + // get selection + htmlEditor->GetSelection(getter_AddRefs(selection)); + if (!selection) { + htmlEditor->BeginningOfDocument(); + break; + } + + // place selection after mailcite + selection->CollapseInLimiter(parent, offset + 1); + + // insert a break at current selection + if (!paragraphMode || !aHTMLEditor) htmlEditor->InsertLineBreak(); + + // i'm not sure if you need to move the selection back to before the + // break. expirement. + selection->CollapseInLimiter(parent, offset + 1); + + break; + } + + case 2: { + nsCOMPtr<nsIEditor> editor(htmlEditor); // Strong reference. + editor->SelectAll(); + break; + } + + // This should set the cursor to the top! + default: { + MoveToBeginningOfDocument(); + break; + } + } + } + + nsCOMPtr<nsISelectionController> selCon; + htmlEditor->GetSelectionController(getter_AddRefs(selCon)); + + if (selCon) + selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_ANCHOR_REGION, true); + + htmlEditor->EnableUndo(true); + SetBodyModified(false); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + nsCOMPtr<nsIMsgComposeService> composeService( + do_GetService("@mozilla.org/messengercompose;1")); + composeService->TimeStamp( + "Finished inserting data into the editor. The window is finally ready!", + false); +#endif + return NS_OK; +} + +/** + * Check the identity pref to include signature on replies and forwards. + */ +bool nsMsgCompose::CheckIncludeSignaturePrefs(nsIMsgIdentity* identity) { + bool includeSignature = true; + switch (mType) { + case nsIMsgCompType::ForwardInline: + case nsIMsgCompType::ForwardAsAttachment: + identity->GetSigOnForward(&includeSignature); + break; + case nsIMsgCompType::Reply: + case nsIMsgCompType::ReplyAll: + case nsIMsgCompType::ReplyToList: + case nsIMsgCompType::ReplyToGroup: + case nsIMsgCompType::ReplyToSender: + case nsIMsgCompType::ReplyToSenderAndGroup: + identity->GetSigOnReply(&includeSignature); + break; + } + return includeSignature; +} + +nsresult nsMsgCompose::SetQuotingToFollow(bool aVal) { + mQuotingToFollow = aVal; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::GetQuotingToFollow(bool* quotingToFollow) { + NS_ENSURE_ARG(quotingToFollow); + *quotingToFollow = mQuotingToFollow; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::Initialize(nsIMsgComposeParams* aParams, + mozIDOMWindowProxy* aWindow, nsIDocShell* aDocShell) { + NS_ENSURE_ARG_POINTER(aParams); + nsresult rv; + + aParams->GetIdentity(getter_AddRefs(m_identity)); + + if (aWindow) { + m_window = aWindow; + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShellTreeItem> treeItem = window->GetDocShell(); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_FAILED(rv)) return rv; + + m_baseWindow = do_QueryInterface(treeOwner); + } + + aParams->GetAutodetectCharset(&mAutodetectCharset); + + MSG_ComposeFormat format; + aParams->GetFormat(&format); + + MSG_ComposeType type; + aParams->GetType(&type); + + nsCString originalMsgURI; + aParams->GetOriginalMsgURI(originalMsgURI); + aParams->GetOrigMsgHdr(getter_AddRefs(mOrigMsgHdr)); + + nsCOMPtr<nsIMsgCompFields> composeFields; + aParams->GetComposeFields(getter_AddRefs(composeFields)); + + nsCOMPtr<nsIMsgComposeService> composeService = + do_GetService("@mozilla.org/messengercompose;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = composeService->DetermineComposeHTML(m_identity, format, &m_composeHTML); + NS_ENSURE_SUCCESS(rv, rv); + +#ifndef MOZ_SUITE + if (m_composeHTML) { + Telemetry::ScalarAdd(Telemetry::ScalarID::TB_COMPOSE_FORMAT_HTML, 1); + } else { + Telemetry::ScalarAdd(Telemetry::ScalarID::TB_COMPOSE_FORMAT_PLAIN_TEXT, 1); + } + Telemetry::Accumulate(Telemetry::TB_COMPOSE_TYPE, type); +#endif + + if (composeFields) { + nsAutoCString draftId; // will get set for drafts and templates + rv = composeFields->GetDraftId(draftId); + NS_ENSURE_SUCCESS(rv, rv); + + // Set return receipt flag and type, and if we should attach a vCard + // by checking the identity prefs - but don't clobber the values for + // drafts and templates as they were set up already by mime when + // initializing the message. + if (m_identity && draftId.IsEmpty() && type != nsIMsgCompType::Template) { + bool requestReturnReceipt = false; + rv = m_identity->GetRequestReturnReceipt(&requestReturnReceipt); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetReturnReceipt(requestReturnReceipt); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t receiptType = nsIMsgMdnGenerator::eDntType; + rv = m_identity->GetReceiptHeaderType(&receiptType); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetReceiptHeaderType(receiptType); + NS_ENSURE_SUCCESS(rv, rv); + + bool requestDSN = false; + rv = m_identity->GetRequestDSN(&requestDSN); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetDSN(requestDSN); + NS_ENSURE_SUCCESS(rv, rv); + + bool attachVCard; + rv = m_identity->GetAttachVCard(&attachVCard); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetAttachVCard(attachVCard); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + nsCOMPtr<nsIMsgSendListener> externalSendListener; + aParams->GetSendListener(getter_AddRefs(externalSendListener)); + if (externalSendListener) AddMsgSendListener(externalSendListener); + + nsString smtpPassword; + aParams->GetSmtpPassword(smtpPassword); + mSmtpPassword = smtpPassword; + + aParams->GetHtmlToQuote(mHtmlToQuote); + + if (aDocShell) { + mDocShell = aDocShell; + // register the compose object with the compose service + rv = composeService->RegisterComposeDocShell(aDocShell, this); + NS_ENSURE_SUCCESS(rv, rv); + } + return CreateMessage(originalMsgURI, type, composeFields); +} + +NS_IMETHODIMP +nsMsgCompose::RegisterStateListener( + nsIMsgComposeStateListener* aStateListener) { + NS_ENSURE_ARG_POINTER(aStateListener); + mStateListeners.AppendElement(aStateListener); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::UnregisterStateListener( + nsIMsgComposeStateListener* aStateListener) { + NS_ENSURE_ARG_POINTER(aStateListener); + return mStateListeners.RemoveElement(aStateListener) ? NS_OK + : NS_ERROR_FAILURE; +} + +// Added to allow easier use of the nsIMsgSendListener +NS_IMETHODIMP nsMsgCompose::AddMsgSendListener( + nsIMsgSendListener* aMsgSendListener) { + NS_ENSURE_ARG_POINTER(aMsgSendListener); + mExternalSendListeners.AppendElement(aMsgSendListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::RemoveMsgSendListener( + nsIMsgSendListener* aMsgSendListener) { + NS_ENSURE_ARG_POINTER(aMsgSendListener); + return mExternalSendListeners.RemoveElement(aMsgSendListener) + ? NS_OK + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgCompose::SendMsgToServer(MSG_DeliverMode deliverMode, + nsIMsgIdentity* identity, const char* accountKey, + Promise** aPromise) { + nsresult rv = NS_OK; + + // clear saved message id if sending, so we don't send out the same + // message-id. + if (deliverMode == nsIMsgCompDeliverMode::Now || + deliverMode == nsIMsgCompDeliverMode::Later || + deliverMode == nsIMsgCompDeliverMode::Background) + m_compFields->SetMessageId(""); + + if (m_compFields && identity) { + // Pref values are supposed to be stored as UTF-8, so no conversion + nsCString email; + nsString fullName; + nsString organization; + + identity->GetEmail(email); + identity->GetFullName(fullName); + identity->GetOrganization(organization); + + const char* pFrom = m_compFields->GetFrom(); + if (!pFrom || !*pFrom) { + nsCString sender; + MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), email, sender); + m_compFields->SetFrom(sender.IsEmpty() ? email.get() : sender.get()); + } + + m_compFields->SetOrganization(organization); + + // We need an nsIMsgSend instance to send the message. Allow extensions + // to override the default SMTP sender by observing mail-set-sender. + mMsgSend = nullptr; + mDeliverMode = deliverMode; // save for possible access by observer. + + // Allow extensions to specify an outgoing server. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_STATE(observerService); + + // Assemble a string with sending parameters. + nsAutoString sendParms; + + // First parameter: account key. This may be null. + sendParms.AppendASCII(accountKey && *accountKey ? accountKey : ""); + sendParms.Append(','); + + // Second parameter: deliverMode. + sendParms.AppendInt(deliverMode); + sendParms.Append(','); + + // Third parameter: identity (as identity key). + nsAutoCString identityKey; + identity->GetKey(identityKey); + sendParms.AppendASCII(identityKey.get()); + + observerService->NotifyObservers(NS_ISUPPORTS_CAST(nsIMsgCompose*, this), + "mail-set-sender", sendParms.get()); + + if (!mMsgSend) + mMsgSend = do_CreateInstance("@mozilla.org/messengercompose/send;1"); + + if (mMsgSend) { + nsString bodyString; + rv = m_compFields->GetBody(bodyString); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the listener for the send operation... + nsCOMPtr<nsIMsgComposeSendListener> composeSendListener = + do_CreateInstance( + "@mozilla.org/messengercompose/composesendlistener;1"); + if (!composeSendListener) return NS_ERROR_OUT_OF_MEMORY; + + // right now, AutoSaveAsDraft is identical to SaveAsDraft as + // far as the msg send code is concerned. This way, we don't have + // to add an nsMsgDeliverMode for autosaveasdraft, and add cases for + // it in the msg send code. + if (deliverMode == nsIMsgCompDeliverMode::AutoSaveAsDraft) + deliverMode = nsIMsgCompDeliverMode::SaveAsDraft; + + RefPtr<nsIMsgCompose> msgCompose(this); + composeSendListener->SetMsgCompose(msgCompose); + composeSendListener->SetDeliverMode(deliverMode); + + if (mProgress) { + nsCOMPtr<nsIWebProgressListener> progressListener = + do_QueryInterface(composeSendListener); + mProgress->RegisterListener(progressListener); + } + + // If we are composing HTML, then this should be sent as + // multipart/related which means we pass the editor into the + // backend...if not, just pass nullptr + // + nsCOMPtr<nsIMsgSendListener> sendListener = + do_QueryInterface(composeSendListener); + RefPtr<mozilla::dom::Promise> promise; + rv = mMsgSend->CreateAndSendMessage( + m_composeHTML ? m_editor.get() : nullptr, identity, accountKey, + m_compFields, false, false, (nsMsgDeliverMode)deliverMode, nullptr, + m_composeHTML ? TEXT_HTML : TEXT_PLAIN, bodyString, m_window, + mProgress, sendListener, mSmtpPassword, mOriginalMsgURI, mType, + getter_AddRefs(promise)); + promise.forget(aPromise); + } else + rv = NS_ERROR_FAILURE; + } else + rv = NS_ERROR_NOT_INITIALIZED; + + return rv; +} + +NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode, + nsIMsgIdentity* identity, + const char* accountKey, + nsIMsgWindow* aMsgWindow, + nsIMsgProgress* progress, + Promise** aPromise) { + NS_ENSURE_TRUE(m_compFields, NS_ERROR_NOT_INITIALIZED); + nsresult rv = NS_OK; + + // Set content type based on which type of compose window we had. + nsString contentType = (m_composeHTML) ? u"text/html"_ns : u"text/plain"_ns; + nsString msgBody; + if (m_editor) { + // Reset message body previously stored in the compose fields + m_compFields->SetBody(EmptyString()); + + uint32_t flags = nsIDocumentEncoder::OutputCRLineBreak | + nsIDocumentEncoder::OutputLFLineBreak; + + if (m_composeHTML) { + flags |= nsIDocumentEncoder::OutputFormatted | + nsIDocumentEncoder::OutputDisallowLineBreaking; + } else { + bool flowed, formatted; + GetSerialiserFlags(&flowed, &formatted); + if (flowed) flags |= nsIDocumentEncoder::OutputFormatFlowed; + if (formatted) flags |= nsIDocumentEncoder::OutputFormatted; + flags |= nsIDocumentEncoder::OutputDisallowLineBreaking; + // Don't lose NBSP in the plain text encoder. + flags |= nsIDocumentEncoder::OutputPersistNBSP; + } + nsresult rv = m_editor->OutputToString(contentType, flags, msgBody); + NS_ENSURE_SUCCESS(rv, rv); + } else { + m_compFields->GetBody(msgBody); + } + if (!msgBody.IsEmpty()) { + // Ensure body ends in CRLF to avoid SMTP server timeout when sent. + if (!StringEndsWith(msgBody, u"\r\n"_ns)) msgBody.AppendLiteral("\r\n"); + bool isAsciiOnly = mozilla::IsAsciiNullTerminated( + static_cast<const char16_t*>(msgBody.get())); + + if (m_compFields->GetForceMsgEncoding()) { + isAsciiOnly = false; + } + + m_compFields->SetBodyIsAsciiOnly(isAsciiOnly); + m_compFields->SetBody(msgBody); + } + + // Let's open the progress dialog + if (progress) { + mProgress = progress; + + if (deliverMode != nsIMsgCompDeliverMode::AutoSaveAsDraft) { + nsAutoString msgSubject; + m_compFields->GetSubject(msgSubject); + + bool showProgress = false; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) { + prefBranch->GetBoolPref("mailnews.show_send_progress", &showProgress); + if (showProgress) { + nsCOMPtr<nsIMsgComposeProgressParams> params = do_CreateInstance( + "@mozilla.org/messengercompose/composeprogressparameters;1", &rv); + if (NS_FAILED(rv) || !params) return NS_ERROR_FAILURE; + + params->SetSubject(msgSubject.get()); + params->SetDeliveryMode(deliverMode); + + mProgress->OpenProgressDialog( + m_window, aMsgWindow, + "chrome://messenger/content/messengercompose/sendProgress.xhtml", + false, params); + } + } + } + + mProgress->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_START, NS_OK); + } + + bool attachVCard = false; + m_compFields->GetAttachVCard(&attachVCard); + + if (attachVCard && identity && + (deliverMode == nsIMsgCompDeliverMode::Now || + deliverMode == nsIMsgCompDeliverMode::Later || + deliverMode == nsIMsgCompDeliverMode::Background)) { + nsCString escapedVCard; + // make sure, if there is no card, this returns an empty string, or + // NS_ERROR_FAILURE + rv = identity->GetEscapedVCard(escapedVCard); + + if (NS_SUCCEEDED(rv) && !escapedVCard.IsEmpty()) { + nsCString vCardUrl; + vCardUrl = "data:text/vcard;charset=utf-8;base64,"; + nsCString unescapedData; + MsgUnescapeString(escapedVCard, 0, unescapedData); + char* result = PL_Base64Encode(unescapedData.get(), 0, nullptr); + vCardUrl += result; + PR_Free(result); + + nsCOMPtr<nsIMsgAttachment> attachment = + do_CreateInstance("@mozilla.org/messengercompose/attachment;1", &rv); + if (NS_SUCCEEDED(rv) && attachment) { + // [comment from 4.x] + // Send the vCard out with a filename which distinguishes this user. + // e.g. jsmith.vcf The main reason to do this is for interop with + // Eudora, which saves off the attachments separately from the message + // body + nsCString userid; + (void)identity->GetEmail(userid); + int32_t index = userid.FindChar('@'); + if (index != kNotFound) userid.SetLength(index); + + if (userid.IsEmpty()) + attachment->SetName(u"vcard.vcf"_ns); + else { + // Replace any dot with underscore to stop vCards + // generating false positives with some heuristic scanners + userid.ReplaceChar('.', '_'); + userid.AppendLiteral(".vcf"); + attachment->SetName(NS_ConvertASCIItoUTF16(userid)); + } + + attachment->SetUrl(vCardUrl); + m_compFields->AddAttachment(attachment); + } + } + } + + // Save the identity being sent for later use. + m_identity = identity; + + RefPtr<mozilla::dom::Promise> promise; + rv = SendMsgToServer(deliverMode, identity, accountKey, + getter_AddRefs(promise)); + + RefPtr<nsMsgCompose> self = this; + auto handleFailure = [self = std::move(self), deliverMode](nsresult rv) { + self->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeProcessDone, rv); + nsCOMPtr<nsIMsgSendReport> sendReport; + if (self->mMsgSend) + self->mMsgSend->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) { + nsresult theError; + sendReport->DisplayReport(self->m_window, true, true, &theError); + } else { + // If we come here it's because we got an error before we could initialize + // a send report! Let's try our best... + switch (deliverMode) { + case nsIMsgCompDeliverMode::Later: + nsMsgDisplayMessageByName(self->m_window, "unableToSendLater"); + break; + case nsIMsgCompDeliverMode::AutoSaveAsDraft: + case nsIMsgCompDeliverMode::SaveAsDraft: + nsMsgDisplayMessageByName(self->m_window, "unableToSaveDraft"); + break; + case nsIMsgCompDeliverMode::SaveAsTemplate: + nsMsgDisplayMessageByName(self->m_window, "unableToSaveTemplate"); + break; + + default: + nsMsgDisplayMessageByName(self->m_window, "sendFailed"); + break; + } + } + if (self->mProgress) self->mProgress->CloseProgressDialog(true); + + self->DeleteTmpAttachments(); + }; + if (promise) { + RefPtr<DomPromiseListener> listener = new DomPromiseListener( + [&](JSContext*, JS::Handle<JS::Value>) { DeleteTmpAttachments(); }, + handleFailure); + promise->AppendNativeHandler(listener); + promise.forget(aPromise); + } else if (NS_FAILED(rv)) { + handleFailure(rv); + } + + return rv; +} + +NS_IMETHODIMP nsMsgCompose::GetDeleteDraft(bool* aDeleteDraft) { + NS_ENSURE_ARG_POINTER(aDeleteDraft); + *aDeleteDraft = mDeleteDraft; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetDeleteDraft(bool aDeleteDraft) { + mDeleteDraft = aDeleteDraft; + return NS_OK; +} + +bool nsMsgCompose::IsLastWindow() { + nsresult rv; + bool more; + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsISimpleEnumerator> windowEnumerator; + rv = windowMediator->GetEnumerator(nullptr, + getter_AddRefs(windowEnumerator)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsISupports> isupports; + + if (NS_SUCCEEDED(windowEnumerator->GetNext(getter_AddRefs(isupports)))) + if (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more))) + return !more; + } + } + return true; +} + +NS_IMETHODIMP nsMsgCompose::CloseWindow(void) { + nsresult rv; + + nsCOMPtr<nsIMsgComposeService> composeService = + do_GetService("@mozilla.org/messengercompose;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // unregister the compose object with the compose service + rv = composeService->UnregisterComposeDocShell(mDocShell); + NS_ENSURE_SUCCESS(rv, rv); + mDocShell = nullptr; + + // ensure that the destructor of nsMsgSend is invoked to remove + // temporary files. + mMsgSend = nullptr; + + // We are going away for real, we need to do some clean up first + if (m_baseWindow) { + if (m_editor) { + // The editor will be destroyed during the close window. + // Set it to null to be sure we won't use it anymore. + m_editor = nullptr; + } + nsCOMPtr<nsIBaseWindow> window = m_baseWindow.forget(); + rv = window->Destroy(); + } + + m_window = nullptr; + return rv; +} + +nsresult nsMsgCompose::Abort() { + if (mMsgSend) mMsgSend->Abort(); + + if (mProgress) mProgress->CloseProgressDialog(true); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetEditor(nsIEditor** aEditor) { + NS_IF_ADDREF(*aEditor = m_editor); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetEditor(nsIEditor* aEditor) { + m_editor = aEditor; + return NS_OK; +} + +// This used to be called BEFORE editor was created +// (it did the loadURI that triggered editor creation) +// It is called from JS after editor creation +// (loadURI is done in JS) +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsMsgCompose::InitEditor( + nsIEditor* aEditor, mozIDOMWindowProxy* aContentWindow) { + NS_ENSURE_ARG_POINTER(aEditor); + NS_ENSURE_ARG_POINTER(aContentWindow); + nsresult rv; + + m_editor = aEditor; + + aEditor->SetDocumentCharacterSet("UTF-8"_ns); + + nsCOMPtr<nsPIDOMWindowOuter> window = + nsPIDOMWindowOuter::From(aContentWindow); + + nsIDocShell* docShell = window->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); + + bool quotingToFollow = false; + GetQuotingToFollow("ingToFollow); + if (quotingToFollow) + return BuildQuotedMessageAndSignature(); + else { + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady, + NS_OK); + rv = BuildBodyMessageAndSignature(); + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady, + NS_OK); + return rv; + } +} + +nsresult nsMsgCompose::GetBodyModified(bool* modified) { + nsresult rv; + + if (!modified) return NS_ERROR_NULL_POINTER; + + *modified = true; + + if (m_editor) { + rv = m_editor->GetDocumentModified(modified); + if (NS_FAILED(rv)) *modified = true; + } + + return NS_OK; +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult +nsMsgCompose::SetBodyModified(bool modified) { + nsresult rv = NS_OK; + + if (m_editor) { + nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference. + if (modified) { + int32_t modCount = 0; + editor->GetModificationCount(&modCount); + if (modCount == 0) editor->IncrementModificationCount(1); + } else + editor->ResetModificationCount(); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::GetDomWindow(mozIDOMWindowProxy** aDomWindow) { + NS_IF_ADDREF(*aDomWindow = m_window); + return NS_OK; +} + +nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields** aCompFields) { + NS_IF_ADDREF(*aCompFields = (nsIMsgCompFields*)m_compFields); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetComposeHTML(bool* aComposeHTML) { + *aComposeHTML = m_composeHTML; + return NS_OK; +} + +nsresult nsMsgCompose::GetWrapLength(int32_t* aWrapLength) { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + return prefBranch->GetIntPref("mailnews.wraplength", aWrapLength); +} + +nsresult nsMsgCompose::CreateMessage(const nsACString& originalMsgURI, + MSG_ComposeType type, + nsIMsgCompFields* compFields) { + nsresult rv = NS_OK; + mType = type; + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None; + + mDeleteDraft = (type == nsIMsgCompType::Draft); + nsAutoCString msgUri(originalMsgURI); + bool fileUrl = StringBeginsWith(msgUri, "file:"_ns); + int32_t typeIndex = msgUri.Find("type=application/x-message-display"); + if (typeIndex != kNotFound && typeIndex > 0) { + // Strip out type=application/x-message-display because it confuses libmime. + msgUri.Cut(typeIndex, sizeof("type=application/x-message-display")); + if (fileUrl) // we're dealing with an .eml file msg + { + // We have now removed the type from the uri. Make sure we don't have + // an uri with "&&" now. If we do, remove the second '&'. + if (msgUri.CharAt(typeIndex) == '&') msgUri.Cut(typeIndex, 1); + // Remove possible trailing '?'. + if (msgUri.CharAt(msgUri.Length() - 1) == '?') + msgUri.Cut(msgUri.Length() - 1, 1); + } else // we're dealing with a message/rfc822 attachment + { + // nsURLFetcher will check for "realtype=message/rfc822" and will set the + // content type to message/rfc822 in the forwarded message. + msgUri.AppendLiteral("&realtype=message/rfc822"); + } + } + + if (compFields) { + m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields); + } else { + m_compFields = new nsMsgCompFields(); + } + + if (m_identity && mType != nsIMsgCompType::Draft) { + // Setup reply-to field. + nsCString replyTo; + m_identity->GetReplyTo(replyTo); + if (!replyTo.IsEmpty()) { + nsCString resultStr; + RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetReplyTo()), + replyTo, resultStr); + if (!resultStr.IsEmpty()) { + replyTo.Append(','); + replyTo.Append(resultStr); + } + m_compFields->SetReplyTo(replyTo.get()); + } + + // Setup auto-Cc field. + bool doCc; + m_identity->GetDoCc(&doCc); + if (doCc) { + nsCString ccList; + m_identity->GetDoCcList(ccList); + + nsCString resultStr; + RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetCc()), + ccList, resultStr); + if (!resultStr.IsEmpty()) { + ccList.Append(','); + ccList.Append(resultStr); + } + m_compFields->SetCc(ccList.get()); + } + + // Setup auto-Bcc field. + bool doBcc; + m_identity->GetDoBcc(&doBcc); + if (doBcc) { + nsCString bccList; + m_identity->GetDoBccList(bccList); + + nsCString resultStr; + RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetBcc()), + bccList, resultStr); + if (!resultStr.IsEmpty()) { + bccList.Append(','); + bccList.Append(resultStr); + } + m_compFields->SetBcc(bccList.get()); + } + } + + if (mType == nsIMsgCompType::Draft) { + nsCString curDraftIdURL; + rv = m_compFields->GetDraftId(curDraftIdURL); + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) { + nsCOMPtr<nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(msgDBHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv), + "CreateMessage can't get msg header DB interface pointer."); + if (msgDBHdr) { + nsCString queuedDisposition; + msgDBHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, + queuedDisposition); + // We need to retrieve the original URI from the database so we can + // set the disposition flags correctly if the draft is a reply or + // forwarded message. + nsCString originalMsgURIfromDB; + msgDBHdr->GetStringProperty(ORIG_URI_PROPERTY, originalMsgURIfromDB); + mOriginalMsgURI = originalMsgURIfromDB; + if (!queuedDisposition.IsEmpty()) { + if (queuedDisposition.EqualsLiteral("replied")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied; + else if (queuedDisposition.EqualsLiteral("forward")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded; + else if (queuedDisposition.EqualsLiteral("redirected")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Redirected; + } + } + } else { + NS_WARNING("CreateMessage can't get draft id"); + } + } + + // If we don't have an original message URI, nothing else to do... + if (msgUri.IsEmpty()) return NS_OK; + + // store the original message URI so we can extract it after we send the + // message to properly mark any disposition flags like replied or forwarded on + // the message. + if (mOriginalMsgURI.IsEmpty()) mOriginalMsgURI = msgUri; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // "Forward inline" and "Reply with template" processing. + // Note the early return at the end of the block. + if (type == nsIMsgCompType::ForwardInline || + type == nsIMsgCompType::ReplyWithTemplate) { + // We want to treat this message as a reference too + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(msgUri, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + + nsAutoCString reference; + // When forwarding we only use the original message for "References:" - + // recipients don't have the other messages anyway. + // For reply with template we want to preserve all the references. + if (type == nsIMsgCompType::ReplyWithTemplate) { + uint16_t numReferences = 0; + msgHdr->GetNumReferences(&numReferences); + for (int32_t i = 0; i < numReferences; i++) { + nsAutoCString ref; + msgHdr->GetStringReference(i, ref); + if (!ref.IsEmpty()) { + reference.Append('<'); + reference.Append(ref); + reference.AppendLiteral("> "); + } + } + reference.Trim(" ", false, true); + } + msgHdr->GetMessageId(getter_Copies(messageId)); + reference.Append('<'); + reference.Append(messageId); + reference.Append('>'); + m_compFields->SetReferences(reference.get()); + + if (type == nsIMsgCompType::ForwardInline) { + nsString subject; + msgHdr->GetMime2DecodedSubject(subject); + nsCString fwdPrefix; + prefs->GetCharPrefWithDefault("mail.forward_subject_prefix", "Fwd"_ns, + 1, fwdPrefix); + nsString unicodeFwdPrefix; + CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix); + unicodeFwdPrefix.AppendLiteral(": "); + subject.Insert(unicodeFwdPrefix, 0); + m_compFields->SetSubject(subject); + } + } + + // Early return for "ForwardInline" and "ReplyWithTemplate" processing. + return NS_OK; + } + + // All other processing. + + // Note the following: + // LoadDraftOrTemplate() is run in nsMsgComposeService::OpenComposeWindow() + // for five compose types: ForwardInline, ReplyWithTemplate (both covered + // in the code block above) and Draft, Template and Redirect. For these + // compose types, the charset is already correct (incl. MIME-applied override) + // unless the default charset should be used. + + bool isFirstPass = true; + char* uriList = ToNewCString(msgUri); + char* uri = uriList; + char* nextUri; + do { + nextUri = strstr(uri, "://"); + if (nextUri) { + // look for next ://, and then back up to previous ',' + nextUri = strstr(nextUri + 1, "://"); + if (nextUri) { + *nextUri = '\0'; + char* saveNextUri = nextUri; + nextUri = strrchr(uri, ','); + if (nextUri) *nextUri = '\0'; + *saveNextUri = ':'; + } + } + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + if (mOrigMsgHdr) + msgHdr = mOrigMsgHdr; + else { + rv = GetMsgDBHdrFromURI(nsDependentCString(uri), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + } + if (msgHdr) { + nsString subject; + rv = msgHdr->GetMime2DecodedSubject(subject); + if (NS_FAILED(rv)) return rv; + + // Check if (was: is present in the subject + int32_t wasOffset = subject.RFind(u" (was:"_ns); + bool strip = true; + + if (wasOffset >= 0) { + // Check the number of references, to check if was: should be stripped + // First, assume that it should be stripped; the variable will be set to + // false later if stripping should not happen. + uint16_t numRef; + msgHdr->GetNumReferences(&numRef); + if (numRef) { + // If there are references, look for the first message in the thread + // firstly, get the database via the folder + nsCOMPtr<nsIMsgFolder> folder; + msgHdr->GetFolder(getter_AddRefs(folder)); + if (folder) { + nsCOMPtr<nsIMsgDatabase> db; + folder->GetMsgDatabase(getter_AddRefs(db)); + + if (db) { + nsAutoCString reference; + msgHdr->GetStringReference(0, reference); + + nsCOMPtr<nsIMsgDBHdr> refHdr; + db->GetMsgHdrForMessageID(reference.get(), + getter_AddRefs(refHdr)); + + if (refHdr) { + nsCString refSubject; + rv = refHdr->GetSubject(refSubject); + if (NS_SUCCEEDED(rv)) { + if (refSubject.Find(" (was:") >= 0) strip = false; + } + } + } + } + } else + strip = false; + } + + if (strip && wasOffset >= 0) { + // Strip off the "(was: old subject)" part + subject.Assign(Substring(subject, 0, wasOffset)); + } + + switch (type) { + default: + break; + case nsIMsgCompType::Draft: + case nsIMsgCompType::Template: + case nsIMsgCompType::EditTemplate: + case nsIMsgCompType::EditAsNew: { + // If opening from file, preseve the subject already present, since + // we can't get a subject from db there. + if (mOriginalMsgURI.Find("&realtype=message/rfc822") != -1) { + break; + } + // Otherwise, set up the subject from db, with possible modifications. + uint32_t flags; + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::HasRe) { + subject.InsertLiteral(u"Re: ", 0); + } + // Set subject from db, where it's already decrypted. The raw + // header may be encrypted. + m_compFields->SetSubject(subject); + break; + } + case nsIMsgCompType::Reply: + case nsIMsgCompType::ReplyAll: + case nsIMsgCompType::ReplyToList: + case nsIMsgCompType::ReplyToGroup: + case nsIMsgCompType::ReplyToSender: + case nsIMsgCompType::ReplyToSenderAndGroup: { + if (!isFirstPass) // safeguard, just in case... + { + PR_Free(uriList); + return rv; + } + mQuotingToFollow = true; + + subject.InsertLiteral(u"Re: ", 0); + m_compFields->SetSubject(subject); + + // Setup quoting callbacks for later... + mWhatHolder = 1; + break; + } + case nsIMsgCompType::ForwardAsAttachment: { + // Add the forwarded message in the references, first + nsAutoCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + if (isFirstPass) { + nsAutoCString reference; + reference.Append('<'); + reference.Append(messageId); + reference.Append('>'); + m_compFields->SetReferences(reference.get()); + } else { + nsAutoCString references; + m_compFields->GetReferences(getter_Copies(references)); + references.AppendLiteral(" <"); + references.Append(messageId); + references.Append('>'); + m_compFields->SetReferences(references.get()); + } + + uint32_t flags; + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::HasRe) + subject.InsertLiteral(u"Re: ", 0); + + // Setup quoting callbacks for later... + mQuotingToFollow = + false; // We don't need to quote the original message. + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance( + "@mozilla.org/messengercompose/attachment;1", &rv); + if (NS_SUCCEEDED(rv) && attachment) { + bool addExtension = true; + nsString sanitizedSubj; + prefs->GetBoolPref("mail.forward_add_extension", &addExtension); + + // copy subject string to sanitizedSubj, use default if empty + if (subject.IsEmpty()) { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> composeBundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/" + "composeMsgs.properties", + getter_AddRefs(composeBundle)); + NS_ENSURE_SUCCESS(rv, rv); + composeBundle->GetStringFromName("messageAttachmentSafeName", + sanitizedSubj); + } else + sanitizedSubj.Assign(subject); + + // set the file size + uint32_t messageSize; + msgHdr->GetMessageSize(&messageSize); + attachment->SetSize(messageSize); + + // change all '.' to '_' see bug #271211 + sanitizedSubj.ReplaceChar(u".", u'_'); + if (addExtension) sanitizedSubj.AppendLiteral(".eml"); + attachment->SetName(sanitizedSubj); + attachment->SetUrl(nsDependentCString(uri)); + m_compFields->AddAttachment(attachment); + } + + if (isFirstPass) { + nsCString fwdPrefix; + prefs->GetCharPrefWithDefault("mail.forward_subject_prefix", + "Fwd"_ns, 1, fwdPrefix); + nsString unicodeFwdPrefix; + CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix); + unicodeFwdPrefix.AppendLiteral(": "); + subject.Insert(unicodeFwdPrefix, 0); + m_compFields->SetSubject(subject); + } + break; + } + case nsIMsgCompType::Redirect: { + // For a redirect, set the Reply-To: header to what was in the + // original From: header... + nsAutoCString author; + msgHdr->GetAuthor(getter_Copies(author)); + m_compFields->SetSubject(subject); + m_compFields->SetReplyTo(author.get()); + + // ... and empty out the various recipient headers + nsAutoString empty; + m_compFields->SetTo(empty); + m_compFields->SetCc(empty); + m_compFields->SetBcc(empty); + m_compFields->SetNewsgroups(empty); + m_compFields->SetFollowupTo(empty); + + // Add the redirected message in the references so that threading + // will work when the new recipient eventually replies to the + // original sender. + nsAutoCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + if (isFirstPass) { + nsAutoCString reference; + reference.Append('<'); + reference.Append(messageId); + reference.Append('>'); + m_compFields->SetReferences(reference.get()); + } else { + nsAutoCString references; + m_compFields->GetReferences(getter_Copies(references)); + references.AppendLiteral(" <"); + references.Append(messageId); + references.Append('>'); + m_compFields->SetReferences(references.get()); + } + break; + } + } + } + isFirstPass = false; + uri = nextUri + 1; + } while (nextUri); + PR_Free(uriList); + return rv; +} + +NS_IMETHODIMP nsMsgCompose::GetProgress(nsIMsgProgress** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + NS_IF_ADDREF(*_retval = mProgress); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + NS_IF_ADDREF(*_retval = mMsgSend); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetMessageSend(nsIMsgSend* aMsgSend) { + mMsgSend = aMsgSend; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::ClearMessageSend() { + mMsgSend = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetCiteReference(nsString citeReference) { + mCiteReference = citeReference; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetSavedFolderURI(const nsACString& folderURI) { + m_folderName = folderURI; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetSavedFolderURI(nsACString& folderURI) { + folderURI = m_folderName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetOriginalMsgURI(nsACString& originalMsgURI) { + originalMsgURI = mOriginalMsgURI; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// THIS IS THE CLASS THAT IS THE STREAM CONSUMER OF THE HTML OUTPUT +// FROM LIBMIME. THIS IS FOR QUOTING +//////////////////////////////////////////////////////////////////////////////////// +QuotingOutputStreamListener::~QuotingOutputStreamListener() {} + +QuotingOutputStreamListener::QuotingOutputStreamListener( + nsIMsgDBHdr* originalMsgHdr, bool quoteHeaders, bool headersOnly, + nsIMsgIdentity* identity, nsIMsgQuote* msgQuote, bool quoteOriginal, + const nsACString& htmlToQuote) { + nsresult rv; + mQuoteHeaders = quoteHeaders; + mHeadersOnly = headersOnly; + mIdentity = identity; + mOrigMsgHdr = originalMsgHdr; + mUnicodeBufferCharacterLength = 0; + mQuoteOriginal = quoteOriginal; + mHtmlToQuote = htmlToQuote; + mQuote = msgQuote; + + if (!mHeadersOnly || !mHtmlToQuote.IsEmpty()) { + // Get header type, locale and strings from pref. + int32_t replyHeaderType; + nsString replyHeaderAuthorWrote; + nsString replyHeaderOnDateAuthorWrote; + nsString replyHeaderAuthorWroteOnDate; + nsString replyHeaderOriginalmessage; + GetReplyHeaderInfo( + &replyHeaderType, replyHeaderAuthorWrote, replyHeaderOnDateAuthorWrote, + replyHeaderAuthorWroteOnDate, replyHeaderOriginalmessage); + + // For the built message body... + if (originalMsgHdr && !quoteHeaders) { + // Setup the cite information.... + nsCString myGetter; + if (NS_SUCCEEDED(originalMsgHdr->GetMessageId(getter_Copies(myGetter)))) { + if (!myGetter.IsEmpty()) { + nsAutoCString buf; + mCiteReference.AssignLiteral("mid:"); + MsgEscapeURL(myGetter, + nsINetUtil::ESCAPE_URL_FILE_BASENAME | + nsINetUtil::ESCAPE_URL_FORCED, + buf); + mCiteReference.Append(NS_ConvertASCIItoUTF16(buf)); + } + } + + bool citingHeader; // Do we have a header needing to cite any info from + // original message? + bool headerDate; // Do we have a header needing to cite date/time from + // original message? + switch (replyHeaderType) { + case 0: // No reply header at all (actually the "---- original message + // ----" string, which is kinda misleading. TODO: Should there + // be a "really no header" option? + mCitePrefix.Assign(replyHeaderOriginalmessage); + citingHeader = false; + headerDate = false; + break; + + case 2: // Insert both the original author and date in the reply header + // (date followed by author) + mCitePrefix.Assign(replyHeaderOnDateAuthorWrote); + citingHeader = true; + headerDate = true; + break; + + case 3: // Insert both the original author and date in the reply header + // (author followed by date) + mCitePrefix.Assign(replyHeaderAuthorWroteOnDate); + citingHeader = true; + headerDate = true; + break; + + case 4: // TODO bug 107884: implement a more featureful user specified + // header + case 1: + default: // Default is to only show the author. + mCitePrefix.Assign(replyHeaderAuthorWrote); + citingHeader = true; + headerDate = false; + break; + } + + if (citingHeader) { + int32_t placeholderIndex = kNotFound; + + if (headerDate) { + PRTime originalMsgDate; + rv = originalMsgHdr->GetDate(&originalMsgDate); + if (NS_SUCCEEDED(rv)) { + nsAutoString citeDatePart; + if ((placeholderIndex = mCitePrefix.Find(u"#2")) != kNotFound) { + mozilla::intl::DateTimeFormat::StyleBag style; + style.date = + mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short); + rv = mozilla::intl::AppDateTimeFormat::Format( + style, originalMsgDate, citeDatePart); + if (NS_SUCCEEDED(rv)) + mCitePrefix.Replace(placeholderIndex, 2, citeDatePart); + } + if ((placeholderIndex = mCitePrefix.Find(u"#3")) != kNotFound) { + mozilla::intl::DateTimeFormat::StyleBag style; + style.time = + mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short); + rv = mozilla::intl::AppDateTimeFormat::Format( + style, originalMsgDate, citeDatePart); + if (NS_SUCCEEDED(rv)) + mCitePrefix.Replace(placeholderIndex, 2, citeDatePart); + } + } + } + + if ((placeholderIndex = mCitePrefix.Find(u"#1")) != kNotFound) { + nsAutoCString author; + rv = originalMsgHdr->GetAuthor(getter_Copies(author)); + if (NS_SUCCEEDED(rv)) { + nsAutoString citeAuthor; + ExtractName(EncodedHeader(author), citeAuthor); + mCitePrefix.Replace(placeholderIndex, 2, citeAuthor); + } + } + } + } + + // This should not happen, but just in case. + if (mCitePrefix.IsEmpty()) { + mCitePrefix.AppendLiteral("\n\n"); + mCitePrefix.Append(replyHeaderOriginalmessage); + mCitePrefix.AppendLiteral("\n"); + } + } +} + +/** + * The formatflowed parameter directs if formatflowed should be used in the + * conversion. format=flowed (RFC 2646) is a way to represent flow in a plain + * text mail, without disturbing the plain text. + */ +nsresult QuotingOutputStreamListener::ConvertToPlainText(bool formatflowed, + bool formatted, + bool disallowBreaks) { + nsresult rv = + ConvertBufToPlainText(mMsgBody, formatflowed, formatted, disallowBreaks); + NS_ENSURE_SUCCESS(rv, rv); + return ConvertBufToPlainText(mSignature, formatflowed, formatted, + disallowBreaks); +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnStartRequest(nsIRequest* request) { + return NS_OK; +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +QuotingOutputStreamListener::OnStopRequest(nsIRequest* request, + nsresult status) { + nsresult rv = NS_OK; + + if (!mHtmlToQuote.IsEmpty()) { + // If we had a selection in the original message to quote, we can add + // it now that we are done ignoring the original body of the message + mHeadersOnly = false; + rv = AppendToMsgBody(mHtmlToQuote); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj); + NS_ENSURE_TRUE(compose, NS_ERROR_NULL_POINTER); + + MSG_ComposeType type; + compose->GetType(&type); + + // Assign cite information if available... + if (!mCiteReference.IsEmpty()) compose->SetCiteReference(mCiteReference); + + bool overrideReplyTo = + mozilla::Preferences::GetBool("mail.override_list_reply_to", true); + + if (mHeaders && + (type == nsIMsgCompType::Reply || type == nsIMsgCompType::ReplyAll || + type == nsIMsgCompType::ReplyToList || + type == nsIMsgCompType::ReplyToSender || + type == nsIMsgCompType::ReplyToGroup || + type == nsIMsgCompType::ReplyToSenderAndGroup) && + mQuoteOriginal) { + nsCOMPtr<nsIMsgCompFields> compFields; + compose->GetCompFields(getter_AddRefs(compFields)); + if (compFields) { + nsAutoString from; + nsAutoString to; + nsAutoString cc; + nsAutoString bcc; + nsAutoString replyTo; + nsAutoString mailReplyTo; + nsAutoString mailFollowupTo; + nsAutoString newgroups; + nsAutoString followUpTo; + nsAutoString messageId; + nsAutoString references; + nsAutoString listPost; + + nsCString outCString; // Temp helper string. + + bool needToRemoveDup = false; + if (!mMimeConverter) { + mMimeConverter = + do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCString charset("UTF-8"); + + mHeaders->ExtractHeader(HEADER_FROM, true, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, from); + + mHeaders->ExtractHeader(HEADER_TO, true, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, to); + + mHeaders->ExtractHeader(HEADER_CC, true, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, cc); + + mHeaders->ExtractHeader(HEADER_BCC, true, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, bcc); + + mHeaders->ExtractHeader(HEADER_MAIL_FOLLOWUP_TO, true, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, mailFollowupTo); + + mHeaders->ExtractHeader(HEADER_REPLY_TO, false, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, replyTo); + + mHeaders->ExtractHeader(HEADER_MAIL_REPLY_TO, true, outCString); + nsMsgI18NConvertRawBytesToUTF16(outCString, charset, mailReplyTo); + + mHeaders->ExtractHeader(HEADER_NEWSGROUPS, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false, + true, newgroups); + + mHeaders->ExtractHeader(HEADER_FOLLOWUP_TO, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false, + true, followUpTo); + + mHeaders->ExtractHeader(HEADER_MESSAGE_ID, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false, + true, messageId); + + mHeaders->ExtractHeader(HEADER_REFERENCES, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false, + true, references); + + mHeaders->ExtractHeader(HEADER_LIST_POST, true, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false, + true, listPost); + if (!listPost.IsEmpty()) { + int32_t startPos = listPost.Find(u"<mailto:"); + int32_t endPos = listPost.FindChar('>', startPos); + // Extract the e-mail address. + if (endPos > startPos) { + const uint32_t mailtoLen = strlen("<mailto:"); + listPost = Substring(listPost, startPos + mailtoLen, + endPos - (startPos + mailtoLen)); + } + } + + nsCString fromEmailAddress; + ExtractEmail(EncodedHeaderW(from), fromEmailAddress); + + nsTArray<nsCString> toEmailAddresses; + ExtractEmails(EncodedHeaderW(to), UTF16ArrayAdapter<>(toEmailAddresses)); + + nsTArray<nsCString> ccEmailAddresses; + ExtractEmails(EncodedHeaderW(cc), UTF16ArrayAdapter<>(ccEmailAddresses)); + + nsCOMPtr<nsIPrefBranch> prefs( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + bool replyToSelfCheckAll = false; + prefs->GetBoolPref("mailnews.reply_to_self_check_all_ident", + &replyToSelfCheckAll); + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgIdentity>> identities; + nsCString accountKey; + mOrigMsgHdr->GetAccountKey(getter_Copies(accountKey)); + if (replyToSelfCheckAll) { + // Check all available identities if the pref was set. + accountManager->GetAllIdentities(identities); + } else if (!accountKey.IsEmpty()) { + // Check headers to see which account the message came in from + // (only works for pop3). + nsCOMPtr<nsIMsgAccount> account; + accountManager->GetAccount(accountKey, getter_AddRefs(account)); + if (account) { + rv = account->GetIdentities(identities); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + // Check identities only for the server of the folder that the message + // is in. + nsCOMPtr<nsIMsgFolder> msgFolder; + rv = mOrigMsgHdr->GetFolder(getter_AddRefs(msgFolder)); + + if (NS_SUCCEEDED(rv) && msgFolder) { + nsCOMPtr<nsIMsgIncomingServer> nsIMsgIncomingServer; + rv = msgFolder->GetServer(getter_AddRefs(nsIMsgIncomingServer)); + + if (NS_SUCCEEDED(rv) && nsIMsgIncomingServer) { + rv = accountManager->GetIdentitiesForServer(nsIMsgIncomingServer, + identities); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + bool isReplyToSelf = false; + nsCOMPtr<nsIMsgIdentity> selfIdentity; + if (!identities.IsEmpty()) { + nsTArray<nsCString> toEmailAddressesLower(toEmailAddresses.Length()); + for (auto email : toEmailAddresses) { + ToLowerCase(email); + toEmailAddressesLower.AppendElement(email); + } + nsTArray<nsCString> ccEmailAddressesLower(ccEmailAddresses.Length()); + for (auto email : ccEmailAddresses) { + ToLowerCase(email); + ccEmailAddressesLower.AppendElement(email); + } + + // Go through the identities to see if any of them is the author of + // the email. + for (auto lookupIdentity : identities) { + selfIdentity = lookupIdentity; + + nsCString curIdentityEmail; + lookupIdentity->GetEmail(curIdentityEmail); + + // See if it's a reply to own message, but not a reply between + // identities. + if (curIdentityEmail.Equals(fromEmailAddress, + nsCaseInsensitiveCStringComparator)) { + isReplyToSelf = true; + // For a true reply-to-self, none of your identities are normally in + // To or Cc. We need to avoid doing a reply-to-self for people that + // have multiple identities set and sometimes *uses* the other + // identity and sometimes *mails* the other identity. + // E.g. husband+wife or own-email+company-role-mail. + for (auto lookupIdentity2 : identities) { + nsCString curIdentityEmail2; + lookupIdentity2->GetEmail(curIdentityEmail2); + ToLowerCase(curIdentityEmail2); + if (toEmailAddressesLower.Contains(curIdentityEmail2)) { + // However, "From:me To:me" should be treated as + // reply-to-self if we have a Bcc. If we don't have a Bcc we + // might have the case of a generated mail of the style + // "From:me To:me Reply-To:customer". Then we need to to do a + // normal reply to the customer. + isReplyToSelf = !bcc.IsEmpty(); // true if bcc is set + break; + } else if (ccEmailAddressesLower.Contains(curIdentityEmail2)) { + // If you auto-Cc yourself your email would be in Cc - but we + // can't detect why it is in Cc so lets just treat it like a + // normal reply. + isReplyToSelf = false; + break; + } + } + break; + } + } + } + if (type == nsIMsgCompType::ReplyToSenderAndGroup || + type == nsIMsgCompType::ReplyToSender || + type == nsIMsgCompType::Reply) { + if (isReplyToSelf) { + // Cast to concrete class. We *only* what to change m_identity, not + // all the things compose->SetIdentity would do. + nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get()); + _compose->m_identity = selfIdentity; + compFields->SetFrom(from); + compFields->SetTo(to); + compFields->SetReplyTo(replyTo); + } else if (!mailReplyTo.IsEmpty()) { + // handle Mail-Reply-To (http://cr.yp.to/proto/replyto.html) + compFields->SetTo(mailReplyTo); + needToRemoveDup = true; + } else if (!replyTo.IsEmpty()) { + // default reply behaviour then + + if (overrideReplyTo && !listPost.IsEmpty() && + replyTo.Find(listPost) != kNotFound) { + // Reply-To munging in this list post. Reply to From instead, + // as the user can choose Reply List if that's what he wants. + compFields->SetTo(from); + } else { + compFields->SetTo(replyTo); + } + needToRemoveDup = true; + } else { + compFields->SetTo(from); + } + } else if (type == nsIMsgCompType::ReplyAll) { + if (isReplyToSelf) { + // Cast to concrete class. We *only* what to change m_identity, not + // all the things compose->SetIdentity would do. + nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get()); + _compose->m_identity = selfIdentity; + compFields->SetFrom(from); + compFields->SetTo(to); + compFields->SetCc(cc); + // In case it's a reply to self, but it's not the actual source of the + // sent message, then we won't know the Bcc header. So set it only if + // it's not empty. If you have auto-bcc and removed the auto-bcc for + // the original mail, you will have to do it manually for this reply + // too. + if (!bcc.IsEmpty()) compFields->SetBcc(bcc); + compFields->SetReplyTo(replyTo); + needToRemoveDup = true; + } else if (mailFollowupTo.IsEmpty()) { + // default reply-all behaviour then + + nsAutoString allTo; + if (!replyTo.IsEmpty()) { + allTo.Assign(replyTo); + needToRemoveDup = true; + if (overrideReplyTo && !listPost.IsEmpty() && + replyTo.Find(listPost) != kNotFound) { + // Reply-To munging in this list. Add From to recipients, it's the + // lesser evil... + allTo.AppendLiteral(", "); + allTo.Append(from); + } + } else { + allTo.Assign(from); + } + + allTo.AppendLiteral(", "); + allTo.Append(to); + compFields->SetTo(allTo); + + nsAutoString allCc; + compFields->GetCc(allCc); // auto-cc + if (!allCc.IsEmpty()) allCc.AppendLiteral(", "); + allCc.Append(cc); + compFields->SetCc(allCc); + + needToRemoveDup = true; + } else { + // Handle Mail-Followup-To (http://cr.yp.to/proto/replyto.html) + compFields->SetTo(mailFollowupTo); + needToRemoveDup = true; // To remove possible self from To. + + // If Cc is set a this point it's auto-Ccs, so we'll just keep those. + } + } else if (type == nsIMsgCompType::ReplyToList) { + compFields->SetTo(listPost); + } + + if (!newgroups.IsEmpty()) { + if ((type != nsIMsgCompType::Reply) && + (type != nsIMsgCompType::ReplyToSender)) + compFields->SetNewsgroups(newgroups); + if (type == nsIMsgCompType::ReplyToGroup) + compFields->SetTo(EmptyString()); + } + + if (!followUpTo.IsEmpty()) { + // Handle "followup-to: poster" magic keyword here + if (followUpTo.EqualsLiteral("poster")) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + compose->GetDomWindow(getter_AddRefs(domWindow)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsMsgDisplayMessageByName(domWindow, "followupToSenderMessage"); + + if (!replyTo.IsEmpty()) { + compFields->SetTo(replyTo); + } else { + // If reply-to is empty, use the From header to fetch the original + // sender's email. + compFields->SetTo(from); + } + + // Clear the newsgroup: header field, because followup-to: poster + // only follows up to the original sender + if (!newgroups.IsEmpty()) compFields->SetNewsgroups(EmptyString()); + } else // Process "followup-to: newsgroup-content" here + { + if (type != nsIMsgCompType::ReplyToSender) + compFields->SetNewsgroups(followUpTo); + if (type == nsIMsgCompType::Reply) { + compFields->SetTo(EmptyString()); + } + } + } + + if (!references.IsEmpty()) references.Append(char16_t(' ')); + references += messageId; + compFields->SetReferences(NS_LossyConvertUTF16toASCII(references).get()); + + nsAutoCString resultStr; + + // Cast interface to concrete class that has direct field getters etc. + nsMsgCompFields* _compFields = + static_cast<nsMsgCompFields*>(compFields.get()); + + // Remove duplicate addresses between To && Cc. + if (needToRemoveDup) { + nsCString addressesToRemoveFromCc; + if (mIdentity) { + bool removeMyEmailInCc = true; + nsCString myEmail; + // Get senders address from composeField or from identity, + nsAutoCString sender(_compFields->GetFrom()); + ExtractEmail(EncodedHeader(sender), myEmail); + if (myEmail.IsEmpty()) mIdentity->GetEmail(myEmail); + + // Remove my own address from To, unless it's a reply to self. + if (!isReplyToSelf) { + RemoveDuplicateAddresses(nsDependentCString(_compFields->GetTo()), + myEmail, resultStr); + _compFields->SetTo(resultStr.get()); + } + addressesToRemoveFromCc.Assign(_compFields->GetTo()); + + // Remove own address from CC unless we want it in there + // through the automatic-CC-to-self (see bug 584962). There are + // three cases: + // - user has no automatic CC + // - user has automatic CC but own email is not in it + // - user has automatic CC and own email in it + // Only in the last case do we want our own email address to stay + // in the CC list. + bool automaticCc; + mIdentity->GetDoCc(&automaticCc); + if (automaticCc) { + nsCString autoCcList; + mIdentity->GetDoCcList(autoCcList); + nsTArray<nsCString> autoCcEmailAddresses; + ExtractEmails(EncodedHeader(autoCcList), + UTF16ArrayAdapter<>(autoCcEmailAddresses)); + if (autoCcEmailAddresses.Contains(myEmail)) { + removeMyEmailInCc = false; + } + } + + if (removeMyEmailInCc) { + addressesToRemoveFromCc.AppendLiteral(", "); + addressesToRemoveFromCc.Append(myEmail); + } + } + RemoveDuplicateAddresses(nsDependentCString(_compFields->GetCc()), + addressesToRemoveFromCc, resultStr); + _compFields->SetCc(resultStr.get()); + if (_compFields->GetBcc()) { + // Remove addresses already in Cc from Bcc. + RemoveDuplicateAddresses(nsDependentCString(_compFields->GetBcc()), + nsDependentCString(_compFields->GetCc()), + resultStr); + if (!resultStr.IsEmpty()) { + // Remove addresses already in To from Bcc. + RemoveDuplicateAddresses( + resultStr, nsDependentCString(_compFields->GetTo()), resultStr); + } + _compFields->SetBcc(resultStr.get()); + } + } + } + } + +#ifdef MSGCOMP_TRACE_PERFORMANCE + nsCOMPtr<nsIMsgComposeService> composeService( + do_GetService("@mozilla.org/messengercompose;1")); + composeService->TimeStamp( + "Done with MIME. Now we're updating the UI elements", false); +#endif + + if (mQuoteOriginal) + compose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + composeService->TimeStamp( + "Addressing widget, window title and focus are now set, time to insert " + "the body", + false); +#endif + + if (!mHeadersOnly) mMsgBody.AppendLiteral("</html>"); + + // Now we have an HTML representation of the quoted message. + // If we are in plain text mode, we need to convert this to plain + // text before we try to insert it into the editor. If we don't, we + // just get lots of HTML text in the message...not good. + // + // XXX not m_composeHTML? /BenB + bool composeHTML = true; + compose->GetComposeHTML(&composeHTML); + if (!composeHTML) { + // Downsampling. + + // In plain text quotes we always allow line breaking to not end up with + // long lines. The quote is inserted into a span with style + // "white-space: pre;" which isn't be wrapped. + // Update: Bug 387687 changed this to "white-space: pre-wrap;". + // Note that the body of the plain text message is wrapped since it uses + // "white-space: pre-wrap; width: 72ch;". + // Look at it in the DOM Inspector to see it. + // + // If we're using format flowed, we need to pass it so the encoder + // can add a space at the end. + nsCOMPtr<nsIPrefBranch> pPrefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + bool flowed = false; + if (pPrefBranch) { + pPrefBranch->GetBoolPref("mailnews.send_plaintext_flowed", &flowed); + } + + rv = ConvertToPlainText(flowed, + true, // formatted + false); // allow line breaks + NS_ENSURE_SUCCESS(rv, rv); + } + + compose->ProcessSignature(mIdentity, true, &mSignature); + + nsCOMPtr<nsIEditor> editor; + if (NS_SUCCEEDED(compose->GetEditor(getter_AddRefs(editor))) && editor) { + if (mQuoteOriginal) + compose->ConvertAndLoadComposeWindow(mCitePrefix, mMsgBody, mSignature, + true, composeHTML); + else + InsertToCompose(editor, composeHTML); + } + + if (mQuoteOriginal) + compose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK); + return rv; +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnDataAvailable( + nsIRequest* request, nsIInputStream* inStr, uint64_t sourceOffset, + uint32_t count) { + nsresult rv = NS_OK; + NS_ENSURE_ARG(inStr); + + if (mHeadersOnly) return rv; + + char* newBuf = (char*)PR_Malloc(count + 1); + if (!newBuf) return NS_ERROR_FAILURE; + + uint32_t numWritten = 0; + rv = inStr->Read(newBuf, count, &numWritten); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK; + newBuf[numWritten] = '\0'; + if (NS_SUCCEEDED(rv) && numWritten > 0) { + rv = AppendToMsgBody(nsDependentCString(newBuf, numWritten)); + } + + PR_FREEIF(newBuf); + return rv; +} + +nsresult QuotingOutputStreamListener::AppendToMsgBody(const nsCString& inStr) { + nsresult rv = NS_OK; + if (!inStr.IsEmpty()) { + nsAutoString tmp; + rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(inStr, tmp); + if (NS_SUCCEEDED(rv)) mMsgBody.Append(tmp); + } + return rv; +} + +nsresult QuotingOutputStreamListener::SetComposeObj(nsIMsgCompose* obj) { + mWeakComposeObj = do_GetWeakReference(obj); + return NS_OK; +} + +NS_IMETHODIMP +QuotingOutputStreamListener::SetMimeHeaders(nsIMimeHeaders* headers) { + mHeaders = headers; + return NS_OK; +} + +nsresult QuotingOutputStreamListener::InsertToCompose(nsIEditor* aEditor, + bool aHTMLEditor) { + NS_ENSURE_ARG(aEditor); + nsCOMPtr<nsINode> nodeInserted; + + TranslateLineEnding(mMsgBody); + + // Now, insert it into the editor... + aEditor->EnableUndo(true); + + nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj); + if (!mMsgBody.IsEmpty() && compose) { + compose->SetAllowRemoteContent(true); + if (!mCitePrefix.IsEmpty()) { + if (!aHTMLEditor) mCitePrefix.AppendLiteral("\n"); + aEditor->InsertText(mCitePrefix); + } + + RefPtr<mozilla::HTMLEditor> htmlEditor = aEditor->AsHTMLEditor(); + if (aHTMLEditor) { + nsAutoString body(mMsgBody); + remove_plaintext_tag(body); + htmlEditor->InsertAsCitedQuotation(body, EmptyString(), true, + getter_AddRefs(nodeInserted)); + } else { + htmlEditor->InsertAsQuotation(mMsgBody, getter_AddRefs(nodeInserted)); + } + compose->SetAllowRemoteContent(false); + } + + RefPtr<Selection> selection; + nsCOMPtr<nsINode> parent; + int32_t offset; + nsresult rv; + + // get parent and offset of mailcite + rv = GetNodeLocation(nodeInserted, address_of(parent), &offset); + NS_ENSURE_SUCCESS(rv, rv); + + // get selection + aEditor->GetSelection(getter_AddRefs(selection)); + if (selection) { + // place selection after mailcite + selection->CollapseInLimiter(parent, offset + 1); + // insert a break at current selection + aEditor->InsertLineBreak(); + selection->CollapseInLimiter(parent, offset + 1); + } + nsCOMPtr<nsISelectionController> selCon; + aEditor->GetSelectionController(getter_AddRefs(selCon)); + + if (selCon) + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_ANCHOR_REGION, true); + + return NS_OK; +} + +/** + * Returns true if the domain is a match for the given the domain list. + * Subdomains are also considered to match. + * @param aDomain - the domain name to check + * @param aDomainList - a comma separated string of domain names + */ +bool IsInDomainList(const nsAString& aDomain, const nsAString& aDomainList) { + if (aDomain.IsEmpty() || aDomainList.IsEmpty()) return false; + + // Check plain text domains. + int32_t left = 0; + int32_t right = 0; + while (right != (int32_t)aDomainList.Length()) { + right = aDomainList.FindChar(',', left); + if (right == kNotFound) right = aDomainList.Length(); + nsDependentSubstring domain = Substring(aDomainList, left, right); + + if (aDomain.Equals(domain, nsCaseInsensitiveStringComparator)) return true; + + nsAutoString dotDomain; + dotDomain.Assign(u'.'); + dotDomain.Append(domain); + if (StringEndsWith(aDomain, dotDomain, nsCaseInsensitiveStringComparator)) + return true; + + left = right + 1; + } + return false; +} + +NS_IMPL_ISUPPORTS(QuotingOutputStreamListener, + nsIMsgQuotingOutputStreamListener, nsIRequestObserver, + nsIStreamListener, nsISupportsWeakReference) + +//////////////////////////////////////////////////////////////////////////////////// +// END OF QUOTING LISTENER +//////////////////////////////////////////////////////////////////////////////////// + +/* attribute MSG_ComposeType type; */ +NS_IMETHODIMP nsMsgCompose::SetType(MSG_ComposeType aType) { + mType = aType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetType(MSG_ComposeType* aType) { + NS_ENSURE_ARG_POINTER(aType); + + *aType = mType; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::QuoteMessage(const nsACString& msgURI) { + nsresult rv; + mQuotingToFollow = false; + + // Create a mime parser (nsIStreamConverter)! + mQuote = do_CreateInstance("@mozilla.org/messengercompose/quoting;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgHdr)); + + // Create the consumer output stream.. this will receive all the HTML from + // libmime + mQuoteStreamListener = + new QuotingOutputStreamListener(msgHdr, false, !mHtmlToQuote.IsEmpty(), + m_identity, mQuote, false, mHtmlToQuote); + + mQuoteStreamListener->SetComposeObj(this); + + rv = mQuote->QuoteMessage(msgURI, false, mQuoteStreamListener, + mAutodetectCharset, false, msgHdr); + return rv; +} + +nsresult nsMsgCompose::QuoteOriginalMessage() // New template +{ + nsresult rv; + + mQuotingToFollow = false; + + // Create a mime parser (nsIStreamConverter)! + mQuote = do_CreateInstance("@mozilla.org/messengercompose/quoting;1", &rv); + if (NS_FAILED(rv) || !mQuote) return NS_ERROR_FAILURE; + + bool bAutoQuote = true; + m_identity->GetAutoQuote(&bAutoQuote); + + nsCOMPtr<nsIMsgDBHdr> originalMsgHdr = mOrigMsgHdr; + if (!originalMsgHdr) { + rv = GetMsgDBHdrFromURI(mOriginalMsgURI, getter_AddRefs(originalMsgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsAutoCString msgUri(mOriginalMsgURI); + bool fileUrl = StringBeginsWith(msgUri, "file:"_ns); + if (fileUrl) { + msgUri.Replace(0, 5, "mailbox:"_ns); + msgUri.AppendLiteral("?number=0"); + } + + // Create the consumer output stream.. this will receive all the HTML from + // libmime + mQuoteStreamListener = new QuotingOutputStreamListener( + originalMsgHdr, mWhatHolder != 1, !bAutoQuote || !mHtmlToQuote.IsEmpty(), + m_identity, mQuote, true, mHtmlToQuote); + + mQuoteStreamListener->SetComposeObj(this); + + rv = mQuote->QuoteMessage(msgUri, mWhatHolder != 1, mQuoteStreamListener, + mAutodetectCharset, !bAutoQuote, originalMsgHdr); + return rv; +} + +// CleanUpRecipient will remove un-necessary "<>" when a recipient as an address +// without name +void nsMsgCompose::CleanUpRecipients(nsString& recipients) { + uint16_t i; + bool startANewRecipient = true; + bool removeBracket = false; + nsAutoString newRecipient; + char16_t aChar; + + for (i = 0; i < recipients.Length(); i++) { + aChar = recipients[i]; + switch (aChar) { + case '<': + if (startANewRecipient) + removeBracket = true; + else + newRecipient += aChar; + startANewRecipient = false; + break; + + case '>': + if (removeBracket) + removeBracket = false; + else + newRecipient += aChar; + break; + + case ' ': + newRecipient += aChar; + break; + + case ',': + newRecipient += aChar; + startANewRecipient = true; + removeBracket = false; + break; + + default: + newRecipient += aChar; + startANewRecipient = false; + break; + } + } + recipients = newRecipient; +} + +NS_IMETHODIMP nsMsgCompose::RememberQueuedDisposition() { + // need to find the msg hdr in the saved folder and then set a property on + // the header that we then look at when we actually send the message. + nsresult rv; + nsAutoCString dispositionSetting; + + if (mType == nsIMsgCompType::Reply || mType == nsIMsgCompType::ReplyAll || + mType == nsIMsgCompType::ReplyToList || + mType == nsIMsgCompType::ReplyToGroup || + mType == nsIMsgCompType::ReplyToSender || + mType == nsIMsgCompType::ReplyToSenderAndGroup) { + dispositionSetting.AssignLiteral("replied"); + } else if (mType == nsIMsgCompType::ForwardAsAttachment || + mType == nsIMsgCompType::ForwardInline) { + dispositionSetting.AssignLiteral("forwarded"); + } else if (mType == nsIMsgCompType::Redirect) { + dispositionSetting.AssignLiteral("redirected"); + } else if (mType == nsIMsgCompType::Draft) { + nsAutoCString curDraftIdURL; + rv = m_compFields->GetDraftId(curDraftIdURL); + NS_ENSURE_SUCCESS(rv, rv); + if (!curDraftIdURL.IsEmpty()) { + nsCOMPtr<nsIMsgDBHdr> draftHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(draftHdr)); + NS_ENSURE_SUCCESS(rv, rv); + draftHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, + dispositionSetting); + } + } + + nsMsgKey msgKey; + if (mMsgSend) { + mMsgSend->GetMessageKey(&msgKey); + nsCString identityKey; + + m_identity->GetKey(identityKey); + + nsCOMPtr<nsIMsgFolder> folder; + rv = GetOrCreateFolder(m_folderName, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = folder->GetMessageHeader(msgKey, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t pseudoHdrProp = 0; + msgHdr->GetUint32Property("pseudoHdr", &pseudoHdrProp); + if (pseudoHdrProp) { + // Use SetAttributeOnPendingHdr for IMAP pseudo headers, as those + // will get deleted (and properties set using SetStringProperty lost.) + nsCOMPtr<nsIMsgFolder> folder; + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDatabase> msgDB; + rv = folder->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString messageId; + mMsgSend->GetMessageId(messageId); + msgHdr->SetMessageId(messageId.get()); + if (!mOriginalMsgURI.IsEmpty()) { + msgDB->SetAttributeOnPendingHdr(msgHdr, ORIG_URI_PROPERTY, + mOriginalMsgURI.get()); + if (!dispositionSetting.IsEmpty()) + msgDB->SetAttributeOnPendingHdr(msgHdr, QUEUED_DISPOSITION_PROPERTY, + dispositionSetting.get()); + } + msgDB->SetAttributeOnPendingHdr(msgHdr, HEADER_X_MOZILLA_IDENTITY_KEY, + identityKey.get()); + } else if (msgHdr) { + if (!mOriginalMsgURI.IsEmpty()) { + msgHdr->SetStringProperty(ORIG_URI_PROPERTY, mOriginalMsgURI); + if (!dispositionSetting.IsEmpty()) + msgHdr->SetStringProperty(QUEUED_DISPOSITION_PROPERTY, + dispositionSetting); + } + msgHdr->SetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey); + } + } + return NS_OK; +} + +nsresult nsMsgCompose::ProcessReplyFlags() { + nsresult rv; + // check to see if we were doing a reply or a forward, if we were, set the + // answered field flag on the message folder for this URI. + if (mType == nsIMsgCompType::Reply || mType == nsIMsgCompType::ReplyAll || + mType == nsIMsgCompType::ReplyToList || + mType == nsIMsgCompType::ReplyToGroup || + mType == nsIMsgCompType::ReplyToSender || + mType == nsIMsgCompType::ReplyToSenderAndGroup || + mType == nsIMsgCompType::ForwardAsAttachment || + mType == nsIMsgCompType::ForwardInline || + mType == nsIMsgCompType::Redirect || + mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None) { + if (!mOriginalMsgURI.IsEmpty()) { + nsCString msgUri(mOriginalMsgURI); + char* newStr = msgUri.BeginWriting(); + char* uri; + while (nullptr != (uri = NS_strtok(",", &newStr))) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = + GetMsgDBHdrFromURI(nsDependentCString(uri), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + if (msgHdr) { + // get the folder for the message resource + nsCOMPtr<nsIMsgFolder> msgFolder; + msgHdr->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) { + // If it's a draft with disposition, default to replied, otherwise, + // check if it's a forward. + nsMsgDispositionState dispositionSetting = + nsIMsgFolder::nsMsgDispositionState_Replied; + if (mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None) + dispositionSetting = mDraftDisposition; + else if (mType == nsIMsgCompType::ForwardAsAttachment || + mType == nsIMsgCompType::ForwardInline) + dispositionSetting = + nsIMsgFolder::nsMsgDispositionState_Forwarded; + else if (mType == nsIMsgCompType::Redirect) + dispositionSetting = + nsIMsgFolder::nsMsgDispositionState_Redirected; + + msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting); + if (mType != nsIMsgCompType::ForwardAsAttachment) + break; // just safeguard + } + } + } + } + } + + return NS_OK; +} +NS_IMETHODIMP nsMsgCompose::OnStartSending(const char* aMsgID, + uint32_t aMsgSize) { + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnStartSending(aMsgID, aMsgSize); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnProgress(const char* aMsgID, uint32_t aProgress, + uint32_t aProgressMax) { + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnProgress(aMsgID, aProgress, aProgressMax); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnStatus(const char* aMsgID, const char16_t* aMsg) { + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnStatus(aMsgID, aMsg); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnStopSending(const char* aMsgID, nsresult aStatus, + const char16_t* aMsg, + nsIFile* returnFile) { + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::OnTransportSecurityError(const char* msgID, nsresult status, + nsITransportSecurityInfo* secInfo, + nsACString const& location) { + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnTransportSecurityError(msgID, status, secInfo, + location); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnSendNotPerformed(const char* aMsgID, + nsresult aStatus) { + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnSendNotPerformed(aMsgID, aStatus); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnGetDraftFolderURI(const char* aMsgID, + const nsACString& aFolderURI) { + m_folderName = aFolderURI; + nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter( + mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) { + externalSendListener = iter.GetNext(); + externalSendListener->OnGetDraftFolderURI(aMsgID, aFolderURI); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for both the send operation and the copy +// operation. We have to create this class to listen for message send completion +// and deal with failures in both send and copy operations +//////////////////////////////////////////////////////////////////////////////////// +NS_IMPL_ADDREF(nsMsgComposeSendListener) +NS_IMPL_RELEASE(nsMsgComposeSendListener) + +/* +NS_IMPL_QUERY_INTERFACE(nsMsgComposeSendListener, + nsIMsgComposeSendListener, + nsIMsgSendListener, + nsIMsgCopyServiceListener, + nsIWebProgressListener) +*/ +NS_INTERFACE_MAP_BEGIN(nsMsgComposeSendListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgComposeSendListener) + NS_INTERFACE_MAP_ENTRY(nsIMsgComposeSendListener) + NS_INTERFACE_MAP_ENTRY(nsIMsgSendListener) + NS_INTERFACE_MAP_ENTRY(nsIMsgCopyServiceListener) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END + +nsMsgComposeSendListener::nsMsgComposeSendListener(void) { mDeliverMode = 0; } + +nsMsgComposeSendListener::~nsMsgComposeSendListener(void) {} + +NS_IMETHODIMP nsMsgComposeSendListener::SetMsgCompose(nsIMsgCompose* obj) { + mWeakComposeObj = do_GetWeakReference(obj); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::SetDeliverMode( + MSG_DeliverMode deliverMode) { + mDeliverMode = deliverMode; + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnStartSending(const char* aMsgID, + uint32_t aMsgSize) { + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnStartSending(aMsgID, aMsgSize); + + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnProgress(const char* aMsgID, + uint32_t aProgress, + uint32_t aProgressMax) { + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnProgress(aMsgID, aProgress, aProgressMax); + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnStatus(const char* aMsgID, + const char16_t* aMsg) { + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnStatus(aMsgID, aMsg); + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnSendNotPerformed(const char* aMsgID, + nsresult aStatus) { + // since OnSendNotPerformed is called in the case where the user aborts the + // operation by closing the compose window, we need not do the stuff required + // for closing the windows. However we would need to do the other operations + // as below. + + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv); + if (msgCompose) + msgCompose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeProcessDone, aStatus); + + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnSendNotPerformed(aMsgID, aStatus); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeSendListener::OnTransportSecurityError( + const char* msgID, nsresult status, nsITransportSecurityInfo* secInfo, + nsACString const& location) { + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnTransportSecurityError(msgID, status, secInfo, + location); + + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnStopSending(const char* aMsgID, + nsresult aStatus, + const char16_t* aMsg, + nsIFile* returnFile) { + nsresult rv = NS_OK; + + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv); + if (msgCompose) { + nsCOMPtr<nsIMsgProgress> progress; + msgCompose->GetProgress(getter_AddRefs(progress)); + + if (NS_SUCCEEDED(aStatus)) { + nsCOMPtr<nsIMsgCompFields> compFields; + msgCompose->GetCompFields(getter_AddRefs(compFields)); + + // only process the reply flags if we successfully sent the message + msgCompose->ProcessReplyFlags(); + + // See if there is a composer window + bool hasDomWindow = true; + nsCOMPtr<mozIDOMWindowProxy> domWindow; + rv = msgCompose->GetDomWindow(getter_AddRefs(domWindow)); + if (NS_FAILED(rv) || !domWindow) hasDomWindow = false; + + // Close the window ONLY if we are not going to do a save operation + nsAutoString fieldsFCC; + if (NS_SUCCEEDED(compFields->GetFcc(fieldsFCC))) { + if (!fieldsFCC.IsEmpty()) { + if (fieldsFCC.LowerCaseEqualsLiteral("nocopy://")) { + msgCompose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK); + if (progress) { + progress->UnregisterListener(this); + progress->CloseProgressDialog(false); + } + if (hasDomWindow) msgCompose->CloseWindow(); + } + } + } else { + msgCompose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK); + if (progress) { + progress->UnregisterListener(this); + progress->CloseProgressDialog(false); + } + if (hasDomWindow) + msgCompose->CloseWindow(); // if we fail on the simple GetFcc call, + // close the window to be safe and avoid + // windows hanging around to prevent the + // app from exiting. + } + + // Remove the current draft msg when sending draft is done. + bool deleteDraft; + msgCompose->GetDeleteDraft(&deleteDraft); + if (deleteDraft) RemoveCurrentDraftMessage(msgCompose, false, false); + } else { + msgCompose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeProcessDone, aStatus); + if (progress) { + progress->CloseProgressDialog(true); + progress->UnregisterListener(this); + } + } + } + + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile); + + return rv; +} + +nsresult nsMsgComposeSendListener::OnGetDraftFolderURI( + const char* aMsgID, const nsACString& aFolderURI) { + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = + do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnGetDraftFolderURI(aMsgID, aFolderURI); + + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnStartCopy() { return NS_OK; } + +nsresult nsMsgComposeSendListener::OnProgress(uint32_t aProgress, + uint32_t aProgressMax) { + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnStopCopy(nsresult aStatus) { + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv); + if (msgCompose) { + if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater || + mDeliverMode == nsIMsgSend::nsMsgDeliverBackground || + mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft) { + msgCompose->RememberQueuedDisposition(); + } + + // Ok, if we are here, we are done with the send/copy operation so + // we have to do something with the window....SHOW if failed, Close + // if succeeded + + nsCOMPtr<nsIMsgProgress> progress; + msgCompose->GetProgress(getter_AddRefs(progress)); + if (progress) { + // Unregister ourself from msg compose progress + progress->UnregisterListener(this); + progress->CloseProgressDialog(NS_FAILED(aStatus)); + } + + msgCompose->NotifyStateListeners( + nsIMsgComposeNotificationType::ComposeProcessDone, aStatus); + + if (NS_SUCCEEDED(aStatus)) { + // We should only close the window if we are done. Things like templates + // and drafts aren't done so their windows should stay open + if (mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft || + mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate) { + msgCompose->NotifyStateListeners( + nsIMsgComposeNotificationType::SaveInFolderDone, aStatus); + // Remove the current draft msg when saving as draft/template is done. + msgCompose->SetDeleteDraft(true); + RemoveCurrentDraftMessage( + msgCompose, true, mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate); + } else { + // Remove (possible) draft if we're in send later mode + if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater || + mDeliverMode == nsIMsgSend::nsMsgDeliverBackground) { + msgCompose->SetDeleteDraft(true); + RemoveCurrentDraftMessage(msgCompose, true, false); + } + msgCompose->CloseWindow(); + } + } + msgCompose->ClearMessageSend(); + } + + return rv; +} + +nsresult nsMsgComposeSendListener::GetMsgFolder(nsIMsgCompose* compObj, + nsIMsgFolder** msgFolder) { + nsCString folderUri; + + nsresult rv = compObj->GetSavedFolderURI(folderUri); + NS_ENSURE_SUCCESS(rv, rv); + + return GetOrCreateFolder(folderUri, msgFolder); +} + +nsresult nsMsgComposeSendListener::RemoveDraftOrTemplate(nsIMsgCompose* compObj, + nsCString msgURI, + bool isSaveTemplate) { + nsresult rv; + nsCOMPtr<nsIMsgFolder> msgFolder; + nsCOMPtr<nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgDBHdr)); + NS_ASSERTION( + NS_SUCCEEDED(rv), + "RemoveDraftOrTemplate can't get msg header DB interface pointer"); + if (NS_SUCCEEDED(rv) && msgDBHdr) { + do { // Break on failure or removal not needed. + // Get the folder for the message resource. + rv = msgDBHdr->GetFolder(getter_AddRefs(msgFolder)); + NS_ASSERTION( + NS_SUCCEEDED(rv), + "RemoveDraftOrTemplate can't get msg folder interface pointer"); + if (NS_FAILED(rv) || !msgFolder) break; + + // Only do this if it's a drafts or templates folder. + uint32_t flags; + msgFolder->GetFlags(&flags); + if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates))) + break; + // Only delete a template when saving a new one, never delete a template + // when sending. + if (!isSaveTemplate && (flags & nsMsgFolderFlags::Templates)) break; + + // Only remove if the message is actually in the db. It might have only + // been in the use cache. + nsMsgKey key; + rv = msgDBHdr->GetMessageKey(&key); + if (NS_FAILED(rv)) break; + nsCOMPtr<nsIMsgDatabase> db; + msgFolder->GetMsgDatabase(getter_AddRefs(db)); + if (!db) break; + bool containsKey = false; + db->ContainsKey(key, &containsKey); + if (!containsKey) break; + + // Ready to delete the msg. + rv = msgFolder->DeleteMessages({&*msgDBHdr}, nullptr, true, false, + nullptr, false /*allowUndo*/); + NS_ASSERTION(NS_SUCCEEDED(rv), + "RemoveDraftOrTemplate can't delete message"); + } while (false); + } else { + // If we get here we have the case where the draft folder is on the server + // and it's not currently open (in thread pane), so draft msgs are saved to + // the server but they're not in our local DB. In this case, + // GetMsgDBHdrFromURI() will never find the msg. If the draft folder is a + // local one then we'll not get here because the draft msgs are saved to the + // local folder and are in local DB. Make sure the msg folder is imap. Even + // if we get here due to DB errors (worst case), we should still try to + // delete msg on the server because that's where the master copy of the msgs + // are stored, if draft folder is on the server. For local case, since DB is + // bad we can't do anything with it anyway so it'll be noop in this case. + rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder)); + if (NS_SUCCEEDED(rv) && msgFolder) { + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder); + NS_ASSERTION(imapFolder, + "The draft folder MUST be an imap folder in order to mark " + "the msg delete!"); + if (NS_SUCCEEDED(rv) && imapFolder) { + // Only do this if it's a drafts or templates folder. + uint32_t flags; + msgFolder->GetFlags(&flags); + if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates))) + return NS_OK; + // Only delete a template when saving a new one, never delete a template + // when sending. + if (!isSaveTemplate && (flags & nsMsgFolderFlags::Templates)) + return NS_OK; + + const char* str = PL_strchr(msgURI.get(), '#'); + NS_ASSERTION(str, "Failed to get current draft id url"); + if (str) { + nsAutoCString srcStr(str + 1); + nsresult err; + nsMsgKey messageID = srcStr.ToInteger(&err); + if (messageID != nsMsgKey_None) { + rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, true, + {messageID}, nullptr); + } + } + } + } + } + + return rv; +} + +/** + * Remove the current draft message since a new one will be saved. + * When we're coming to save a template, also delete the original template. + * This is necessary since auto-save doesn't delete the original template. + */ +nsresult nsMsgComposeSendListener::RemoveCurrentDraftMessage( + nsIMsgCompose* compObj, bool calledByCopy, bool isSaveTemplate) { + nsresult rv; + nsCOMPtr<nsIMsgCompFields> compFields = nullptr; + + rv = compObj->GetCompFields(getter_AddRefs(compFields)); + NS_ASSERTION(NS_SUCCEEDED(rv), + "RemoveCurrentDraftMessage can't get compose fields"); + if (NS_FAILED(rv) || !compFields) return rv; + + nsCString curDraftIdURL; + rv = compFields->GetDraftId(curDraftIdURL); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) { + rv = RemoveDraftOrTemplate(compObj, curDraftIdURL, isSaveTemplate); + if (NS_FAILED(rv)) NS_WARNING("Removing current draft failed"); + } else { + NS_WARNING("RemoveCurrentDraftMessage can't get draft id"); + } + + if (isSaveTemplate) { + nsCString templateIdURL; + rv = compFields->GetTemplateId(templateIdURL); + if (NS_SUCCEEDED(rv) && !templateIdURL.Equals(curDraftIdURL)) { + // Above we deleted an auto-saved draft, so here we need to delete + // the original template. + rv = RemoveDraftOrTemplate(compObj, templateIdURL, isSaveTemplate); + if (NS_FAILED(rv)) NS_WARNING("Removing original template failed"); + } + } + + // Now get the new uid so that next save will remove the right msg + // regardless whether or not the exiting msg can be deleted. + if (calledByCopy) { + nsMsgKey newUid = 0; + nsCOMPtr<nsIMsgFolder> savedToFolder; + nsCOMPtr<nsIMsgSend> msgSend; + rv = compObj->GetMessageSend(getter_AddRefs(msgSend)); + NS_ASSERTION(msgSend, "RemoveCurrentDraftMessage msgSend is null."); + if (NS_FAILED(rv) || !msgSend) return rv; + + rv = msgSend->GetMessageKey(&newUid); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure we have a folder interface pointer + rv = GetMsgFolder(compObj, getter_AddRefs(savedToFolder)); + + // Reset draft (uid) url with the new uid. + if (savedToFolder && newUid != nsMsgKey_None) { + uint32_t folderFlags; + savedToFolder->GetFlags(&folderFlags); + if (folderFlags & + (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)) { + nsCString newDraftIdURL; + rv = savedToFolder->GenerateMessageURI(newUid, newDraftIdURL); + NS_ENSURE_SUCCESS(rv, rv); + compFields->SetDraftId(newDraftIdURL); + if (isSaveTemplate) compFields->SetTemplateId(newDraftIdURL); + } + } + } + return rv; +} + +nsresult nsMsgComposeSendListener::SetMessageKey(nsMsgKey aMessageKey) { + return NS_OK; +} + +nsresult nsMsgComposeSendListener::GetMessageId(nsACString& messageId) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::OnStateChange( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aStateFlags, + nsresult aStatus) { + if (aStateFlags == nsIWebProgressListener::STATE_STOP) { + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj); + if (msgCompose) { + nsCOMPtr<nsIMsgProgress> progress; + msgCompose->GetProgress(getter_AddRefs(progress)); + + // Time to stop any pending operation... + if (progress) { + // Unregister ourself from msg compose progress + progress->UnregisterListener(this); + + bool bCanceled = false; + progress->GetProcessCanceledByUser(&bCanceled); + if (bCanceled) { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/" + "composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + nsString msg; + bundle->GetStringFromName("msgCancelling", msg); + progress->OnStatusChange(nullptr, nullptr, NS_OK, msg.get()); + } + } + + nsCOMPtr<nsIMsgSend> msgSend; + msgCompose->GetMessageSend(getter_AddRefs(msgSend)); + if (msgSend) msgSend->Abort(); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::OnProgressChange( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, int32_t aMaxTotalProgress) { + /* Ignore this call */ + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::OnLocationChange( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* location, + uint32_t aFlags) { + /* Ignore this call */ + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::OnStatusChange( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, + const char16_t* aMessage) { + /* Ignore this call */ + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::OnSecurityChange( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t state) { + /* Ignore this call */ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeSendListener::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + /* Ignore this call */ + return NS_OK; +} + +nsresult nsMsgCompose::ConvertHTMLToText(nsIFile* aSigFile, + nsString& aSigData) { + nsAutoString origBuf; + + nsresult rv = LoadDataFromFile(aSigFile, origBuf); + NS_ENSURE_SUCCESS(rv, rv); + + ConvertBufToPlainText(origBuf, false, true, true); + aSigData = origBuf; + return NS_OK; +} + +nsresult nsMsgCompose::ConvertTextToHTML(nsIFile* aSigFile, + nsString& aSigData) { + nsresult rv; + nsAutoString origBuf; + + rv = LoadDataFromFile(aSigFile, origBuf); + if (NS_FAILED(rv)) return rv; + + // Ok, once we are here, we need to escape the data to make sure that + // we don't do HTML stuff with plain text sigs. + nsCString escapedUTF8; + nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(origBuf), escapedUTF8); + aSigData.Append(NS_ConvertUTF8toUTF16(escapedUTF8)); + + return NS_OK; +} + +nsresult nsMsgCompose::LoadDataFromFile(nsIFile* file, nsString& sigData, + bool aAllowUTF8, bool aAllowUTF16) { + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory) { + NS_ERROR("file is a directory"); + return NS_MSG_ERROR_READING_FILE; + } + + nsAutoCString data; + nsresult rv = nsMsgCompose::SlurpFileToString(file, data); + NS_ENSURE_SUCCESS(rv, rv); + + const char* readBuf = data.get(); + int32_t readSize = data.Length(); + + nsAutoCString sigEncoding(nsMsgI18NParseMetaCharset(file)); + bool removeSigCharset = !sigEncoding.IsEmpty() && m_composeHTML; + + if (sigEncoding.IsEmpty()) { + if (aAllowUTF8 && mozilla::IsUtf8(nsDependentCString(readBuf))) { + sigEncoding.AssignLiteral("UTF-8"); + } else if (sigEncoding.IsEmpty() && aAllowUTF16 && readSize % 2 == 0 && + readSize >= 2 && + ((readBuf[0] == char(0xFE) && readBuf[1] == char(0xFF)) || + (readBuf[0] == char(0xFF) && readBuf[1] == char(0xFE)))) { + sigEncoding.AssignLiteral("UTF-16"); + } else { + // Autodetect encoding for plain text files w/o meta charset + nsAutoCString textFileCharset; + rv = MsgDetectCharsetFromFile(file, textFileCharset); + NS_ENSURE_SUCCESS(rv, rv); + sigEncoding.Assign(textFileCharset); + } + } + + if (NS_FAILED(nsMsgI18NConvertToUnicode(sigEncoding, data, sigData))) + CopyASCIItoUTF16(data, sigData); + + // remove sig meta charset to allow user charset override during composition + if (removeSigCharset) { + nsAutoCString metaCharset("charset="); + metaCharset.Append(sigEncoding); + int32_t pos = sigData.LowerCaseFindASCII(metaCharset); + if (pos != kNotFound) sigData.Cut(pos, metaCharset.Length()); + } + return NS_OK; +} + +/** + * If the data contains file URLs, convert them to data URLs instead. + * This is intended to be used in for signature files, so that we can make sure + * images loaded into the editor are available on send. + */ +nsresult nsMsgCompose::ReplaceFileURLs(nsString& aData) { + // XXX This code is rather incomplete since it looks for "file://" even + // outside tags. + + int32_t offset = 0; + while (true) { + int32_t fPos = aData.LowerCaseFindASCII("file://", offset); + if (fPos == kNotFound) { + break; // All done. + } + bool quoted = false; + char16_t q = 'x'; // initialise to anything to keep compilers happy. + if (fPos > 0) { + q = aData.CharAt(fPos - 1); + quoted = (q == '"' || q == '\''); + } + int32_t end = kNotFound; + if (quoted) { + end = aData.FindChar(q, fPos); + } else { + int32_t spacePos = aData.FindChar(' ', fPos); + int32_t gtPos = aData.FindChar('>', fPos); + if (gtPos != kNotFound && spacePos != kNotFound) { + end = (spacePos < gtPos) ? spacePos : gtPos; + } else if (gtPos == kNotFound && spacePos != kNotFound) { + end = spacePos; + } else if (gtPos != kNotFound && spacePos == kNotFound) { + end = gtPos; + } + } + if (end == kNotFound) { + break; + } + nsString fileURL; + fileURL = Substring(aData, fPos, end - fPos); + nsString dataURL; + nsresult rv = DataURLForFileURL(fileURL, dataURL); + if (NS_SUCCEEDED(rv)) { + aData.Replace(fPos, fileURL.Length(), dataURL); + offset = fPos + dataURL.Length(); + } else { + // If this one failed, maybe because the file wasn't found, + // continue to process the next one. + offset = fPos + fileURL.Length(); + } + } + return NS_OK; +} + +nsresult nsMsgCompose::DataURLForFileURL(const nsAString& aFileURL, + nsAString& aDataURL) { + nsresult rv; + nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> fileUri; + rv = + NS_NewURI(getter_AddRefs(fileUri), NS_ConvertUTF16toUTF8(aFileURL).get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(fileUri, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFile> file; + rv = fileUrl->GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString type; + rv = mime->GetTypeFromFile(file, type); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString data; + rv = nsMsgCompose::SlurpFileToString(file, data); + NS_ENSURE_SUCCESS(rv, rv); + + aDataURL.AssignLiteral("data:"); + AppendUTF8toUTF16(type, aDataURL); + + nsAutoString filename; + rv = file->GetLeafName(filename); + if (NS_SUCCEEDED(rv)) { + nsAutoCString fn; + MsgEscapeURL( + NS_ConvertUTF16toUTF8(filename), + nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, + fn); + if (!fn.IsEmpty()) { + aDataURL.AppendLiteral(";filename="); + aDataURL.Append(NS_ConvertUTF8toUTF16(fn)); + } + } + + aDataURL.AppendLiteral(";base64,"); + char* result = PL_Base64Encode(data.get(), data.Length(), nullptr); + nsDependentCString base64data(result); + NS_ENSURE_SUCCESS(rv, rv); + AppendUTF8toUTF16(base64data, aDataURL); + return NS_OK; +} + +nsresult nsMsgCompose::SlurpFileToString(nsIFile* aFile, nsACString& aString) { + aString.Truncate(); + + nsCOMPtr<nsIURI> fileURI; + nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), fileURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIInputStream> stream; + rv = channel->Open(getter_AddRefs(stream)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_ConsumeStream(stream, UINT32_MAX, aString); + if (NS_FAILED(rv)) { + return rv; + } + + rv = stream->Close(); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +nsresult nsMsgCompose::BuildQuotedMessageAndSignature(void) { + // + // This should never happen...if it does, just bail out... + // + NS_ASSERTION(m_editor, "BuildQuotedMessageAndSignature but no editor!"); + if (!m_editor) return NS_ERROR_FAILURE; + + // We will fire off the quote operation and wait for it to + // finish before we actually do anything with Ender... + return QuoteOriginalMessage(); +} + +// +// This will process the signature file for the user. This method +// will always append the results to the mMsgBody member variable. +// +nsresult nsMsgCompose::ProcessSignature(nsIMsgIdentity* identity, bool aQuoted, + nsString* aMsgBody) { + nsresult rv = NS_OK; + + // Now, we can get sort of fancy. This is the time we need to check + // for all sorts of user defined stuff, like signatures and editor + // types and the like! + // + // user_pref(".....sig_file", "y:\\sig.html"); + // user_pref(".....attach_signature", true); + // user_pref(".....htmlSigText", "unicode sig"); + // + // Note: We will have intelligent signature behavior in that we + // look at the signature file first...if the extension is .htm or + // .html, we assume its HTML, otherwise, we assume it is plain text + // + // ...and that's not all! What we will also do now is look and see if + // the file is an image file. If it is an image file, then we should + // insert the correct HTML into the composer to have it work, but if we + // are doing plain text compose, we should insert some sort of message + // saying "Image Signature Omitted" or something (not done yet). + // + // If there's a sig pref, it will only be used if there is no sig file + // defined, thus if attach_signature is checked, htmlSigText is ignored (bug + // 324495). Plain-text signatures may or may not have a trailing line break + // (bug 428040). + + bool attachFile = false; + bool useSigFile = false; + bool htmlSig = false; + bool imageSig = false; + nsAutoString sigData; + nsAutoString sigOutput; + int32_t reply_on_top = 0; + bool sig_bottom = true; + bool suppressSigSep = false; + + nsCOMPtr<nsIFile> sigFile; + if (identity) { + if (!CheckIncludeSignaturePrefs(identity)) return NS_OK; + + identity->GetReplyOnTop(&reply_on_top); + identity->GetSigBottom(&sig_bottom); + identity->GetSuppressSigSep(&suppressSigSep); + + rv = identity->GetAttachSignature(&attachFile); + if (NS_SUCCEEDED(rv) && attachFile) { + rv = identity->GetSignature(getter_AddRefs(sigFile)); + if (NS_SUCCEEDED(rv) && sigFile) { + if (!sigFile->NativePath().IsEmpty()) { + bool exists = false; + sigFile->Exists(&exists); + if (exists) { + useSigFile = true; // ok, there's a signature file + + // Now, most importantly, we need to figure out what the content + // type is for this signature...if we can't, we assume text + nsAutoCString sigContentType; + nsresult rv2; // don't want to clobber the other rv + nsCOMPtr<nsIMIMEService> mimeFinder( + do_GetService(NS_MIMESERVICE_CONTRACTID, &rv2)); + if (NS_SUCCEEDED(rv2)) { + rv2 = mimeFinder->GetTypeFromFile(sigFile, sigContentType); + if (NS_SUCCEEDED(rv2)) { + if (StringBeginsWith(sigContentType, "image/"_ns, + nsCaseInsensitiveCStringComparator)) + imageSig = true; + else if (sigContentType.Equals( + TEXT_HTML, nsCaseInsensitiveCStringComparator)) + htmlSig = true; + } + } + } + } + } + } + } + + // Unless signature to be attached from file, use preference value; + // the htmlSigText value is always going to be treated as html if + // the htmlSigFormat pref is true, otherwise it is considered text + nsAutoString prefSigText; + if (identity && !attachFile) identity->GetHtmlSigText(prefSigText); + // Now, if they didn't even want to use a signature, we should + // just return nicely. + // + if ((!useSigFile && prefSigText.IsEmpty()) || NS_FAILED(rv)) return NS_OK; + + static const char htmlBreak[] = "<br>"; + static const char dashes[] = "-- "; + static const char htmlsigopen[] = "<div class=\"moz-signature\">"; + static const char htmlsigclose[] = "</div>"; /* XXX: Due to a bug in + 4.x' HTML editor, it will not be able to + break this HTML sig, if quoted (for the user to + interleave a comment). */ + static const char _preopen[] = "<pre class=\"moz-signature\" cols=%d>"; + char* preopen; + static const char preclose[] = "</pre>"; + + int32_t wrapLength = 72; // setup default value in case GetWrapLength failed + GetWrapLength(&wrapLength); + preopen = PR_smprintf(_preopen, wrapLength); + if (!preopen) return NS_ERROR_OUT_OF_MEMORY; + + bool paragraphMode = + mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false); + + if (imageSig) { + // We have an image signature. If we're using the in HTML composer, we + // should put in the appropriate HTML for inclusion, otherwise, do nothing. + if (m_composeHTML) { + if (!paragraphMode) sigOutput.AppendLiteral(htmlBreak); + sigOutput.AppendLiteral(htmlsigopen); + if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) && + (reply_on_top != 1 || sig_bottom || !aQuoted)) { + sigOutput.AppendLiteral(dashes); + } + + sigOutput.AppendLiteral(htmlBreak); + sigOutput.AppendLiteral("<img src='"); + + nsCOMPtr<nsIURI> fileURI; + nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), sigFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCString fileURL; + fileURI->GetSpec(fileURL); + + nsString dataURL; + rv = DataURLForFileURL(NS_ConvertUTF8toUTF16(fileURL), dataURL); + if (NS_SUCCEEDED(rv)) { + sigOutput.Append(dataURL); + } + sigOutput.AppendLiteral("' border=0>"); + sigOutput.AppendLiteral(htmlsigclose); + } + } else if (useSigFile) { + // is this a text sig with an HTML editor? + if ((m_composeHTML) && (!htmlSig)) { + ConvertTextToHTML(sigFile, sigData); + } + // is this a HTML sig with a text window? + else if ((!m_composeHTML) && (htmlSig)) { + ConvertHTMLToText(sigFile, sigData); + } else { // We have a match... + LoadDataFromFile(sigFile, sigData); // Get the data! + ReplaceFileURLs(sigData); + } + } + + // if we have a prefSigText, append it to sigData. + if (!prefSigText.IsEmpty()) { + // set htmlSig if the pref is supposed to contain HTML code, defaults to + // false + rv = identity->GetHtmlSigFormat(&htmlSig); + if (NS_FAILED(rv)) htmlSig = false; + + if (!m_composeHTML) { + if (htmlSig) ConvertBufToPlainText(prefSigText, false, true, true); + sigData.Append(prefSigText); + } else { + if (!htmlSig) { + nsCString escapedUTF8; + nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(prefSigText), escapedUTF8); + sigData.Append(NS_ConvertUTF8toUTF16(escapedUTF8)); + } else { + ReplaceFileURLs(prefSigText); + sigData.Append(prefSigText); + } + } + } + + // post-processing for plain-text signatures to ensure we end in CR, LF, or + // CRLF + if (!htmlSig && !m_composeHTML) { + int32_t sigLength = sigData.Length(); + if (sigLength > 0 && !(sigData.CharAt(sigLength - 1) == '\r') && + !(sigData.CharAt(sigLength - 1) == '\n')) + sigData.AppendLiteral(CRLF); + } + + // Now that sigData holds data...if any, append it to the body in a nice + // looking manner + if (!sigData.IsEmpty()) { + if (m_composeHTML) { + if (!paragraphMode) sigOutput.AppendLiteral(htmlBreak); + + if (htmlSig) + sigOutput.AppendLiteral(htmlsigopen); + else + sigOutput.Append(NS_ConvertASCIItoUTF16(preopen)); + } + + if ((reply_on_top != 1 || sig_bottom || !aQuoted) && + sigData.Find(u"\r-- \r") < 0 && sigData.Find(u"\n-- \n") < 0 && + sigData.Find(u"\n-- \r") < 0) { + nsDependentSubstring firstFourChars(sigData, 0, 4); + + if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) && + !(firstFourChars.EqualsLiteral("-- \n") || + firstFourChars.EqualsLiteral("-- \r"))) { + sigOutput.AppendLiteral(dashes); + + if (!m_composeHTML || !htmlSig) + sigOutput.AppendLiteral(CRLF); + else if (m_composeHTML) + sigOutput.AppendLiteral(htmlBreak); + } + } + + // add CRLF before signature for plain-text mode if signature comes before + // quote + if (!m_composeHTML && reply_on_top == 1 && !sig_bottom && aQuoted) + sigOutput.AppendLiteral(CRLF); + + sigOutput.Append(sigData); + + if (m_composeHTML) { + if (htmlSig) + sigOutput.AppendLiteral(htmlsigclose); + else + sigOutput.AppendLiteral(preclose); + } + } + + aMsgBody->Append(sigOutput); + PR_Free(preopen); + return NS_OK; +} + +nsresult nsMsgCompose::BuildBodyMessageAndSignature() { + nsresult rv = NS_OK; + + // + // This should never happen...if it does, just bail out... + // + if (!m_editor) return NS_ERROR_FAILURE; + + // + // Now, we have the body so we can just blast it into the + // composition editor window. + // + nsAutoString body; + m_compFields->GetBody(body); + + // Some time we want to add a signature and sometime we won't. + // Let's figure that out now... + bool addSignature; + bool isQuoted = false; + switch (mType) { + case nsIMsgCompType::ForwardInline: + addSignature = true; + isQuoted = true; + break; + case nsIMsgCompType::New: + case nsIMsgCompType::MailToUrl: /* same as New */ + case nsIMsgCompType::Reply: /* should not happen! but just in case */ + case nsIMsgCompType::ReplyAll: /* should not happen! but just in case */ + case nsIMsgCompType::ReplyToList: /* should not happen! but just in case */ + case nsIMsgCompType::ForwardAsAttachment: /* should not happen! but just in + case */ + case nsIMsgCompType::NewsPost: + case nsIMsgCompType::ReplyToGroup: + case nsIMsgCompType::ReplyToSender: + case nsIMsgCompType::ReplyToSenderAndGroup: + addSignature = true; + break; + + case nsIMsgCompType::Draft: + case nsIMsgCompType::Template: + case nsIMsgCompType::Redirect: + case nsIMsgCompType::EditAsNew: + addSignature = false; + break; + + default: + addSignature = false; + break; + } + + nsAutoString tSignature; + if (addSignature) ProcessSignature(m_identity, isQuoted, &tSignature); + + // if type is new, but we have body, this is probably a mapi send, so we need + // to replace '\n' with <br> so that the line breaks won't be lost by html. if + // mailtourl, do the same. + if (m_composeHTML && + (mType == nsIMsgCompType::New || mType == nsIMsgCompType::MailToUrl)) + body.ReplaceSubstring(u"\n"_ns, u"<br>"_ns); + + // Restore flowed text wrapping for Drafts/Templates. + // Look for unquoted lines - if we have an unquoted line + // that ends in a space, join this line with the next one + // by removing the end of line char(s). + int32_t wrapping_enabled = 0; + GetWrapLength(&wrapping_enabled); + if (!m_composeHTML && wrapping_enabled) { + bool quote = false; + for (uint32_t i = 0; i < body.Length(); i++) { + if (i == 0 || body[i - 1] == '\n') // newline + { + if (body[i] == '>') { + quote = true; + continue; + } + nsString s(Substring(body, i, 10)); + if (StringBeginsWith(s, u"-- \r"_ns) || + StringBeginsWith(s, u"-- \n"_ns)) { + i += 4; + continue; + } + if (StringBeginsWith(s, u"- -- \r"_ns) || + StringBeginsWith(s, u"- -- \n"_ns)) { + i += 6; + continue; + } + } + if (body[i] == '\n' && i > 1) { + if (quote) { + quote = false; + continue; // skip quoted lines + } + uint32_t j = i - 1; // look backward for space + if (body[j] == '\r') j--; + if (body[j] == ' ') // join this line with next one + body.Cut(j + 1, i - j); // remove CRLF + } + } + } + + nsString empty; + rv = ConvertAndLoadComposeWindow(empty, body, tSignature, false, + m_composeHTML); + + return rv; +} + +nsresult nsMsgCompose::NotifyStateListeners(int32_t aNotificationType, + nsresult aResult) { + nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener>>::ForwardIterator iter( + mStateListeners); + nsCOMPtr<nsIMsgComposeStateListener> thisListener; + + while (iter.HasMore()) { + thisListener = iter.GetNext(); + + switch (aNotificationType) { + case nsIMsgComposeNotificationType::ComposeFieldsReady: + thisListener->NotifyComposeFieldsReady(); + break; + + case nsIMsgComposeNotificationType::ComposeProcessDone: + thisListener->ComposeProcessDone(aResult); + break; + + case nsIMsgComposeNotificationType::SaveInFolderDone: + thisListener->SaveInFolderDone(m_folderName.get()); + break; + + case nsIMsgComposeNotificationType::ComposeBodyReady: + thisListener->NotifyComposeBodyReady(); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown notification"); + break; + } + } + + return NS_OK; +} + +nsresult nsMsgCompose::AttachmentPrettyName(const nsACString& scheme, + const char* charset, + nsACString& _retval) { + nsresult rv; + + if (StringHead(scheme, 5).LowerCaseEqualsLiteral("file:")) { + nsCOMPtr<nsIFile> file; + rv = NS_GetFileFromURLSpec(scheme, getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF16toUTF8(leafName, _retval); + return rv; + } + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString retUrl; + rv = textToSubURI->UnEscapeURIForUI(scheme, retUrl); + + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(retUrl, _retval); + } else { + _retval.Assign(scheme); + } + if (StringHead(scheme, 5).LowerCaseEqualsLiteral("http:")) _retval.Cut(0, 7); + + return NS_OK; +} + +/** + * Retrieve address book directories and mailing lists. + * + * @param aDirUri directory URI + * @param allDirectoriesArray retrieved directories and sub-directories + * @param allMailListArray retrieved maillists + */ +nsresult nsMsgCompose::GetABDirAndMailLists( + const nsACString& aDirUri, nsCOMArray<nsIAbDirectory>& aDirArray, + nsTArray<nsMsgMailList>& aMailListArray) { + static bool collectedAddressbookFound = false; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = + do_GetService("@mozilla.org/abmanager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (aDirUri.Equals(kAllDirectoryRoot)) { + nsTArray<RefPtr<nsIAbDirectory>> directories; + rv = abManager->GetDirectories(directories); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count = directories.Length(); + nsCString uri; + for (uint32_t i = 0; i < count; i++) { + rv = directories[i]->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t pos; + if (uri.EqualsLiteral(kPersonalAddressbookUri)) { + pos = 0; + } else { + uint32_t count = aDirArray.Count(); + + if (uri.EqualsLiteral(kCollectedAddressbookUri)) { + collectedAddressbookFound = true; + pos = count; + } else { + if (collectedAddressbookFound && count > 1) { + pos = count - 1; + } else { + pos = count; + } + } + } + + aDirArray.InsertObjectAt(directories[i], pos); + rv = GetABDirAndMailLists(uri, aDirArray, aMailListArray); + } + + return NS_OK; + } + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(aDirUri, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIAbDirectory>> subDirectories; + rv = directory->GetChildNodes(subDirectories); + NS_ENSURE_SUCCESS(rv, rv); + for (nsIAbDirectory* subDirectory : subDirectories) { + bool bIsMailList; + if (NS_SUCCEEDED(subDirectory->GetIsMailList(&bIsMailList)) && + bIsMailList) { + aMailListArray.AppendElement(subDirectory); + } + } + return rv; +} + +/** + * Comparator for use with nsTArray::IndexOf to find a recipient. + * This comparator will check if an "address" is a mail list or not. + */ +struct nsMsgMailListComparator { + // A mail list will have one of the formats + // 1) "mName <mDescription>" when the list has a description + // 2) "mName <mName>" when the list lacks description + // A recipient is of the form "mName <mEmail>" - for equality the list + // name must be the same. The recipient "email" must match the list name for + // case 1, and the list description for case 2. + bool Equals(const nsMsgMailList& mailList, + const nsMsgRecipient& recipient) const { + if (!mailList.mName.Equals(recipient.mName, + nsCaseInsensitiveStringComparator)) + return false; + return mailList.mDescription.IsEmpty() + ? mailList.mName.Equals(recipient.mEmail, + nsCaseInsensitiveStringComparator) + : mailList.mDescription.Equals( + recipient.mEmail, nsCaseInsensitiveStringComparator); + } +}; + +/** + * Comparator for use with nsTArray::IndexOf to find a recipient. + */ +struct nsMsgRecipientComparator { + bool Equals(const nsMsgRecipient& recipient, + const nsMsgRecipient& recipientToFind) const { + if (!recipient.mEmail.Equals(recipientToFind.mEmail, + nsCaseInsensitiveStringComparator)) + return false; + + if (!recipient.mName.Equals(recipientToFind.mName, + nsCaseInsensitiveStringComparator)) + return false; + + return true; + } +}; + +/** + * This function recursively resolves a mailing list and returns individual + * email addresses. Nested lists are supported. It maintains an array of + * already visited mailing lists to avoid endless recursion. + * + * @param aMailList the list + * @param allDirectoriesArray all directories + * @param allMailListArray all maillists + * @param mailListProcessed maillists processed (to avoid recursive lists) + * @param aListMembers list members + */ +nsresult nsMsgCompose::ResolveMailList( + nsIAbDirectory* aMailList, nsCOMArray<nsIAbDirectory>& allDirectoriesArray, + nsTArray<nsMsgMailList>& allMailListArray, + nsTArray<nsMsgMailList>& mailListProcessed, + nsTArray<nsMsgRecipient>& aListMembers) { + nsresult rv = NS_OK; + + nsTArray<RefPtr<nsIAbCard>> mailListAddresses; + rv = aMailList->GetChildCards(mailListAddresses); + NS_ENSURE_SUCCESS(rv, rv); + + for (nsIAbCard* existingCard : mailListAddresses) { + nsMsgRecipient newRecipient; + + rv = existingCard->GetDisplayName(newRecipient.mName); + NS_ENSURE_SUCCESS(rv, rv); + rv = existingCard->GetPrimaryEmail(newRecipient.mEmail); + NS_ENSURE_SUCCESS(rv, rv); + + if (newRecipient.mName.IsEmpty() && newRecipient.mEmail.IsEmpty()) { + continue; + } + + // First check if it's a mailing list. + size_t index = + allMailListArray.IndexOf(newRecipient, 0, nsMsgMailListComparator()); + if (index != allMailListArray.NoIndex && + allMailListArray[index].mDirectory) { + // Check if maillist processed. + if (mailListProcessed.Contains(newRecipient, nsMsgMailListComparator())) { + continue; + } + + nsCOMPtr<nsIAbDirectory> directory2(allMailListArray[index].mDirectory); + + // Add mailList to mailListProcessed. + mailListProcessed.AppendElement(directory2); + + // Resolve mailList members. + rv = ResolveMailList(directory2, allDirectoriesArray, allMailListArray, + mailListProcessed, aListMembers); + NS_ENSURE_SUCCESS(rv, rv); + + continue; + } + + // Check if recipient is in aListMembers. + if (aListMembers.Contains(newRecipient, nsMsgRecipientComparator())) { + continue; + } + + // Now we need to insert the new address into the list of recipients. + newRecipient.mCard = existingCard; + newRecipient.mDirectory = aMailList; + + aListMembers.AppendElement(newRecipient); + } + + return rv; +} + +/** + * Lookup the recipients as specified in the compose fields (To, Cc, Bcc) + * in the address books and return an array of individual recipients. + * Mailing lists are replaced by the cards they contain, nested and recursive + * lists are taken care of, recipients contained in multiple lists are only + * added once. + * + * @param recipientsList (out) recipient array + */ +nsresult nsMsgCompose::LookupAddressBook(RecipientsArray& recipientsList) { + nsresult rv = NS_OK; + + // First, build some arrays with the original recipients. + + nsAutoString originalRecipients[MAX_OF_RECIPIENT_ARRAY]; + m_compFields->GetTo(originalRecipients[0]); + m_compFields->GetCc(originalRecipients[1]); + m_compFields->GetBcc(originalRecipients[2]); + + for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i) { + if (originalRecipients[i].IsEmpty()) continue; + + rv = m_compFields->SplitRecipientsEx(originalRecipients[i], + recipientsList[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Then look them up in the Addressbooks + bool stillNeedToSearch = true; + nsCOMPtr<nsIAbDirectory> abDirectory; + nsCOMPtr<nsIAbCard> existingCard; + nsTArray<nsMsgMailList> mailListArray; + nsTArray<nsMsgMailList> mailListProcessed; + + nsCOMArray<nsIAbDirectory> addrbookDirArray; + rv = GetABDirAndMailLists(nsLiteralCString(kAllDirectoryRoot), + addrbookDirArray, mailListArray); + if (NS_FAILED(rv)) return rv; + + nsString dirPath; + uint32_t nbrAddressbook = addrbookDirArray.Count(); + + for (uint32_t k = 0; k < nbrAddressbook && stillNeedToSearch; ++k) { + // Avoid recursive mailing lists. + if (abDirectory && (addrbookDirArray[k] == abDirectory)) { + stillNeedToSearch = false; + break; + } + + abDirectory = addrbookDirArray[k]; + if (!abDirectory) continue; + + stillNeedToSearch = false; + for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; i++) { + mailListProcessed.Clear(); + + // Note: We check this each time to allow for length changes. + for (uint32_t j = 0; j < recipientsList[i].Length(); j++) { + nsMsgRecipient& recipient = recipientsList[i][j]; + if (!recipient.mDirectory) { + // First check if it's a mailing list. + size_t index = + mailListArray.IndexOf(recipient, 0, nsMsgMailListComparator()); + if (index != mailListArray.NoIndex && + mailListArray[index].mDirectory) { + // Check mailList Processed. + if (mailListProcessed.Contains(recipient, + nsMsgMailListComparator())) { + // Remove from recipientsList. + recipientsList[i].RemoveElementAt(j--); + continue; + } + + nsCOMPtr<nsIAbDirectory> directory(mailListArray[index].mDirectory); + + // Add mailList to mailListProcessed. + mailListProcessed.AppendElement(directory); + + // Resolve mailList members. + nsTArray<nsMsgRecipient> members; + rv = ResolveMailList(directory, addrbookDirArray, mailListArray, + mailListProcessed, members); + NS_ENSURE_SUCCESS(rv, rv); + + // Remove mailList from recipientsList. + recipientsList[i].RemoveElementAt(j); + + // Merge members into recipientsList[i]. + uint32_t pos = 0; + for (uint32_t c = 0; c < members.Length(); c++) { + nsMsgRecipient& member = members[c]; + if (!recipientsList[i].Contains(member, + nsMsgRecipientComparator())) { + recipientsList[i].InsertElementAt(j + pos, member); + pos++; + } + } + } else { + // Find a card that contains this e-mail address. + rv = abDirectory->CardForEmailAddress( + NS_ConvertUTF16toUTF8(recipient.mEmail), + getter_AddRefs(existingCard)); + if (NS_SUCCEEDED(rv) && existingCard) { + recipient.mCard = existingCard; + recipient.mDirectory = abDirectory; + } else { + stillNeedToSearch = true; + } + } + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::ExpandMailingLists() { + RecipientsArray recipientsList; + nsresult rv = LookupAddressBook(recipientsList); + NS_ENSURE_SUCCESS(rv, rv); + + // Reset the final headers with the expanded mailing lists. + nsAutoString recipientsStr; + + for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i) { + uint32_t nbrRecipients = recipientsList[i].Length(); + if (nbrRecipients == 0) continue; + recipientsStr.Truncate(); + + // Note: We check this each time to allow for length changes. + for (uint32_t j = 0; j < recipientsList[i].Length(); ++j) { + nsMsgRecipient& recipient = recipientsList[i][j]; + + if (!recipientsStr.IsEmpty()) recipientsStr.Append(char16_t(',')); + nsAutoString address; + MakeMimeAddress(recipient.mName, recipient.mEmail, address); + recipientsStr.Append(address); + + if (recipient.mCard) { + bool readOnly; + rv = recipient.mDirectory->GetReadOnly(&readOnly); + NS_ENSURE_SUCCESS(rv, rv); + + // Bump the popularity index for this card since we are about to send + // e-mail to it. + if (!readOnly) { + uint32_t popularityIndex = 0; + if (NS_FAILED(recipient.mCard->GetPropertyAsUint32( + kPopularityIndexProperty, &popularityIndex))) { + // TB 2 wrote the popularity value as hex, so if we get here, + // then we've probably got a hex value. We'll convert it back + // to decimal, as that's the best we can do. + + nsCString hexPopularity; + if (NS_SUCCEEDED(recipient.mCard->GetPropertyAsAUTF8String( + kPopularityIndexProperty, hexPopularity))) { + nsresult errorCode = NS_OK; + popularityIndex = hexPopularity.ToInteger(&errorCode, 16); + if (NS_FAILED(errorCode)) + // We failed, just set it to zero. + popularityIndex = 0; + } else + // We couldn't get it as a string either, so just reset to zero. + popularityIndex = 0; + } + + recipient.mCard->SetPropertyAsUint32(kPopularityIndexProperty, + ++popularityIndex); + recipient.mDirectory->ModifyCard(recipient.mCard); + } + } + } + + switch (i) { + case 0: + m_compFields->SetTo(recipientsStr); + break; + case 1: + m_compFields->SetCc(recipientsStr); + break; + case 2: + m_compFields->SetBcc(recipientsStr); + break; + } + } + + return NS_OK; +} + +/** + * Decides which tags trigger which convertible mode, + * i.e. here is the logic for BodyConvertible. + * Note: Helper function. Parameters are not checked. + */ +void nsMsgCompose::TagConvertible(Element* node, int32_t* _retval) { + *_retval = nsIMsgCompConvertible::No; + + nsAutoString element; + element = node->NodeName(); + + // A style attribute on any element can change layout in any way, + // so that is not convertible. + nsAutoString attribValue; + node->GetAttribute(u"style"_ns, attribValue); + if (!attribValue.IsEmpty()) { + *_retval = nsIMsgCompConvertible::No; + return; + } + + // moz-* classes are used internally by the editor and mail composition + // (like moz-cite-prefix or moz-signature). Those can be discarded. + // But any other ones are unconvertible. Style can be attached to them or any + // other context (e.g. in microformats). + node->GetAttribute(u"class"_ns, attribValue); + if (!attribValue.IsEmpty()) { + if (StringBeginsWith(attribValue, u"moz-"_ns, + nsCaseInsensitiveStringComparator)) { + // We assume that anything with a moz-* class is convertible regardless of + // the tag, because we add, for example, class="moz-signature" to HTML + // messages and we still want to be able to downgrade them. + *_retval = nsIMsgCompConvertible::Plain; + } else { + *_retval = nsIMsgCompConvertible::No; + } + + return; + } + + // ID attributes can contain attached style/context or be target of links + // so we should preserve them. + node->GetAttribute(u"id"_ns, attribValue); + if (!attribValue.IsEmpty()) { + *_retval = nsIMsgCompConvertible::No; + return; + } + + // Alignment is not convertible to plaintext; editor currently uses this. + node->GetAttribute(u"align"_ns, attribValue); + if (!attribValue.IsEmpty()) { + *_retval = nsIMsgCompConvertible::No; + return; + } + + // Title attribute is not convertible to plaintext; + // this also preserves any links with titles. + node->GetAttribute(u"title"_ns, attribValue); + if (!attribValue.IsEmpty()) { + *_retval = nsIMsgCompConvertible::No; + return; + } + + // Treat <font face="monospace"> as converible to plaintext. + if (element.LowerCaseEqualsLiteral("font")) { + node->GetAttribute(u"size"_ns, attribValue); + if (!attribValue.IsEmpty()) { + *_retval = nsIMsgCompConvertible::No; + return; + } + node->GetAttribute(u"face"_ns, attribValue); + if (attribValue.LowerCaseEqualsLiteral("monospace")) { + *_retval = nsIMsgCompConvertible::Plain; + } + } + + if ( // Considered convertible to plaintext: Some "simple" elements + // without non-convertible attributes like style, class, id, + // or align (see above). + element.LowerCaseEqualsLiteral("br") || + element.LowerCaseEqualsLiteral("p") || + element.LowerCaseEqualsLiteral("tt") || + element.LowerCaseEqualsLiteral("html") || + element.LowerCaseEqualsLiteral("head") || + element.LowerCaseEqualsLiteral("meta") || + element.LowerCaseEqualsLiteral("title")) { + *_retval = nsIMsgCompConvertible::Plain; + } else if ( + // element.LowerCaseEqualsLiteral("blockquote") || // see below + element.LowerCaseEqualsLiteral("ul") || + element.LowerCaseEqualsLiteral("ol") || + element.LowerCaseEqualsLiteral("li") || + element.LowerCaseEqualsLiteral("dl") || + element.LowerCaseEqualsLiteral("dt") || + element.LowerCaseEqualsLiteral("dd")) { + *_retval = nsIMsgCompConvertible::Yes; + } else if ( + // element.LowerCaseEqualsLiteral("a") || // see below + element.LowerCaseEqualsLiteral("h1") || + element.LowerCaseEqualsLiteral("h2") || + element.LowerCaseEqualsLiteral("h3") || + element.LowerCaseEqualsLiteral("h4") || + element.LowerCaseEqualsLiteral("h5") || + element.LowerCaseEqualsLiteral("h6") || + element.LowerCaseEqualsLiteral("hr") || + element.LowerCaseEqualsLiteral("pre") || + (mConvertStructs && (element.LowerCaseEqualsLiteral("em") || + element.LowerCaseEqualsLiteral("strong") || + element.LowerCaseEqualsLiteral("code") || + element.LowerCaseEqualsLiteral("b") || + element.LowerCaseEqualsLiteral("i") || + element.LowerCaseEqualsLiteral("u")))) { + *_retval = nsIMsgCompConvertible::Altering; + } else if (element.LowerCaseEqualsLiteral("body")) { + *_retval = nsIMsgCompConvertible::Plain; + + if (node->HasAttribute(u"background"_ns) || // There is a background image + node->HasAttribute( + u"dir"_ns)) { // dir=rtl attributes should not downconvert + *_retval = nsIMsgCompConvertible::No; + } else { + nsAutoString color; + if (node->HasAttribute(u"text"_ns)) { + node->GetAttribute(u"text"_ns, color); + if (!color.EqualsLiteral("#000000")) + *_retval = nsIMsgCompConvertible::Altering; + } + if (*_retval != nsIMsgCompConvertible::Altering && // small optimization + node->HasAttribute(u"bgcolor"_ns)) { + node->GetAttribute(u"bgcolor"_ns, color); + if (!color.LowerCaseEqualsLiteral("#ffffff")) + *_retval = nsIMsgCompConvertible::Altering; + } + } + + // ignore special color setting for link, vlink and alink at this point. + } else if (element.LowerCaseEqualsLiteral("blockquote")) { + // Skip <blockquote type="cite"> + *_retval = nsIMsgCompConvertible::Yes; + + node->GetAttribute(u"type"_ns, attribValue); + if (attribValue.LowerCaseEqualsLiteral("cite")) { + *_retval = nsIMsgCompConvertible::Plain; + } + } else if (element.LowerCaseEqualsLiteral("div") || + element.LowerCaseEqualsLiteral("span") || + element.LowerCaseEqualsLiteral("a")) { + // Do some special checks for these tags. They are inside this |else if| + // for performance reasons. + + // Maybe, it's an <a> element inserted by another recognizer (e.g. 4.x') + if (element.LowerCaseEqualsLiteral("a")) { + // Ignore anchor tag, if the URI is the same as the text + // (as inserted by recognizers). + *_retval = nsIMsgCompConvertible::Altering; + + nsAutoString hrefValue; + node->GetAttribute(u"href"_ns, hrefValue); + nsINodeList* children = node->ChildNodes(); + if (children->Length() > 0) { + nsINode* pItem = children->Item(0); + nsAutoString textValue; + pItem->GetNodeValue(textValue); + if (textValue == hrefValue) *_retval = nsIMsgCompConvertible::Plain; + } + } + + // Lastly, test, if it is just a "simple" <div> or <span> + else if (element.LowerCaseEqualsLiteral("div") || + element.LowerCaseEqualsLiteral("span")) { + *_retval = nsIMsgCompConvertible::Plain; + } + } +} + +/** + * Note: Helper function. Parameters are not checked. + */ +NS_IMETHODIMP +nsMsgCompose::NodeTreeConvertible(Element* node, int32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + int32_t result; + + // Check this node + TagConvertible(node, &result); + + // Walk tree recursively to check the children. + nsINodeList* children = node->ChildNodes(); + for (uint32_t i = 0; i < children->Length(); i++) { + nsINode* pItem = children->Item(i); + // We assume all nodes that are not elements are convertible, + // so only test elements. + nsCOMPtr<Element> domElement = do_QueryInterface(pItem); + if (domElement) { + int32_t curresult; + NodeTreeConvertible(domElement, &curresult); + + if (curresult > result) result = curresult; + } + } + + *_retval = result; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::BodyConvertible(int32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(m_editor); + + nsCOMPtr<Document> rootDocument; + nsresult rv = m_editor->GetDocument(getter_AddRefs(rootDocument)); + if (NS_FAILED(rv)) return rv; + if (!rootDocument) return NS_ERROR_UNEXPECTED; + + // get the top level element, which contains <html> + nsCOMPtr<Element> rootElement = rootDocument->GetDocumentElement(); + if (!rootElement) return NS_ERROR_UNEXPECTED; + NodeTreeConvertible(rootElement, _retval); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::GetIdentity(nsIMsgIdentity** aIdentity) { + NS_ENSURE_ARG_POINTER(aIdentity); + NS_IF_ADDREF(*aIdentity = m_identity); + return NS_OK; +} + +/** + * Position above the quote, that is either <blockquote> or + * <div class="moz-cite-prefix"> or <div class="moz-forward-container"> + * in an inline-forwarded message. + */ +nsresult nsMsgCompose::MoveToAboveQuote(void) { + RefPtr<Element> rootElement; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + nsCOMPtr<nsINode> node; + nsAutoString attributeName; + nsAutoString attributeValue; + nsAutoString tagLocalName; + attributeName.AssignLiteral("class"); + + RefPtr<nsINode> rootElement2 = rootElement; + node = rootElement2->GetFirstChild(); + while (node) { + nsCOMPtr<Element> element = do_QueryInterface(node); + if (element) { + // First check for <blockquote>. This will most likely not trigger + // since well-behaved quotes are preceded by a cite prefix. + tagLocalName = node->LocalName(); + if (tagLocalName.EqualsLiteral("blockquote")) { + break; + } + + // Get the class value. + element->GetAttribute(attributeName, attributeValue); + + // Now check for the cite prefix, so an element with + // class="moz-cite-prefix". + if (attributeValue.LowerCaseFindASCII("moz-cite-prefix") != kNotFound) { + break; + } + + // Next check for forwarded content. + // The forwarded part is inside an element with + // class="moz-forward-container". + if (attributeValue.LowerCaseFindASCII("moz-forward-container") != + kNotFound) { + break; + } + } + + node = node->GetNextSibling(); + if (!node) { + // No further siblings found, so we didn't find what we were looking for. + rv = NS_OK; + break; + } + } + + // Now position. If no quote was found, we position to the very front. + int32_t offset = 0; + if (node) { + rv = GetChildOffset(node, rootElement2, offset); + if (NS_FAILED(rv)) { + return rv; + } + } + RefPtr<Selection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) rv = selection->CollapseInLimiter(rootElement, offset); + + return rv; +} + +/** + * nsEditor::BeginningOfDocument() will position to the beginning of the + * document before the first editable element. It will position into a + * container. We need to be at the very front. + */ +nsresult nsMsgCompose::MoveToBeginningOfDocument(void) { + RefPtr<Element> rootElement; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + RefPtr<Selection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) rv = selection->CollapseInLimiter(rootElement, 0); + + return rv; +} + +/** + * M-C's nsEditor::EndOfDocument() will position to the end of the document + * but it will position into a container. We really need to position + * after the last container so we don't accidentally position into a + * <blockquote>. That's why we use our own function. + */ +nsresult nsMsgCompose::MoveToEndOfDocument(void) { + int32_t offset; + RefPtr<Element> rootElement; + nsCOMPtr<nsINode> lastNode; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + RefPtr<nsINode> rootElement2 = rootElement; + lastNode = rootElement2->GetLastChild(); + if (!lastNode) { + return NS_ERROR_NULL_POINTER; + } + + rv = GetChildOffset(lastNode, rootElement2, offset); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr<Selection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) rv = selection->CollapseInLimiter(rootElement, offset + 1); + + return rv; +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +nsMsgCompose::SetIdentity(nsIMsgIdentity* aIdentity) { + NS_ENSURE_ARG_POINTER(aIdentity); + + m_identity = aIdentity; + + nsresult rv; + + if (!m_editor) return NS_ERROR_FAILURE; + + RefPtr<Element> rootElement; + rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) return rv; + + // First look for the current signature, if we have one + nsCOMPtr<nsINode> lastNode; + nsCOMPtr<nsINode> node; + nsCOMPtr<nsINode> tempNode; + nsAutoString tagLocalName; + + RefPtr<nsINode> rootElement2 = rootElement; + lastNode = rootElement2->GetLastChild(); + if (lastNode) { + node = lastNode; + // In html, the signature is inside an element with + // class="moz-signature" + bool signatureFound = false; + nsAutoString attributeName; + attributeName.AssignLiteral("class"); + + while (node) { + nsCOMPtr<Element> element = do_QueryInterface(node); + if (element) { + nsAutoString attributeValue; + + element->GetAttribute(attributeName, attributeValue); + + if (attributeValue.LowerCaseFindASCII("moz-signature") != kNotFound) { + signatureFound = true; + break; + } + } + node = node->GetPreviousSibling(); + } + + if (signatureFound) { + nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference. + editor->BeginTransaction(); + tempNode = node->GetPreviousSibling(); + rv = editor->DeleteNode(node); + if (NS_FAILED(rv)) { + editor->EndTransaction(); + return rv; + } + + // Also, remove the <br> right before the signature. + if (tempNode) { + tagLocalName = tempNode->LocalName(); + if (tagLocalName.EqualsLiteral("br")) editor->DeleteNode(tempNode); + } + editor->EndTransaction(); + } + } + + if (!CheckIncludeSignaturePrefs(aIdentity)) return NS_OK; + + // Then add the new one if needed + nsAutoString aSignature; + + // No delimiter needed if not a compose window + bool isQuoted; + switch (mType) { + case nsIMsgCompType::New: + case nsIMsgCompType::NewsPost: + case nsIMsgCompType::MailToUrl: + case nsIMsgCompType::ForwardAsAttachment: + isQuoted = false; + break; + default: + isQuoted = true; + break; + } + + ProcessSignature(aIdentity, isQuoted, &aSignature); + + if (!aSignature.IsEmpty()) { + TranslateLineEnding(aSignature); + nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference. + + editor->BeginTransaction(); + int32_t reply_on_top = 0; + bool sig_bottom = true; + aIdentity->GetReplyOnTop(&reply_on_top); + aIdentity->GetSigBottom(&sig_bottom); + bool sigOnTop = (reply_on_top == 1 && !sig_bottom); + if (sigOnTop && isQuoted) { + rv = MoveToAboveQuote(); + } else { + // Note: New messages aren't quoted so we always move to the end. + rv = MoveToEndOfDocument(); + } + + if (NS_SUCCEEDED(rv)) { + if (m_composeHTML) { + bool oldAllow; + GetAllowRemoteContent(&oldAllow); + SetAllowRemoteContent(true); + rv = MOZ_KnownLive(editor->AsHTMLEditor())->InsertHTML(aSignature); + SetAllowRemoteContent(oldAllow); + } else { + rv = editor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns); + } + } + editor->EndTransaction(); + } + + return rv; +} + +NS_IMETHODIMP nsMsgCompose::CheckCharsetConversion(nsIMsgIdentity* identity, + char** fallbackCharset, + bool* _retval) { + NS_ENSURE_ARG_POINTER(identity); + NS_ENSURE_ARG_POINTER(_retval); + + // Kept around for legacy reasons. This method is supposed to check that the + // headers can be converted to the appropriate charset, but we don't support + // encoding headers to non-UTF-8, so this is now moot. + if (fallbackCharset) *fallbackCharset = nullptr; + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetDeliverMode(MSG_DeliverMode* aDeliverMode) { + NS_ENSURE_ARG_POINTER(aDeliverMode); + *aDeliverMode = mDeliverMode; + return NS_OK; +} + +void nsMsgCompose::DeleteTmpAttachments() { + if (mTmpAttachmentsDeleted || m_window) { + // Don't delete tmp attachments if compose window is still open, e.g. saving + // a draft. + return; + } + mTmpAttachmentsDeleted = true; + // Remove temporary attachment files, e.g. key.asc when attaching public key. + nsTArray<RefPtr<nsIMsgAttachment>> attachments; + m_compFields->GetAttachments(attachments); + for (nsIMsgAttachment* attachment : attachments) { + bool isTemporary; + attachment->GetTemporary(&isTemporary); + bool sentViaCloud; + attachment->GetSendViaCloud(&sentViaCloud); + if (isTemporary && !sentViaCloud) { + nsCString url; + attachment->GetUrl(url); + nsCOMPtr<nsIFile> urlFile; + nsresult rv = NS_GetFileFromURLSpec(url, getter_AddRefs(urlFile)); + if (NS_SUCCEEDED(rv)) { + urlFile->Remove(false); + } + } + } +} + +nsMsgMailList::nsMsgMailList(nsIAbDirectory* directory) + : mDirectory(directory) { + mDirectory->GetDirName(mName); + mDirectory->GetDescription(mDescription); + + if (mDescription.IsEmpty()) mDescription = mName; + + mDirectory = directory; +} diff --git a/comm/mailnews/compose/src/nsMsgCompose.h b/comm/mailnews/compose/src/nsMsgCompose.h new file mode 100644 index 0000000000..fe9b9724c8 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCompose.h @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgCompose_H_ +#define _nsMsgCompose_H_ + +#include "nsIMsgCompose.h" +#include "nsCOMArray.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" +#include "nsMsgCompFields.h" +#include "nsIOutputStream.h" +#include "nsIMsgQuote.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIBaseWindow.h" +#include "nsIAbDirectory.h" +#include "nsIWebProgressListener.h" +#include "nsIMimeConverter.h" +#include "nsIMsgFolder.h" +#include "mozIDOMWindow.h" +#include "mozilla/dom/Element.h" + +// Forward declares +class QuotingOutputStreamListener; +class nsMsgComposeSendListener; +class nsIEditor; +class nsIArray; +struct nsMsgMailList; + +class nsMsgCompose : public nsIMsgCompose, public nsSupportsWeakReference { + public: + nsMsgCompose(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_THREADSAFE_ISUPPORTS + + /*** nsIMsgCompose pure virtual functions */ + NS_DECL_NSIMSGCOMPOSE + + /* nsIMsgSendListener interface */ + NS_DECL_NSIMSGSENDLISTENER + + protected: + virtual ~nsMsgCompose(); + + // Deal with quoting issues... + nsresult QuoteOriginalMessage(); // New template + nsresult SetQuotingToFollow(bool aVal); + nsresult ConvertHTMLToText(nsIFile* aSigFile, nsString& aSigData); + nsresult ConvertTextToHTML(nsIFile* aSigFile, nsString& aSigData); + bool IsEmbeddedObjectSafe(const char* originalScheme, + const char* originalHost, const char* originalPath, + mozilla::dom::Element* element); + nsresult TagEmbeddedObjects(nsIEditor* aEditor); + + nsCString mOriginalMsgURI; // used so we can mark message disposition flags + // after we send the message + + int32_t mWhatHolder; + + nsresult LoadDataFromFile(nsIFile* file, nsString& sigData, + bool aAllowUTF8 = true, bool aAllowUTF16 = true); + + bool CheckIncludeSignaturePrefs(nsIMsgIdentity* identity); + // m_folderName to store the value of the saved drafts folder. + nsCString m_folderName; + MOZ_CAN_RUN_SCRIPT void InsertDivWrappedTextAtSelection( + const nsAString& aText, const nsAString& classStr); + + protected: + nsresult CreateMessage(const nsACString& originalMsgURI, MSG_ComposeType type, + nsIMsgCompFields* compFields); + void CleanUpRecipients(nsString& recipients); + nsresult GetABDirAndMailLists(const nsACString& aDirUri, + nsCOMArray<nsIAbDirectory>& aDirArray, + nsTArray<nsMsgMailList>& aMailListArray); + nsresult ResolveMailList(nsIAbDirectory* aMailList, + nsCOMArray<nsIAbDirectory>& allDirectoriesArray, + nsTArray<nsMsgMailList>& allMailListArray, + nsTArray<nsMsgMailList>& mailListResolved, + nsTArray<nsMsgRecipient>& aListMembers); + void TagConvertible(mozilla::dom::Element* node, int32_t* _retval); + MOZ_CAN_RUN_SCRIPT nsresult MoveToAboveQuote(void); + MOZ_CAN_RUN_SCRIPT nsresult MoveToBeginningOfDocument(void); + MOZ_CAN_RUN_SCRIPT nsresult MoveToEndOfDocument(void); + nsresult ReplaceFileURLs(nsString& sigData); + nsresult DataURLForFileURL(const nsAString& aFileURL, nsAString& aDataURL); + + /** + * Given an nsIFile, attempts to read it into aString. + * + * Note: Use sparingly! This causes main-thread I/O, which causes jank and all + * other bad things. + */ + static nsresult SlurpFileToString(nsIFile* aFile, nsACString& aString); + +// 3 = To, Cc, Bcc +#define MAX_OF_RECIPIENT_ARRAY 3 + typedef nsTArray<nsMsgRecipient> RecipientsArray[MAX_OF_RECIPIENT_ARRAY]; + /** + * This method parses the compose fields and associates email addresses with + * the relevant cards from the address books. + */ + nsresult LookupAddressBook(RecipientsArray& recipientList); + bool IsLastWindow(); + + // Helper function. Parameters are not checked. + bool mConvertStructs; // for TagConvertible + + nsCOMPtr<nsIEditor> m_editor; + mozIDOMWindowProxy* m_window; + nsCOMPtr<nsIDocShell> mDocShell; + nsCOMPtr<nsIBaseWindow> m_baseWindow; + RefPtr<nsMsgCompFields> m_compFields; + nsCOMPtr<nsIMsgIdentity> m_identity; + bool m_composeHTML; + RefPtr<QuotingOutputStreamListener> mQuoteStreamListener; + nsCOMPtr<nsIOutputStream> mBaseStream; + + nsCOMPtr<nsIMsgSend> mMsgSend; // for composition back end + nsCOMPtr<nsIMsgProgress> + mProgress; // use by the back end to report progress to the front end + + // Deal with quoting issues... + nsString mCiteReference; + nsCOMPtr<nsIMsgQuote> mQuote; + bool mQuotingToFollow; // Quoting indicator + MSG_ComposeType mType; // Message type + bool mAutodetectCharset; + bool mDeleteDraft; + nsMsgDispositionState mDraftDisposition; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + + nsString mSmtpPassword; + nsCString mHtmlToQuote; + + nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener> > mStateListeners; + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> > mExternalSendListeners; + + bool mAllowRemoteContent; + MSG_DeliverMode mDeliverMode; // nsIMsgCompDeliverMode long. + + friend class QuotingOutputStreamListener; + friend class nsMsgComposeSendListener; + + private: + void DeleteTmpAttachments(); + bool mTmpAttachmentsDeleted; +}; + +//////////////////////////////////////////////////////////////////////////////////// +// THIS IS THE CLASS THAT IS THE STREAM Listener OF THE HTML OUTPUT +// FROM LIBMIME. THIS IS FOR QUOTING +//////////////////////////////////////////////////////////////////////////////////// +class QuotingOutputStreamListener : public nsIMsgQuotingOutputStreamListener, + public nsSupportsWeakReference { + public: + QuotingOutputStreamListener(nsIMsgDBHdr* origMsgHdr, bool quoteHeaders, + bool headersOnly, nsIMsgIdentity* identity, + nsIMsgQuote* msgQuote, bool quoteOriginal, + const nsACString& htmlToQuote); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIMSGQUOTINGOUTPUTSTREAMLISTENER + + nsresult SetComposeObj(nsIMsgCompose* obj); + nsresult ConvertToPlainText(bool formatflowed, bool formatted, + bool disallowBreaks); + MOZ_CAN_RUN_SCRIPT nsresult InsertToCompose(nsIEditor* aEditor, + bool aHTMLEditor); + nsresult AppendToMsgBody(const nsCString& inStr); + + private: + virtual ~QuotingOutputStreamListener(); + nsWeakPtr mWeakComposeObj; + nsString mMsgBody; + nsString mCitePrefix; + nsString mSignature; + bool mQuoteHeaders; + bool mHeadersOnly; + nsCOMPtr<nsIMsgQuote> mQuote; + nsCOMPtr<nsIMimeHeaders> mHeaders; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + nsString mCiteReference; + nsCOMPtr<nsIMimeConverter> mMimeConverter; + int32_t mUnicodeBufferCharacterLength; + bool mQuoteOriginal; + nsCString mHtmlToQuote; +}; + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the send operation. We have to create this +// class to listen for message send completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +class nsMsgComposeSendListener : public nsIMsgComposeSendListener, + public nsIMsgSendListener, + public nsIMsgCopyServiceListener, + public nsIWebProgressListener { + public: + nsMsgComposeSendListener(void); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIMsgComposeSendListener interface + NS_DECL_NSIMSGCOMPOSESENDLISTENER + + // nsIMsgSendListener interface + NS_DECL_NSIMSGSENDLISTENER + + // nsIMsgCopyServiceListener interface + NS_DECL_NSIMSGCOPYSERVICELISTENER + + // nsIWebProgressListener interface + NS_DECL_NSIWEBPROGRESSLISTENER + + nsresult RemoveDraftOrTemplate(nsIMsgCompose* compObj, nsCString msgURI, + bool isSaveTemplate); + nsresult RemoveCurrentDraftMessage(nsIMsgCompose* compObj, bool calledByCopy, + bool isSaveTemplate); + nsresult GetMsgFolder(nsIMsgCompose* compObj, nsIMsgFolder** msgFolder); + + private: + virtual ~nsMsgComposeSendListener(); + nsWeakPtr mWeakComposeObj; + MSG_DeliverMode mDeliverMode; +}; + +/****************************************************************************** + * nsMsgMailList + ******************************************************************************/ +struct nsMsgMailList { + explicit nsMsgMailList(nsIAbDirectory* directory); + + nsString mName; + nsString mDescription; + nsCOMPtr<nsIAbDirectory> mDirectory; +}; + +#endif /* _nsMsgCompose_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp b/comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp new file mode 100644 index 0000000000..7683d04d89 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgComposeContentHandler.h" +#include "nsMsgComposeService.h" +#include "nsIChannel.h" +#include "nsIURI.h" +#include "plstr.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "mozilla/dom/Document.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsNetUtil.h" +#include "nsIMsgFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgAccountManager.h" + +#define NS_MSGCOMPOSESERVICE_CID \ + { /* 588595FE-1ADA-11d3-A715-0060B0EB39B5 */ \ + 0x588595fe, 0x1ada, 0x11d3, { \ + 0xa7, 0x15, 0x0, 0x60, 0xb0, 0xeb, 0x39, 0xb5 \ + } \ + } +static NS_DEFINE_CID(kMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID); + +nsMsgComposeContentHandler::nsMsgComposeContentHandler() {} + +// The following macro actually implement addref, release and query interface +// for our component. +NS_IMPL_ISUPPORTS(nsMsgComposeContentHandler, nsIContentHandler) + +nsMsgComposeContentHandler::~nsMsgComposeContentHandler() {} + +// Try to get an appropriate nsIMsgIdentity by going through the window, getting +// the document's URI, then the corresponding nsIMsgDBHdr. Then find the server +// associated with that header and get the first identity for it. +nsresult nsMsgComposeContentHandler::GetBestIdentity( + nsIInterfaceRequestor* aWindowContext, nsIMsgIdentity** aIdentity) { + nsresult rv; + + nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(domWindow); + + nsAutoString documentURIString; + rv = window->GetDoc()->GetDocumentURI(documentURIString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> documentURI; + rv = NS_NewURI(getter_AddRefs(documentURI), documentURIString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMessageUrl> msgURI = do_QueryInterface(documentURI); + if (!msgURI) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = msgURI->GetMessageHeader(getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> folder; + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIMsgDBHdrs from .eml messages have a null folder, so bail out if that's + // the case. + if (!folder) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = accountManager->GetFirstIdentityForServer(server, aIdentity); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +NS_IMETHODIMP nsMsgComposeContentHandler::HandleContent( + const char* aContentType, nsIInterfaceRequestor* aWindowContext, + nsIRequest* request) { + nsresult rv = NS_OK; + if (!request) return NS_ERROR_NULL_POINTER; + + // First of all, get the content type and make sure it is a content type we + // know how to handle! + if (PL_strcasecmp(aContentType, "application/x-mailto") == 0) { + nsCOMPtr<nsIMsgIdentity> identity; + + if (aWindowContext) + GetBestIdentity(aWindowContext, getter_AddRefs(identity)); + + nsCOMPtr<nsIURI> aUri; + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if (!aChannel) return NS_ERROR_FAILURE; + + rv = aChannel->GetURI(getter_AddRefs(aUri)); + if (aUri) { + nsCOMPtr<nsIMsgComposeService> composeService = + do_GetService(kMsgComposeServiceCID, &rv); + if (NS_SUCCEEDED(rv)) + rv = composeService->OpenComposeWindowWithURI(nullptr, aUri, identity); + } + } else { + // The content-type was not application/x-mailto... + return NS_ERROR_WONT_HANDLE_CONTENT; + } + + return rv; +} diff --git a/comm/mailnews/compose/src/nsMsgComposeContentHandler.h b/comm/mailnews/compose/src/nsMsgComposeContentHandler.h new file mode 100644 index 0000000000..158edfa09b --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeContentHandler.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContentHandler.h" +#include "nsIMsgIdentity.h" + +class nsMsgComposeContentHandler : public nsIContentHandler { + public: + nsMsgComposeContentHandler(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTHANDLER + private: + virtual ~nsMsgComposeContentHandler(); + nsresult GetBestIdentity(nsIInterfaceRequestor* aWindowContext, + nsIMsgIdentity** identity); +}; diff --git a/comm/mailnews/compose/src/nsMsgComposeParams.cpp b/comm/mailnews/compose/src/nsMsgComposeParams.cpp new file mode 100644 index 0000000000..653ed11f46 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeParams.cpp @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgComposeParams.h" + +nsMsgComposeParams::nsMsgComposeParams() + : mType(nsIMsgCompType::New), + mFormat(nsIMsgCompFormat::Default), + mBodyIsLink(false), + mAutodetectCharset(false) {} + +/* the following macro actually implement addref, release and query interface + * for our component. */ +NS_IMPL_ISUPPORTS(nsMsgComposeParams, nsIMsgComposeParams) + +nsMsgComposeParams::~nsMsgComposeParams() {} + +/* attribute MSG_ComposeType type; */ +NS_IMETHODIMP nsMsgComposeParams::GetType(MSG_ComposeType* aType) { + NS_ENSURE_ARG_POINTER(aType); + + *aType = mType; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetType(MSG_ComposeType aType) { + mType = aType; + return NS_OK; +} + +/* attribute MSG_ComposeFormat format; */ +NS_IMETHODIMP nsMsgComposeParams::GetFormat(MSG_ComposeFormat* aFormat) { + NS_ENSURE_ARG_POINTER(aFormat); + + *aFormat = mFormat; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetFormat(MSG_ComposeFormat aFormat) { + mFormat = aFormat; + return NS_OK; +} + +/* attribute string originalMsgURI; */ +NS_IMETHODIMP nsMsgComposeParams::GetOriginalMsgURI( + nsACString& aOriginalMsgURI) { + aOriginalMsgURI = mOriginalMsgUri; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetOriginalMsgURI( + const nsACString& aOriginalMsgURI) { + mOriginalMsgUri = aOriginalMsgURI; + return NS_OK; +} + +/* attribute nsIMsgIdentity identity; */ +NS_IMETHODIMP nsMsgComposeParams::GetIdentity(nsIMsgIdentity** aIdentity) { + NS_ENSURE_ARG_POINTER(aIdentity); + NS_IF_ADDREF(*aIdentity = mIdentity); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::SetIdentity(nsIMsgIdentity* aIdentity) { + mIdentity = aIdentity; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::SetOrigMsgHdr(nsIMsgDBHdr* aMsgHdr) { + mOrigMsgHdr = aMsgHdr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::GetOrigMsgHdr(nsIMsgDBHdr** aMsgHdr) { + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::GetAutodetectCharset( + bool* aAutodetectCharset) { + NS_ENSURE_ARG_POINTER(aAutodetectCharset); + *aAutodetectCharset = mAutodetectCharset; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetAutodetectCharset( + bool aAutodetectCharset) { + mAutodetectCharset = aAutodetectCharset; + return NS_OK; +} + +/* attribute ACString htmlToQuote; */ +NS_IMETHODIMP nsMsgComposeParams::GetHtmlToQuote(nsACString& aHtmlToQuote) { + aHtmlToQuote = mHtmlToQuote; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetHtmlToQuote( + const nsACString& aHtmlToQuote) { + mHtmlToQuote = aHtmlToQuote; + return NS_OK; +} + +/* attribute nsIMsgCompFields composeFields; */ +NS_IMETHODIMP nsMsgComposeParams::GetComposeFields( + nsIMsgCompFields** aComposeFields) { + NS_ENSURE_ARG_POINTER(aComposeFields); + + if (mComposeFields) { + NS_ADDREF(*aComposeFields = mComposeFields); + } else + *aComposeFields = nullptr; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetComposeFields( + nsIMsgCompFields* aComposeFields) { + mComposeFields = aComposeFields; + return NS_OK; +} + +/* attribute boolean bodyIsLink; */ +NS_IMETHODIMP nsMsgComposeParams::GetBodyIsLink(bool* aBodyIsLink) { + NS_ENSURE_ARG_POINTER(aBodyIsLink); + + *aBodyIsLink = mBodyIsLink; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetBodyIsLink(bool aBodyIsLink) { + mBodyIsLink = aBodyIsLink; + return NS_OK; +} + +/* attribute nsIMsgSendLisneter sendListener; */ +NS_IMETHODIMP nsMsgComposeParams::GetSendListener( + nsIMsgSendListener** aSendListener) { + NS_ENSURE_ARG_POINTER(aSendListener); + + if (mSendListener) { + NS_ADDREF(*aSendListener = mSendListener); + } else + *aSendListener = nullptr; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetSendListener( + nsIMsgSendListener* aSendListener) { + mSendListener = aSendListener; + return NS_OK; +} + +/* attribute string smtpPassword; */ +NS_IMETHODIMP nsMsgComposeParams::GetSmtpPassword(nsAString& aSmtpPassword) { + aSmtpPassword = mSMTPPassword; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetSmtpPassword( + const nsAString& aSmtpPassword) { + mSMTPPassword = aSmtpPassword; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgComposeParams.h b/comm/mailnews/compose/src/nsMsgComposeParams.h new file mode 100644 index 0000000000..b447304e30 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeParams.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMsgComposeParams.h" +#include "nsString.h" +#include "nsIMsgHdr.h" +#include "nsCOMPtr.h" +class nsMsgComposeParams : public nsIMsgComposeParams { + public: + nsMsgComposeParams(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSEPARAMS + + private: + virtual ~nsMsgComposeParams(); + MSG_ComposeType mType; + MSG_ComposeFormat mFormat; + nsCString mOriginalMsgUri; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCOMPtr<nsIMsgCompFields> mComposeFields; + bool mBodyIsLink; + nsCOMPtr<nsIMsgSendListener> mSendListener; + nsString mSMTPPassword; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + bool mAutodetectCharset; + nsCString mHtmlToQuote; +}; diff --git a/comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp b/comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp new file mode 100644 index 0000000000..d54355ab8f --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgComposeProgressParams.h" +#include "nsServiceManagerUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgComposeProgressParams, nsIMsgComposeProgressParams) + +nsMsgComposeProgressParams::nsMsgComposeProgressParams() + : m_deliveryMode(nsIMsgCompDeliverMode::Now) {} + +nsMsgComposeProgressParams::~nsMsgComposeProgressParams() {} + +/* attribute wstring subject; */ +NS_IMETHODIMP nsMsgComposeProgressParams::GetSubject(char16_t** aSubject) { + NS_ENSURE_ARG(aSubject); + + *aSubject = ToNewUnicode(m_subject); + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeProgressParams::SetSubject(const char16_t* aSubject) { + m_subject = aSubject; + return NS_OK; +} + +/* attribute MSG_DeliverMode deliveryMode; */ +NS_IMETHODIMP nsMsgComposeProgressParams::GetDeliveryMode( + MSG_DeliverMode* aDeliveryMode) { + NS_ENSURE_ARG(aDeliveryMode); + + *aDeliveryMode = m_deliveryMode; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeProgressParams::SetDeliveryMode( + MSG_DeliverMode aDeliveryMode) { + m_deliveryMode = aDeliveryMode; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgComposeProgressParams.h b/comm/mailnews/compose/src/nsMsgComposeProgressParams.h new file mode 100644 index 0000000000..141d870195 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeProgressParams.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMsgComposeProgressParams.h" + +class nsMsgComposeProgressParams : public nsIMsgComposeProgressParams { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSEPROGRESSPARAMS + + nsMsgComposeProgressParams(); + + private: + virtual ~nsMsgComposeProgressParams(); + nsString m_subject; + MSG_DeliverMode m_deliveryMode; +}; diff --git a/comm/mailnews/compose/src/nsMsgComposeService.cpp b/comm/mailnews/compose/src/nsMsgComposeService.cpp new file mode 100644 index 0000000000..be12fbbef4 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeService.cpp @@ -0,0 +1,1411 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgComposeService.h" +#include "nsIMsgSend.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsIMsgIdentity.h" +#include "nsISmtpUrl.h" +#include "nsIURI.h" +#include "nsMsgI18N.h" +#include "nsIMsgComposeParams.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIWindowWatcher.h" +#include "mozIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIMsgWindow.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Document.h" +#include "nsIAppWindow.h" +#include "nsIWindowMediator.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIBaseWindow.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMsgAccountManager.h" +#include "nsIStreamConverter.h" +#include "nsToolkitCompsCID.h" +#include "nsNetUtil.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgDatabase.h" +#include "nsIDocumentEncoder.h" +#include "nsContentCID.h" +#include "mozilla/dom/Selection.h" +#include "nsUTF8Utils.h" +#include "mozilla/intl/LineBreaker.h" +#include "mimemoz2.h" +#include "nsIURIMutator.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/XULFrameElement.h" +#include "nsFrameLoader.h" +#include "nsSmtpUrl.h" +#include "nsUnicharUtils.h" +#include "mozilla/NullPrincipal.h" + +#ifdef MSGCOMP_TRACE_PERFORMANCE +# include "mozilla/Logging.h" +# include "nsIMsgHdr.h" +# include "nsIMsgMessageService.h" +# include "nsMsgUtils.h" +#endif + +#include "nsICommandLine.h" +#include "nsIAppStartup.h" +#include "nsMsgUtils.h" +#include "nsIPrincipal.h" +#include "nsIMutableArray.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#ifdef XP_WIN +# include <windows.h> +# include <shellapi.h> +# include "nsIWidget.h" +#endif + +#define DEFAULT_CHROME \ + "chrome://messenger/content/messengercompose/messengercompose.xhtml"_ns + +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION "mailnews.reply_quoting_selection" +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD \ + "mailnews.reply_quoting_selection.multi_word" +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF \ + "mailnews.reply_quoting_selection.only_if_chars" + +#define MAIL_ROOT_PREF "mail." +#define MAILNEWS_ROOT_PREF "mailnews." +#define HTMLDOMAINUPDATE_VERSION_PREF_NAME "global_html_domains.version" +#define HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME "global_html_domains" +#define USER_CURRENT_HTMLDOMAINLIST_PREF_NAME "html_domains" +#define USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME "plaintext_domains" +#define DOMAIN_DELIMITER ',' + +#ifdef MSGCOMP_TRACE_PERFORMANCE +static mozilla::LazyLogModule MsgComposeLogModule("MsgCompose"); + +static uint32_t GetMessageSizeFromURI(const nsACString& originalMsgURI) { + uint32_t msgSize = 0; + + if (!originalMsgURI.IsEmpty()) { + nsCOMPtr<nsIMsgDBHdr> originalMsgHdr; + GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(originalMsgHdr)); + if (originalMsgHdr) originalMsgHdr->GetMessageSize(&msgSize); + } + + return msgSize; +} +#endif + +nsMsgComposeService::nsMsgComposeService() { + // Defaulting the value of mLogComposePerformance to FALSE to prevent logging. + mLogComposePerformance = false; +#ifdef MSGCOMP_TRACE_PERFORMANCE + mStartTime = PR_IntervalNow(); + mPreviousTime = mStartTime; +#endif +} + +NS_IMPL_ISUPPORTS(nsMsgComposeService, nsIMsgComposeService, + ICOMMANDLINEHANDLER, nsISupportsWeakReference) + +nsMsgComposeService::~nsMsgComposeService() { mOpenComposeWindows.Clear(); } + +nsresult nsMsgComposeService::Init() { + nsresult rv = NS_OK; + + Reset(); + + AddGlobalHtmlDomains(); + // Since the compose service should only be initialized once, we can + // be pretty sure there aren't any existing compose windows open. + MsgCleanupTempFiles("nsmail", "tmp"); + MsgCleanupTempFiles("nscopy", "tmp"); + MsgCleanupTempFiles("nsemail", "eml"); + MsgCleanupTempFiles("nsemail", "tmp"); + MsgCleanupTempFiles("nsqmail", "tmp"); + return rv; +} + +void nsMsgComposeService::Reset() { + mOpenComposeWindows.Clear(); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetBoolPref("mailnews.logComposePerformance", + &mLogComposePerformance); +} + +// Function to open a message compose window and pass an nsIMsgComposeParams +// parameter to it. +NS_IMETHODIMP +nsMsgComposeService::OpenComposeWindowWithParams(const char* chrome, + nsIMsgComposeParams* params) { + NS_ENSURE_ARG_POINTER(params); +#ifdef MSGCOMP_TRACE_PERFORMANCE + if (mLogComposePerformance) { + TimeStamp("Start opening the window", true); + } +#endif + + nsresult rv; + + NS_ENSURE_ARG_POINTER(params); + + // Use default identity if no identity has been specified + nsCOMPtr<nsIMsgIdentity> identity; + params->GetIdentity(getter_AddRefs(identity)); + if (!identity) { + GetDefaultIdentity(getter_AddRefs(identity)); + params->SetIdentity(identity); + } + + // Create a new window. + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (!wwatch) return NS_ERROR_FAILURE; + + nsCOMPtr<nsISupportsInterfacePointer> msgParamsWrapper = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + msgParamsWrapper->SetData(params); + msgParamsWrapper->SetDataIID(&NS_GET_IID(nsIMsgComposeParams)); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + nsAutoCString chromeURL; + if (chrome && *chrome) { + chromeURL = nsDependentCString(chrome); + } else { + chromeURL = DEFAULT_CHROME; + } + rv = wwatch->OpenWindow(0, chromeURL, "_blank"_ns, + "all,chrome,dialog=no,status,toolbar"_ns, + msgParamsWrapper, getter_AddRefs(newWindow)); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::DetermineComposeHTML(nsIMsgIdentity* aIdentity, + MSG_ComposeFormat aFormat, + bool* aComposeHTML) { + NS_ENSURE_ARG_POINTER(aComposeHTML); + + *aComposeHTML = true; + switch (aFormat) { + case nsIMsgCompFormat::HTML: + *aComposeHTML = true; + break; + case nsIMsgCompFormat::PlainText: + *aComposeHTML = false; + break; + + default: + nsCOMPtr<nsIMsgIdentity> identity = aIdentity; + if (!identity) GetDefaultIdentity(getter_AddRefs(identity)); + + if (identity) { + identity->GetComposeHtml(aComposeHTML); + if (aFormat == nsIMsgCompFormat::OppositeOfDefault) + *aComposeHTML = !*aComposeHTML; + } else { + // default identity not found. Use the mail.html_compose pref to + // determine message compose type (HTML or PlainText). + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + nsresult rv; + bool useHTMLCompose; + rv = prefs->GetBoolPref(MAIL_ROOT_PREF "html_compose", + &useHTMLCompose); + if (NS_SUCCEEDED(rv)) *aComposeHTML = useHTMLCompose; + } + } + break; + } + + return NS_OK; +} + +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION nsresult +nsMsgComposeService::GetOrigWindowSelection(MSG_ComposeType type, + mozilla::dom::Selection* selection, + nsACString& aSelHTML) { + nsresult rv; + + // Good hygiene + aSelHTML.Truncate(); + + // Get the pref to see if we even should do reply quoting selection + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool replyQuotingSelection; + rv = prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION, + &replyQuotingSelection); + NS_ENSURE_SUCCESS(rv, rv); + if (!replyQuotingSelection) return NS_ERROR_ABORT; + + bool requireMultipleWords = true; + nsAutoCString charsOnlyIf; + prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD, + &requireMultipleWords); + prefs->GetCharPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF, + charsOnlyIf); + if (requireMultipleWords || !charsOnlyIf.IsEmpty()) { + nsAutoString selPlain; + selection->Stringify(selPlain); + + // If "mailnews.reply_quoting_selection.multi_word" is on, then there must + // be at least two words selected in order to quote just the selected text + if (requireMultipleWords) { + if (selPlain.IsEmpty()) return NS_ERROR_ABORT; + + if (NS_SUCCEEDED(rv)) { + const uint32_t length = selPlain.Length(); + const char16_t* unicodeStr = selPlain.get(); + int32_t endWordPos = + mozilla::intl::LineBreaker::Next(unicodeStr, length, 0); + + // If there's not even one word, then there's not multiple words + if (endWordPos == NS_LINEBREAKER_NEED_MORE_TEXT) return NS_ERROR_ABORT; + + // If after the first word is only space, then there's not multiple + // words + const char16_t* end; + for (end = unicodeStr + endWordPos; mozilla::intl::NS_IsSpace(*end); + end++) + ; + if (!*end) return NS_ERROR_ABORT; + } + } + + if (!charsOnlyIf.IsEmpty()) { + if (selPlain.FindCharInSet(NS_ConvertUTF8toUTF16(charsOnlyIf)) == + kNotFound) { + return NS_ERROR_ABORT; + } + } + } + + nsAutoString selHTML; + IgnoredErrorResult rv2; + selection->ToStringWithFormat(u"text/html"_ns, + nsIDocumentEncoder::SkipInvisibleContent, 0, + selHTML, rv2); + if (rv2.Failed()) { + return NS_ERROR_FAILURE; + } + + // Now remove <span class="moz-txt-citetags">> </span>. + nsAutoCString html(NS_ConvertUTF16toUTF8(selHTML).get()); + int32_t spanInd = html.Find("<span class=\"moz-txt-citetags\">"); + while (spanInd != kNotFound) { + nsAutoCString right0(Substring(html, spanInd)); + int32_t endInd = right0.Find("</span>"); + if (endInd == kNotFound) break; // oops, where is the closing tag gone? + nsAutoCString right1(Substring(html, spanInd + endInd + 7)); + html.SetLength(spanInd); + html.Append(right1); + spanInd = html.Find("<span class=\"moz-txt-citetags\">"); + } + + aSelHTML.Assign(html); + + return rv; +} + +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION NS_IMETHODIMP +nsMsgComposeService::OpenComposeWindow( + const nsACString& msgComposeWindowURL, nsIMsgDBHdr* origMsgHdr, + const nsACString& originalMsgURI, MSG_ComposeType type, + MSG_ComposeFormat format, nsIMsgIdentity* aIdentity, const nsACString& from, + nsIMsgWindow* aMsgWindow, mozilla::dom::Selection* selection, + bool autodetectCharset) { + nsresult rv; + + nsCOMPtr<nsIMsgIdentity> identity = aIdentity; + if (!identity) GetDefaultIdentity(getter_AddRefs(identity)); + + /* Actually, the only way to implement forward inline is to simulate a + template message. Maybe one day when we will have more time we can change + that + */ + if (type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft || + type == nsIMsgCompType::EditTemplate || + type == nsIMsgCompType::Template || + type == nsIMsgCompType::ReplyWithTemplate || + type == nsIMsgCompType::Redirect || type == nsIMsgCompType::EditAsNew) { + nsAutoCString uriToOpen(originalMsgURI); + char sep = (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&'; + + // The compose type that gets transmitted to a compose window open in mime + // is communicated using url query parameters here. + if (type == nsIMsgCompType::Redirect) { + uriToOpen += sep; + uriToOpen.AppendLiteral("redirect=true"); + } else if (type == nsIMsgCompType::EditAsNew) { + uriToOpen += sep; + uriToOpen.AppendLiteral("editasnew=true"); + } else if (type == nsIMsgCompType::EditTemplate) { + uriToOpen += sep; + uriToOpen.AppendLiteral("edittempl=true"); + } + + return LoadDraftOrTemplate( + uriToOpen, + type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft + ? nsMimeOutput::nsMimeMessageDraftOrTemplate + : nsMimeOutput::nsMimeMessageEditorTemplate, + identity, originalMsgURI, origMsgHdr, + type == nsIMsgCompType::ForwardInline, + format == nsIMsgCompFormat::OppositeOfDefault, aMsgWindow, + autodetectCharset); + } + + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv)); + if (NS_SUCCEEDED(rv) && pMsgComposeParams) { + nsCOMPtr<nsIMsgCompFields> pMsgCompFields(do_CreateInstance( + "@mozilla.org/messengercompose/composefields;1", &rv)); + if (NS_SUCCEEDED(rv) && pMsgCompFields) { + pMsgComposeParams->SetType(type); + pMsgComposeParams->SetFormat(format); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetAutodetectCharset(autodetectCharset); + + // When doing a reply (except with a template) see if there's a selection + // that we should quote + if (selection && + (type == nsIMsgCompType::Reply || type == nsIMsgCompType::ReplyAll || + type == nsIMsgCompType::ReplyToSender || + type == nsIMsgCompType::ReplyToGroup || + type == nsIMsgCompType::ReplyToSenderAndGroup || + type == nsIMsgCompType::ReplyToList)) { + nsAutoCString selHTML; + if (NS_SUCCEEDED(GetOrigWindowSelection(type, selection, selHTML))) { + nsCOMPtr<nsINode> node = selection->GetFocusNode(); + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + IgnoredErrorResult er; + + if ((node->LocalName().IsEmpty() || + node->LocalName().EqualsLiteral("pre")) && + node->OwnerDoc()->QuerySelector( + "body > div:first-of-type.moz-text-plain"_ns, er)) { + // Treat the quote as <pre> for selections in moz-text-plain bodies. + // If focusNode.localName isn't empty, we had e.g. body selected + // and should not add <pre>. + pMsgComposeParams->SetHtmlToQuote("<pre>"_ns + selHTML + + "</pre>"_ns); + } else { + pMsgComposeParams->SetHtmlToQuote(selHTML); + } + } + } + + if (!originalMsgURI.IsEmpty()) { + if (type == nsIMsgCompType::NewsPost) { + nsAutoCString newsURI(originalMsgURI); + nsAutoCString group; + nsAutoCString host; + + int32_t slashpos = newsURI.RFindChar('/'); + if (slashpos > 0) { + // uri is "[s]news://host[:port]/group" + host = StringHead(newsURI, slashpos); + group = Substring(newsURI, slashpos + 1); + + } else + group = originalMsgURI; + + nsAutoCString unescapedName; + MsgUnescapeString(group, + nsINetUtil::ESCAPE_URL_FILE_BASENAME | + nsINetUtil::ESCAPE_URL_FORCED, + unescapedName); + pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(unescapedName)); + pMsgCompFields->SetNewspostUrl(host.get()); + } else { + pMsgComposeParams->SetOriginalMsgURI(originalMsgURI); + pMsgComposeParams->SetOrigMsgHdr(origMsgHdr); + pMsgCompFields->SetFrom(NS_ConvertUTF8toUTF16(from)); + } + } + + pMsgComposeParams->SetComposeFields(pMsgCompFields); + + if (mLogComposePerformance) { +#ifdef MSGCOMP_TRACE_PERFORMANCE + // ducarroz, properly fix this in the case of new message (not a reply) + if (type != nsIMsgCompType::NewsPost) { + char buff[256]; + sprintf(buff, "Start opening the window, message size = %d", + GetMessageSizeFromURI(originalMsgURI)); + TimeStamp(buff, true); + } +#endif + } // end if(mLogComposePerformance) + + rv = OpenComposeWindowWithParams( + PromiseFlatCString(msgComposeWindowURL).get(), pMsgComposeParams); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgComposeService::GetParamsForMailto( + nsIURI* aURI, nsIMsgComposeParams** aParams) { + nsresult rv = NS_OK; + if (aURI) { + nsCString spec; + aURI->GetSpec(spec); + + nsCOMPtr<nsIURI> url; + rv = nsMailtoUrl::NewMailtoURI(spec, nullptr, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMailtoUrl> aMailtoUrl = do_QueryInterface(url, &rv); + + if (NS_SUCCEEDED(rv)) { + MSG_ComposeFormat requestedComposeFormat = nsIMsgCompFormat::Default; + nsCString toPart; + nsCString ccPart; + nsCString bccPart; + nsCString subjectPart; + nsCString bodyPart; + nsCString newsgroup; + nsCString refPart; + nsCString HTMLBodyPart; + + aMailtoUrl->GetMessageContents(toPart, ccPart, bccPart, subjectPart, + bodyPart, HTMLBodyPart, refPart, newsgroup, + &requestedComposeFormat); + + nsAutoString sanitizedBody; + + bool composeHTMLFormat; + DetermineComposeHTML(NULL, requestedComposeFormat, &composeHTMLFormat); + + // If there was an 'html-body' param, finding it will have requested + // HTML format in GetMessageContents, so we try to use it first. If it's + // empty, but we are composing in HTML because of the user's prefs, the + // 'body' param needs to be escaped, since it's supposed to be plain + // text, but it then doesn't need to sanitized. + nsString rawBody; + if (HTMLBodyPart.IsEmpty()) { + if (composeHTMLFormat) { + nsCString escaped; + nsAppendEscapedHTML(bodyPart, escaped); + CopyUTF8toUTF16(escaped, sanitizedBody); + } else + CopyUTF8toUTF16(bodyPart, rawBody); + } else + CopyUTF8toUTF16(HTMLBodyPart, rawBody); + + if (!rawBody.IsEmpty() && composeHTMLFormat) { + // For security reason, we must sanitize the message body before + // accepting any html... + + rv = HTMLSanitize(rawBody, sanitizedBody); // from mimemoz2.h + + if (NS_FAILED(rv)) { + // Something went horribly wrong with parsing for html format + // in the body. Set composeHTMLFormat to false so we show the + // plain text mail compose. + composeHTMLFormat = false; + } + } + + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams(do_CreateInstance( + "@mozilla.org/messengercompose/composeparams;1", &rv)); + if (NS_SUCCEEDED(rv) && pMsgComposeParams) { + pMsgComposeParams->SetType(nsIMsgCompType::MailToUrl); + pMsgComposeParams->SetFormat(composeHTMLFormat + ? nsIMsgCompFormat::HTML + : nsIMsgCompFormat::PlainText); + + nsCOMPtr<nsIMsgCompFields> pMsgCompFields(do_CreateInstance( + "@mozilla.org/messengercompose/composefields;1", &rv)); + if (pMsgCompFields) { + // ugghh more conversion work!!!! + pMsgCompFields->SetTo(NS_ConvertUTF8toUTF16(toPart)); + pMsgCompFields->SetCc(NS_ConvertUTF8toUTF16(ccPart)); + pMsgCompFields->SetBcc(NS_ConvertUTF8toUTF16(bccPart)); + pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(newsgroup)); + pMsgCompFields->SetReferences(refPart.get()); + pMsgCompFields->SetSubject(NS_ConvertUTF8toUTF16(subjectPart)); + pMsgCompFields->SetBody(composeHTMLFormat ? sanitizedBody : rawBody); + pMsgComposeParams->SetComposeFields(pMsgCompFields); + + NS_ADDREF(*aParams = pMsgComposeParams); + return NS_OK; + } + } // if we created msg compose params.... + } // if we had a mailto url + } // if we had a url... + + // if we got here we must have encountered an error + *aParams = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgComposeService::OpenComposeWindowWithURI( + const char* aMsgComposeWindowURL, nsIURI* aURI, nsIMsgIdentity* identity) { + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = GetParamsForMailto(aURI, getter_AddRefs(pMsgComposeParams)); + if (NS_SUCCEEDED(rv)) { + pMsgComposeParams->SetIdentity(identity); + rv = OpenComposeWindowWithParams(aMsgComposeWindowURL, pMsgComposeParams); + } + return rv; +} + +NS_IMETHODIMP nsMsgComposeService::InitCompose(nsIMsgComposeParams* aParams, + mozIDOMWindowProxy* aWindow, + nsIDocShell* aDocShell, + nsIMsgCompose** _retval) { + nsresult rv; + nsCOMPtr<nsIMsgCompose> msgCompose = + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = msgCompose->Initialize(aParams, aWindow, aDocShell); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*_retval = msgCompose); + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::GetDefaultIdentity(nsIMsgIdentity** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, rv); + + return defaultAccount ? defaultAccount->GetDefaultIdentity(_retval) : NS_OK; +} + +/* readonly attribute boolean logComposePerformance; */ +NS_IMETHODIMP nsMsgComposeService::GetLogComposePerformance( + bool* aLogComposePerformance) { + *aLogComposePerformance = mLogComposePerformance; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeService::TimeStamp(const char* label, + bool resetTime) { + if (!mLogComposePerformance) return NS_OK; + +#ifdef MSGCOMP_TRACE_PERFORMANCE + + PRIntervalTime now; + + if (resetTime) { + MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, + ("\n[process]: [totalTime][deltaTime]\n--------------------\n")); + + mStartTime = PR_IntervalNow(); + mPreviousTime = mStartTime; + now = mStartTime; + } else + now = PR_IntervalNow(); + + PRIntervalTime totalTime = PR_IntervalToMilliseconds(now - mStartTime); + PRIntervalTime deltaTime = PR_IntervalToMilliseconds(now - mPreviousTime); + + MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, + ("[%3.2f][%3.2f] - %s\n", ((double)totalTime / 1000.0) + 0.005, + ((double)deltaTime / 1000.0) + 0.005, label)); + + mPreviousTime = now; +#endif + return NS_OK; +} + +class nsMsgTemplateReplyHelper final : public nsIStreamListener, + public nsIUrlListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + nsMsgTemplateReplyHelper(); + + nsCOMPtr<nsIMsgDBHdr> mHdrToReplyTo; + nsCOMPtr<nsIMsgDBHdr> mTemplateHdr; + nsCOMPtr<nsIMsgWindow> mMsgWindow; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCString mTemplateBody; + bool mInMsgBody; + char mLastBlockChars[3]; + + private: + ~nsMsgTemplateReplyHelper(); +}; + +NS_IMPL_ISUPPORTS(nsMsgTemplateReplyHelper, nsIStreamListener, + nsIRequestObserver, nsIUrlListener) + +nsMsgTemplateReplyHelper::nsMsgTemplateReplyHelper() { + mInMsgBody = false; + memset(mLastBlockChars, 0, sizeof(mLastBlockChars)); +} + +nsMsgTemplateReplyHelper::~nsMsgTemplateReplyHelper() {} + +NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStartRunningUrl(nsIURI* aUrl) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStopRunningUrl(nsIURI* aUrl, + nsresult aExitCode) { + NS_ENSURE_SUCCESS(aExitCode, aExitCode); + nsresult rv; + nsCOMPtr<nsPIDOMWindowOuter> parentWindow; + if (mMsgWindow) { + nsCOMPtr<nsIDocShell> docShell; + rv = mMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + parentWindow = do_GetInterface(docShell); + NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE); + } + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv)); + if (NS_FAILED(rv) || (!pMsgComposeParams)) return rv; + nsCOMPtr<nsIMsgCompFields> compFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + + nsCString replyTo; + mHdrToReplyTo->GetStringProperty("replyTo", replyTo); + if (replyTo.IsEmpty()) mHdrToReplyTo->GetAuthor(getter_Copies(replyTo)); + compFields->SetTo(NS_ConvertUTF8toUTF16(replyTo)); + + nsString body; + nsString templateSubject, replySubject; + + mHdrToReplyTo->GetMime2DecodedSubject(replySubject); + mTemplateHdr->GetMime2DecodedSubject(templateSubject); + nsString subject(u"Auto: "_ns); // RFC 3834 3.1.5. + subject.Append(templateSubject); + if (!replySubject.IsEmpty()) { + subject.AppendLiteral(u" (was: "); + subject.Append(replySubject); + subject.Append(u')'); + } + + compFields->SetSubject(subject); + compFields->SetRawHeader("Auto-Submitted", "auto-replied"_ns); + + nsCString charset; + rv = mTemplateHdr->GetCharset(getter_Copies(charset)); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsMsgI18NConvertToUnicode(charset, mTemplateBody, body); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "couldn't convert templ body to unicode"); + compFields->SetBody(body); + + nsCString msgUri; + nsCOMPtr<nsIMsgFolder> folder; + mHdrToReplyTo->GetFolder(getter_AddRefs(folder)); + folder->GetUriForMsg(mHdrToReplyTo, msgUri); + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::ReplyWithTemplate); + pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default); + pMsgComposeParams->SetIdentity(mIdentity); + pMsgComposeParams->SetComposeFields(compFields); + pMsgComposeParams->SetOriginalMsgURI(msgUri); + + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose( + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion + * response **/ + + rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<mozilla::dom::Promise> promise; + return pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, mIdentity, nullptr, + nullptr, nullptr, getter_AddRefs(promise)); +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnStartRequest(nsIRequest* request) { return NS_OK; } + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnStopRequest(nsIRequest* request, nsresult status) { + if (NS_SUCCEEDED(status)) { + // now we've got the message body in mTemplateBody - + // need to set body in compose params and send the reply. + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnDataAvailable(nsIRequest* request, + nsIInputStream* inStream, + uint64_t srcOffset, uint32_t count) { + nsresult rv = NS_OK; + + char readBuf[1024]; + + uint64_t available; + uint32_t readCount; + uint32_t maxReadCount = sizeof(readBuf) - 1; + + rv = inStream->Available(&available); + while (NS_SUCCEEDED(rv) && available > 0) { + uint32_t bodyOffset = 0, readOffset = 0; + if (!mInMsgBody && mLastBlockChars[0]) { + memcpy(readBuf, mLastBlockChars, 3); + readOffset = 3; + maxReadCount -= 3; + } + if (maxReadCount > available) maxReadCount = (uint32_t)available; + memset(readBuf, 0, sizeof(readBuf)); + rv = inStream->Read(readBuf + readOffset, maxReadCount, &readCount); + available -= readCount; + readCount += readOffset; + // we're mainly interested in the msg body, so we need to + // find the header/body delimiter of a blank line. A blank line + // looks like <CR><CR>, <LF><LF>, or <CRLF><CRLF> + if (!mInMsgBody) { + for (uint32_t charIndex = 0; charIndex < readCount && !bodyOffset; + charIndex++) { + if (readBuf[charIndex] == '\r' || readBuf[charIndex] == '\n') { + if (charIndex + 1 < readCount) { + if (readBuf[charIndex] == readBuf[charIndex + 1]) { + // got header+body separator + bodyOffset = charIndex + 2; + break; + } else if ((charIndex + 3 < readCount) && + !strncmp(readBuf + charIndex, "\r\n\r\n", 4)) { + bodyOffset = charIndex + 4; + break; + } + } + } + } + mInMsgBody = bodyOffset != 0; + if (!mInMsgBody && readCount > 3) // still in msg hdrs + strncpy(mLastBlockChars, readBuf + readCount - 3, 3); + } + mTemplateBody.Append(readBuf + bodyOffset); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeService::ReplyWithTemplate( + nsIMsgDBHdr* aMsgHdr, const nsACString& templateUri, + nsIMsgWindow* aMsgWindow, nsIMsgIncomingServer* aServer) { + // To reply with template, we need the message body of the template. + // I think we're going to need to stream the template message to ourselves, + // and construct the body, and call setBody on the compFields. + nsresult rv; + const nsPromiseFlatCString& templateUriFlat = PromiseFlatCString(templateUri); + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgIdentity>> identities; + rv = account->GetIdentities(identities); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString recipients; + aMsgHdr->GetRecipients(getter_Copies(recipients)); + + nsAutoCString ccList; + aMsgHdr->GetCcList(getter_Copies(ccList)); + + // Go through the identities to see to whom this was addressed. + // In case we get no match, this is likely a list/bulk/bcc/spam mail and we + // shouldn't reply. RFC 3834 2. + nsCOMPtr<nsIMsgIdentity> identity; // identity to reply from + for (auto anIdentity : identities) { + nsAutoCString identityEmail; + anIdentity->GetEmail(identityEmail); + + if (FindInReadable(identityEmail, recipients, + nsCaseInsensitiveCStringComparator) || + FindInReadable(identityEmail, ccList, + nsCaseInsensitiveCStringComparator)) { + identity = anIdentity; + break; + } + } + if (!identity) // Found no match -> don't reply. + return NS_ERROR_ABORT; + + RefPtr<nsMsgTemplateReplyHelper> helper = new nsMsgTemplateReplyHelper; + + helper->mHdrToReplyTo = aMsgHdr; + helper->mMsgWindow = aMsgWindow; + helper->mIdentity = identity; + + nsAutoCString replyTo; + aMsgHdr->GetStringProperty("replyTo", replyTo); + if (replyTo.IsEmpty()) aMsgHdr->GetAuthor(getter_Copies(replyTo)); + if (replyTo.IsEmpty()) return NS_ERROR_FAILURE; // nowhere to send the reply + + nsCOMPtr<nsIMsgFolder> templateFolder; + nsCOMPtr<nsIMsgDatabase> templateDB; + nsCString templateMsgHdrUri; + const char* query = PL_strstr(templateUriFlat.get(), "?messageId="); + if (!query) return NS_ERROR_FAILURE; + + nsAutoCString folderUri(Substring(templateUriFlat.get(), query)); + rv = GetExistingFolder(folderUri, getter_AddRefs(templateFolder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = templateFolder->GetMsgDatabase(getter_AddRefs(templateDB)); + NS_ENSURE_SUCCESS(rv, rv); + + const char* subject = PL_strstr(templateUriFlat.get(), "&subject="); + if (subject) { + const char* subjectEnd = subject + strlen(subject); + nsAutoCString messageId(Substring(query + 11, subject)); + nsAutoCString subjectString(Substring(subject + 9, subjectEnd)); + templateDB->GetMsgHdrForMessageID(messageId.get(), + getter_AddRefs(helper->mTemplateHdr)); + if (helper->mTemplateHdr) + templateFolder->GetUriForMsg(helper->mTemplateHdr, templateMsgHdrUri); + // to use the subject, we'd need to expose a method to find a message by + // subject, or painfully iterate through messages...We'll try to make the + // message-id not change when saving a template first. + } + if (templateMsgHdrUri.IsEmpty()) { + // ### probably want to return a specific error and + // have the calling code disable the filter. + NS_ASSERTION(false, "failed to get msg hdr"); + return NS_ERROR_FAILURE; + } + // we need to convert the template uri, which is of the form + // <folder uri>?messageId=<messageId>&subject=<subject> + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(templateMsgHdrUri, getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> listenerSupports; + helper->QueryInterface(NS_GET_IID(nsISupports), + getter_AddRefs(listenerSupports)); + + nsCOMPtr<nsIURI> dummyNull; + rv = msgService->StreamMessage( + templateMsgHdrUri, listenerSupports, aMsgWindow, helper, + false, // convert data + EmptyCString(), false, getter_AddRefs(dummyNull)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> folder; + aMsgHdr->GetFolder(getter_AddRefs(folder)); + if (!folder) return NS_ERROR_NULL_POINTER; + + // We're sending a new message. Conceptually it's a reply though, so mark the + // original message as replied. + return folder->AddMessageDispositionState( + aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Replied); +} + +NS_IMETHODIMP +nsMsgComposeService::ForwardMessage(const nsAString& forwardTo, + nsIMsgDBHdr* aMsgHdr, + nsIMsgWindow* aMsgWindow, + nsIMsgIncomingServer* aServer, + uint32_t aForwardType) { + NS_ENSURE_ARG_POINTER(aMsgHdr); + + nsresult rv; + if (aForwardType == nsIMsgComposeService::kForwardAsDefault) { + int32_t forwardPref = 0; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + prefBranch->GetIntPref("mail.forward_message_mode", &forwardPref); + // 0=default as attachment 2=forward as inline with attachments, + // (obsolete 4.x value)1=forward as quoted (mapped to 2 in mozilla) + aForwardType = forwardPref == 0 ? nsIMsgComposeService::kForwardAsAttachment + : nsIMsgComposeService::kForwardInline; + } + nsCString msgUri; + + nsCOMPtr<nsIMsgFolder> folder; + aMsgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER); + + folder->GetUriForMsg(aMsgHdr, msgUri); + + nsAutoCString uriToOpen(msgUri); + + // get the MsgIdentity for the above key using AccountManager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + nsCOMPtr<nsIMsgIdentity> identity; + + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + rv = account->GetDefaultIdentity(getter_AddRefs(identity)); + // Use default identity if no identity has been found on this account + if (NS_FAILED(rv) || !identity) { + rv = GetDefaultIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aForwardType == nsIMsgComposeService::kForwardInline) + return RunMessageThroughMimeDraft( + uriToOpen, nsMimeOutput::nsMimeMessageDraftOrTemplate, identity, + uriToOpen, aMsgHdr, true, forwardTo, false, aMsgWindow, false); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow; + if (aMsgWindow) { + nsCOMPtr<nsIDocShell> docShell; + rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + parentWindow = do_GetInterface(docShell); + NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE); + } + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgCompFields> compFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + + compFields->SetTo(forwardTo); + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::ForwardAsAttachment); + pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetComposeFields(compFields); + pMsgComposeParams->SetOriginalMsgURI(uriToOpen); + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose( + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion + * response **/ + rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<Promise> promise; + rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, + nullptr, nullptr, getter_AddRefs(promise)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsMsgCompose::ProcessReplyFlags usually takes care of marking messages + // as forwarded. ProcessReplyFlags is normally called from + // nsMsgComposeSendListener::OnStopSending but for this case the msgCompose + // object is not set so ProcessReplyFlags won't get called. + // Therefore, let's just mark it here instead. + return folder->AddMessageDispositionState( + aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded); +} + +nsresult nsMsgComposeService::AddGlobalHtmlDomains() { + nsresult rv; + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> defaultsPrefBranch; + rv = prefs->GetDefaultBranch(MAILNEWS_ROOT_PREF, + getter_AddRefs(defaultsPrefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + /** + * Check to see if we need to add any global domains. + * If so, make sure the following prefs are added to mailnews.js + * + * 1. pref("mailnews.global_html_domains.version", version number); + * This pref registers the current version in the user prefs file. A default + * value is stored in mailnews file. Depending the changes we plan to make we + * can move the default version number. Comparing version number from user's + * prefs file and the default one from mailnews.js, we can effect ppropriate + * changes. + * + * 2. pref("mailnews.global_html_domains", <comma separated domain list>); + * This pref contains the list of html domains that ISP can add to make that + * user's contain all of these under the HTML domains in the + * Mail&NewsGrpus|Send Format under global preferences. + */ + int32_t htmlDomainListCurrentVersion, htmlDomainListDefaultVersion; + rv = prefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, + &htmlDomainListCurrentVersion); + NS_ENSURE_SUCCESS(rv, rv); + + rv = defaultsPrefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, + &htmlDomainListDefaultVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // Update the list as needed + if (htmlDomainListCurrentVersion <= htmlDomainListDefaultVersion) { + // Get list of global domains need to be added + nsCString globalHtmlDomainList; + rv = prefBranch->GetCharPref(HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME, + globalHtmlDomainList); + + if (NS_SUCCEEDED(rv) && !globalHtmlDomainList.IsEmpty()) { + nsTArray<nsCString> domainArray; + + // Get user's current HTML domain set for send format + nsCString currentHtmlDomainList; + rv = prefBranch->GetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, + currentHtmlDomainList); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString newHtmlDomainList(currentHtmlDomainList); + // Get the current html domain list into new list var + ParseString(currentHtmlDomainList, DOMAIN_DELIMITER, domainArray); + + // Get user's current Plaintext domain set for send format + nsCString currentPlaintextDomainList; + rv = prefBranch->GetCharPref(USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME, + currentPlaintextDomainList); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current plaintext domain list into new list var + ParseString(currentPlaintextDomainList, DOMAIN_DELIMITER, domainArray); + + size_t i = domainArray.Length(); + if (i > 0) { + // Append each domain in the preconfigured html domain list + globalHtmlDomainList.StripWhitespace(); + ParseString(globalHtmlDomainList, DOMAIN_DELIMITER, domainArray); + + // Now add each domain that does not already appear in + // the user's current html or plaintext domain lists + for (; i < domainArray.Length(); i++) { + if (domainArray.IndexOf(domainArray[i]) == i) { + if (!newHtmlDomainList.IsEmpty()) + newHtmlDomainList += DOMAIN_DELIMITER; + newHtmlDomainList += domainArray[i]; + } + } + } else { + // User has no domains listed either in html or plain text category. + // Assign the global list to be the user's current html domain list + newHtmlDomainList = globalHtmlDomainList; + } + + // Set user's html domain pref with the updated list + rv = prefBranch->SetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, + newHtmlDomainList); + NS_ENSURE_SUCCESS(rv, rv); + + // Increase the version to avoid running the update code unless needed + // (based on default version) + rv = prefBranch->SetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, + htmlDomainListCurrentVersion + 1); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeService::RegisterComposeDocShell(nsIDocShell* aDocShell, + nsIMsgCompose* aComposeObject) { + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aComposeObject); + + nsresult rv; + + // add the msg compose / dom window mapping to our hash table + nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsWeakPtr weakMsgComposePtr = do_GetWeakReference(aComposeObject); + NS_ENSURE_SUCCESS(rv, rv); + mOpenComposeWindows.InsertOrUpdate(weakDocShell, weakMsgComposePtr); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::UnregisterComposeDocShell(nsIDocShell* aDocShell) { + NS_ENSURE_ARG_POINTER(aDocShell); + + nsresult rv; + nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mOpenComposeWindows.Remove(weakDocShell); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::GetMsgComposeForDocShell(nsIDocShell* aDocShell, + nsIMsgCompose** aComposeObject) { + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aComposeObject); + + if (!mOpenComposeWindows.Count()) return NS_ERROR_FAILURE; + + // get the weak reference for our dom window + nsresult rv; + nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsWeakPtr weakMsgComposePtr; + + if (!mOpenComposeWindows.Get(weakDocShell, getter_AddRefs(weakMsgComposePtr))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(weakMsgComposePtr, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aComposeObject = msgCompose); + return rv; +} + +/** + * LoadDraftOrTemplate + * Helper routine used to run msgURI through libmime in order to fetch the + * contents for a draft or template. + */ +nsresult nsMsgComposeService::LoadDraftOrTemplate( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI, + nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, bool overrideComposeFormat, + nsIMsgWindow* aMsgWindow, bool autodetectCharset) { + return RunMessageThroughMimeDraft( + aMsgURI, aOutType, aIdentity, aOriginalMsgURI, aOrigMsgHdr, + aForwardInline, EmptyString(), overrideComposeFormat, aMsgWindow, + autodetectCharset); +} + +/** + * Run the aMsgURI message through libmime. We set various attributes of the + * nsIMimeStreamConverter so mimedrft.cpp will know what to do with the message + * when its done streaming. Usually that will be opening a compose window + * with the contents of the message, but if forwardTo is non-empty, mimedrft.cpp + * will forward the contents directly. + * + * @param aMsgURI URI to stream, which is the msgUri + any extra terms, e.g., + * "redirect=true". + * @param aOutType nsMimeOutput::nsMimeMessageDraftOrTemplate or + * nsMimeOutput::nsMimeMessageEditorTemplate + * @param aIdentity identity to use for the new message + * @param aOriginalMsgURI msgURI w/o any extra terms + * @param aOrigMsgHdr nsIMsgDBHdr corresponding to aOriginalMsgURI + * @param aForwardInline true if doing a forward inline + * @param aForwardTo e-mail address to forward msg to. This is used for + * forward inline message filter actions. + * @param aOverrideComposeFormat True if the user had shift key down when + doing a command that opens the compose window, + * which means we switch the compose window used + * from the default. + * @param aMsgWindow msgWindow to pass into LoadMessage. + */ +nsresult nsMsgComposeService::RunMessageThroughMimeDraft( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI, + nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, const nsAString& aForwardTo, + bool aOverrideComposeFormat, nsIMsgWindow* aMsgWindow, + bool autodetectCharset) { + nsCOMPtr<nsIMsgMessageService> messageService; + nsresult rv = + GetMessageServiceFromURI(aMsgURI, getter_AddRefs(messageService)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a mime parser (nsIMimeStreamConverter)to do the conversion. + nsCOMPtr<nsIMimeStreamConverter> mimeConverter = do_CreateInstance( + "@mozilla.org/streamconv;1?from=message/rfc822&to=application/xhtml+xml", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mimeConverter->SetMimeOutputType( + aOutType); // Set the type of output for libmime + mimeConverter->SetForwardInline(aForwardInline); + if (!aForwardTo.IsEmpty()) { + mimeConverter->SetForwardInlineFilter(true); + mimeConverter->SetForwardToAddress(aForwardTo); + } + mimeConverter->SetOverrideComposeFormat(aOverrideComposeFormat); + mimeConverter->SetIdentity(aIdentity); + mimeConverter->SetOriginalMsgURI(aOriginalMsgURI); + mimeConverter->SetOrigMsgHdr(aOrigMsgHdr); + + nsCOMPtr<nsIURI> url; + bool fileUrl = StringBeginsWith(aMsgURI, "file:"_ns); + nsCString mailboxUri(aMsgURI); + if (fileUrl) { + // We loaded a .eml file from a file: url. Construct equivalent mailbox url. + mailboxUri.Replace(0, 5, "mailbox:"_ns); + mailboxUri.AppendLiteral("&number=0"); + // Need this to prevent nsMsgCompose::TagEmbeddedObjects from setting + // inline images as moz-do-not-send. + mimeConverter->SetOriginalMsgURI(mailboxUri); + } + if (fileUrl || PromiseFlatCString(aMsgURI).Find( + "&type=application/x-message-display") >= 0) + rv = NS_NewURI(getter_AddRefs(url), mailboxUri); + else + rv = messageService->GetUrlForUri(aMsgURI, aMsgWindow, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(url); + if (!mailnewsurl) { + NS_WARNING( + "Trying to run a message through MIME which doesn't have a " + "nsIMsgMailNewsUrl?"); + return NS_ERROR_UNEXPECTED; + } + // SetSpecInternal must not fail, or else the URL won't have a base URL and + // we'll crash later. + rv = mailnewsurl->SetSpecInternal(mailboxUri); + NS_ENSURE_SUCCESS(rv, rv); + + // if we are forwarding a message and that message used a charset override + // then forward that as auto-detect flag, too. + nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(url)); + if (i18nUrl) (void)i18nUrl->SetAutodetectCharset(autodetectCharset); + + nsCOMPtr<nsIPrincipal> nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannel( + getter_AddRefs(channel), url, nullptr, nullPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed."); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIStreamConverter> converter = do_QueryInterface(mimeConverter); + rv = converter->AsyncConvertData(nullptr, nullptr, nullptr, channel); + NS_ENSURE_SUCCESS(rv, rv); + + // Now, just plug the two together and get the hell out of the way! + nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(mimeConverter); + return messageService->LoadMessage(aMsgURI, streamListener, aMsgWindow, + nullptr, autodetectCharset); +} + +NS_IMETHODIMP +nsMsgComposeService::Handle(nsICommandLine* aCmdLine) { + NS_ENSURE_ARG_POINTER(aCmdLine); + + nsresult rv; + int32_t found, end, count; + nsAutoString uristr; + bool composeShouldHandle = true; + + rv = aCmdLine->FindFlag(u"compose"_ns, false, &found); + NS_ENSURE_SUCCESS(rv, rv); + +#ifndef MOZ_SUITE + // MAC OS X passes in -url mailto:mscott@mozilla.org into the command line + // instead of -compose. + if (found == -1) { + rv = aCmdLine->FindFlag(u"url"_ns, false, &found); + NS_ENSURE_SUCCESS(rv, rv); + // we don't want to consume the argument for -url unless we're sure it is a + // mailto url and we'll figure that out shortly. + composeShouldHandle = false; + } +#endif + + if (found == -1) return NS_OK; + + end = found; + + rv = aCmdLine->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count > found + 1) { + aCmdLine->GetArgument(found + 1, uristr); + if (StringBeginsWith(uristr, u"mailto:"_ns) || + StringBeginsWith(uristr, u"preselectid="_ns) || + StringBeginsWith(uristr, u"to="_ns) || + StringBeginsWith(uristr, u"cc="_ns) || + StringBeginsWith(uristr, u"bcc="_ns) || + StringBeginsWith(uristr, u"newsgroups="_ns) || + StringBeginsWith(uristr, u"subject="_ns) || + StringBeginsWith(uristr, u"format="_ns) || + StringBeginsWith(uristr, u"body="_ns) || + StringBeginsWith(uristr, u"attachment="_ns) || + StringBeginsWith(uristr, u"message="_ns) || + StringBeginsWith(uristr, u"from="_ns)) { + composeShouldHandle = true; // the -url argument looks like mailto + end++; + // mailto: URIs are frequently passed with spaces in them. They should be + // escaped with %20, but we hack around broken clients. See bug 231032. + while (end + 1 < count) { + nsAutoString curarg; + aCmdLine->GetArgument(end + 1, curarg); + if (curarg.First() == '-') break; + + uristr.Append(' '); + uristr.Append(curarg); + ++end; + } + } else { + uristr.Truncate(); + } + } + if (composeShouldHandle) { + aCmdLine->RemoveArguments(found, end); + + nsCOMPtr<nsIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupportsString> arg( + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + if (arg) arg->SetData(uristr); + + nsCOMPtr<nsIMutableArray> params(do_CreateInstance(NS_ARRAY_CONTRACTID)); + params->AppendElement(arg); + params->AppendElement(aCmdLine); + + nsCOMPtr<mozIDOMWindowProxy> opened; + wwatch->OpenWindow(nullptr, DEFAULT_CHROME, "_blank"_ns, + "chrome,dialog=no,all"_ns, params, + getter_AddRefs(opened)); + + aCmdLine->SetPreventDefault(true); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeService::GetHelpInfo(nsACString& aResult) { + // clang-format off + aResult.AssignLiteral( + " -compose [ <options> ] Compose a mail or news message. Options are specified\n" + " as string \"option='value,...',option=value,...\" and\n" + " include: from, to, cc, bcc, newsgroups, subject, body,\n" + " message (file), attachment (file), format (html | text).\n" + " Example: \"to=john@example.com,subject='Dinner tonight?'\"\n"); + return NS_OK; + // clang-format on +} diff --git a/comm/mailnews/compose/src/nsMsgComposeService.h b/comm/mailnews/compose/src/nsMsgComposeService.h new file mode 100644 index 0000000000..97911fb71f --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeService.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define MSGCOMP_TRACE_PERFORMANCE 1 + +#include "nsIMsgComposeService.h" +#include "nsCOMPtr.h" +#include "mozIDOMWindow.h" +#include "nsIAppWindow.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsIWeakReference.h" +#include "nsIMimeStreamConverter.h" +#include "nsInterfaceHashtable.h" + +#include "nsICommandLineHandler.h" +#define ICOMMANDLINEHANDLER nsICommandLineHandler + +class nsMsgComposeService : public nsIMsgComposeService, + public ICOMMANDLINEHANDLER, + public nsSupportsWeakReference { + public: + nsMsgComposeService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSESERVICE + NS_DECL_NSICOMMANDLINEHANDLER + + nsresult Init(); + void Reset(); + void DeleteCachedWindows(); + nsresult AddGlobalHtmlDomains(); + + private: + virtual ~nsMsgComposeService(); + bool mLogComposePerformance; + + nsresult LoadDraftOrTemplate( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI, + nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, bool overrideComposeFormat, + nsIMsgWindow* aMsgWindow, bool autodetectCharset); + + nsresult RunMessageThroughMimeDraft( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI, + nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, const nsAString& forwardTo, + bool overrideComposeFormat, nsIMsgWindow* aMsgWindow, + bool autodetectCharset); + + // hash table mapping dom windows to nsIMsgCompose objects + nsInterfaceHashtable<nsISupportsHashKey, nsIWeakReference> + mOpenComposeWindows; + + // When doing a reply and the settings are enabled, get the HTML of the + // selected text in the original message window so that it can be quoted + // instead of the entire message. + nsresult GetOrigWindowSelection(MSG_ComposeType type, + mozilla::dom::Selection* selection, + nsACString& aSelHTML); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + PRIntervalTime mStartTime; + PRIntervalTime mPreviousTime; +#endif +}; diff --git a/comm/mailnews/compose/src/nsMsgCopy.cpp b/comm/mailnews/compose/src/nsMsgCopy.cpp new file mode 100644 index 0000000000..0cccc108f9 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCopy.cpp @@ -0,0 +1,471 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsMsgCopy.h" + +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIThread.h" +#include "nscore.h" +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefCountType.h" +#include "mozilla/RefPtr.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFolder.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgProtocolInfo.h" +#include "nsISupports.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsMsgCompUtils.h" +#include "prcmon.h" +#include "nsIMsgImapMailFolder.h" +#include "nsThreadUtils.h" +#include "nsIMsgWindow.h" +#include "nsIMsgProgress.h" +#include "nsComposeStrings.h" +#include "prmem.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsIURIMutator.h" + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the copy operation. We have to create this +// class to listen for message copy completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +NS_IMPL_ISUPPORTS(CopyListener, nsIMsgCopyServiceListener) + +CopyListener::CopyListener(void) { mCopyInProgress = false; } + +CopyListener::~CopyListener(void) {} + +nsresult CopyListener::OnStartCopy() { +#ifdef NS_DEBUG + printf("CopyListener::OnStartCopy()\n"); +#endif + + if (mComposeAndSend) mComposeAndSend->NotifyListenerOnStartCopy(); + return NS_OK; +} + +nsresult CopyListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) { +#ifdef NS_DEBUG + printf("CopyListener::OnProgress() %d of %d\n", aProgress, aProgressMax); +#endif + + if (mComposeAndSend) + mComposeAndSend->NotifyListenerOnProgressCopy(aProgress, aProgressMax); + + return NS_OK; +} + +nsresult CopyListener::SetMessageKey(nsMsgKey aMessageKey) { + if (mComposeAndSend) mComposeAndSend->SetMessageKey(aMessageKey); + return NS_OK; +} + +NS_IMETHODIMP +CopyListener::GetMessageId(nsACString& aMessageId) { + if (mComposeAndSend) mComposeAndSend->GetMessageId(aMessageId); + return NS_OK; +} + +nsresult CopyListener::OnStopCopy(nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { +#ifdef NS_DEBUG + printf("CopyListener: SUCCESSFUL ON THE COPY OPERATION!\n"); +#endif + } else { +#ifdef NS_DEBUG + printf("CopyListener: COPY OPERATION FAILED!\n"); +#endif + } + + if (mCopyInProgress) { + PR_CEnterMonitor(this); + PR_CNotifyAll(this); + mCopyInProgress = false; + PR_CExitMonitor(this); + } + if (mComposeAndSend) mComposeAndSend->NotifyListenerOnStopCopy(aStatus); + + return NS_OK; +} + +nsresult CopyListener::SetMsgComposeAndSendObject(nsIMsgSend* obj) { + if (obj) mComposeAndSend = obj; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// END END END END END END END END END END END END END END END +// This is the listener class for the copy operation. We have to create this +// class to listen for message copy completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsMsgCopy, nsIMsgCopy, nsIUrlListener) + +nsMsgCopy::nsMsgCopy() { + mFile = nullptr; + mMode = nsIMsgSend::nsMsgDeliverNow; + mSavePref = nullptr; + mIsDraft = false; + mMsgFlags = nsMsgMessageFlags::Read; +} + +nsMsgCopy::~nsMsgCopy() { PR_Free(mSavePref); } + +NS_IMETHODIMP +nsMsgCopy::StartCopyOperation(nsIMsgIdentity* aUserIdentity, nsIFile* aFile, + nsMsgDeliverMode aMode, nsIMsgSend* aMsgSendObj, + const nsACString& aSavePref, + nsIMsgDBHdr* aMsgToReplace) { + nsCOMPtr<nsIMsgFolder> dstFolder; + bool isDraft = false; + uint32_t msgFlags = nsMsgMessageFlags::Read; + bool waitForUrl = false; + nsresult rv; + + if (!aMsgSendObj) return NS_ERROR_INVALID_ARG; + + // Store away the server location... + if (!aSavePref.IsEmpty()) mSavePref = ToNewCString(aSavePref); + + // + // Vars for implementation... + // + + // QueueForLater (Outbox) + if (aMode == nsIMsgSend::nsMsgQueueForLater || + aMode == nsIMsgSend::nsMsgDeliverBackground) { + rv = GetUnsentMessagesFolder(aUserIdentity, getter_AddRefs(dstFolder), + &waitForUrl); + isDraft = false; + // Do not mark outgoing messages as read. + msgFlags = 0; + if (!dstFolder || NS_FAILED(rv)) { + return NS_MSG_UNABLE_TO_SEND_LATER; + } + } else if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts) + { + rv = GetDraftsFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl); + isDraft = true; + // Do not mark drafts as read. + msgFlags = 0; + if (!dstFolder || NS_FAILED(rv)) return NS_MSG_UNABLE_TO_SAVE_DRAFT; + } else if (aMode == + nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates) + { + rv = GetTemplatesFolder(aUserIdentity, getter_AddRefs(dstFolder), + &waitForUrl); + // Mark saved templates as read. + isDraft = false; + msgFlags = nsMsgMessageFlags::Read; + if (!dstFolder || NS_FAILED(rv)) return NS_MSG_UNABLE_TO_SAVE_TEMPLATE; + } else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent + { + rv = GetSentFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl); + // Mark send messages as read. + isDraft = false; + msgFlags = nsMsgMessageFlags::Read; + if (!dstFolder || NS_FAILED(rv)) return NS_MSG_COULDNT_OPEN_FCC_FOLDER; + } + + nsCOMPtr<nsIMsgWindow> msgWindow; + + if (aMsgSendObj) { + nsCOMPtr<nsIMsgProgress> progress; + aMsgSendObj->GetProgress(getter_AddRefs(progress)); + if (progress) progress->GetMsgWindow(getter_AddRefs(msgWindow)); + } + + mMode = aMode; + mFile = aFile; + mDstFolder = dstFolder; + mMsgToReplace = aMsgToReplace; + mIsDraft = isDraft; + mMsgSendObj = aMsgSendObj; + mMsgFlags = msgFlags; + if (!waitForUrl) { + // cache info needed for DoCopy and call DoCopy when OnStopUrl is called. + rv = DoCopy(aFile, dstFolder, aMsgToReplace, isDraft, msgFlags, msgWindow, + aMsgSendObj); + // N.B. "this" may be deleted when this call returns. + } + return rv; +} + +nsresult nsMsgCopy::DoCopy(nsIFile* aDiskFile, nsIMsgFolder* dstFolder, + nsIMsgDBHdr* aMsgToReplace, bool aIsDraft, + uint32_t aMsgFlags, nsIMsgWindow* msgWindow, + nsIMsgSend* aMsgSendObj) { + nsresult rv = NS_OK; + + // Check sanity + if ((!aDiskFile) || (!dstFolder)) return NS_ERROR_INVALID_ARG; + + // Call copyservice with dstFolder, disk file, and txnManager + if (NS_SUCCEEDED(rv)) { + RefPtr<CopyListener> copyListener = new CopyListener(); + if (!copyListener) return NS_ERROR_OUT_OF_MEMORY; + + copyListener->SetMsgComposeAndSendObject(aMsgSendObj); + nsCOMPtr<nsIThread> thread; + + if (aIsDraft) { + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(dstFolder); + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) return rv; + bool shutdownInProgress = false; + rv = accountManager->GetShutdownInProgress(&shutdownInProgress); + + if (NS_SUCCEEDED(rv) && shutdownInProgress && imapFolder) { + // set the following only when we were in the middle of shutdown + // process + copyListener->mCopyInProgress = true; + thread = do_GetCurrentThread(); + } + } + nsCOMPtr<nsIMsgCopyService> copyService = + do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyService->CopyFileMessage(aDiskFile, dstFolder, aMsgToReplace, + aIsDraft, aMsgFlags, EmptyCString(), + copyListener, msgWindow); + // copyListener->mCopyInProgress can only be set when we are in the + // middle of the shutdown process + while (copyListener->mCopyInProgress) { + PR_CEnterMonitor(copyListener); + PR_CWait(copyListener, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(copyListener); + if (thread) NS_ProcessPendingEvents(thread); + } + } + + return rv; +} + +NS_IMETHODIMP +nsMsgCopy::GetDstFolder(nsIMsgFolder** aDstFolder) { + NS_ENSURE_ARG_POINTER(aDstFolder); + NS_IF_ADDREF(*aDstFolder = mDstFolder); + return NS_OK; +} + +// nsIUrlListener methods +NS_IMETHODIMP +nsMsgCopy::OnStartRunningUrl(nsIURI* aUrl) { return NS_OK; } + +NS_IMETHODIMP +nsMsgCopy::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) { + nsresult rv = aExitCode; + if (NS_SUCCEEDED(aExitCode)) { + rv = DoCopy(mFile, mDstFolder, mMsgToReplace, mIsDraft, mMsgFlags, nullptr, + mMsgSendObj); + } + return rv; +} + +nsresult nsMsgCopy::GetUnsentMessagesFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** folder, + bool* waitForUrl) { + nsresult ret = LocateMessageFolder( + userIdentity, nsIMsgSend::nsMsgQueueForLater, mSavePref, folder); + if (*folder) (*folder)->SetFlag(nsMsgFolderFlags::Queue); + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult nsMsgCopy::GetDraftsFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** folder, bool* waitForUrl) { + nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgSaveAsDraft, + mSavePref, folder); + if (*folder) (*folder)->SetFlag(nsMsgFolderFlags::Drafts); + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult nsMsgCopy::GetTemplatesFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** folder, + bool* waitForUrl) { + nsresult ret = LocateMessageFolder( + userIdentity, nsIMsgSend::nsMsgSaveAsTemplate, mSavePref, folder); + if (*folder) (*folder)->SetFlag(nsMsgFolderFlags::Templates); + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult nsMsgCopy::GetSentFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** folder, bool* waitForUrl) { + nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgDeliverNow, + mSavePref, folder); + if (*folder) { + // If mSavePref is the same as the identity's fcc folder, set the sent flag. + nsCString identityFccUri; + userIdentity->GetFccFolder(identityFccUri); + if (identityFccUri.Equals(mSavePref)) + (*folder)->SetFlag(nsMsgFolderFlags::SentMail); + } + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult nsMsgCopy::CreateIfMissing(nsIMsgFolder** folder, bool* waitForUrl) { + nsresult rv = NS_OK; + if (folder && *folder) { + nsCOMPtr<nsIMsgFolder> parent; + (*folder)->GetParent(getter_AddRefs(parent)); + if (!parent) { + nsCOMPtr<nsIFile> folderPath; + // for local folders, path is to the berkeley mailbox. + // for imap folders, path needs to have .msf appended to the name + (*folder)->GetFilePath(getter_AddRefs(folderPath)); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = (*folder)->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isAsyncFolder; + rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // if we can't get the path from the folder, then try to create the + // storage. for imap, it doesn't matter if the .msf file exists - it still + // might not exist on the server, so we should try to create it + bool exists = false; + if (!isAsyncFolder && folderPath) folderPath->Exists(&exists); + if (!exists) { + (*folder)->CreateStorageIfMissing(this); + if (isAsyncFolder) *waitForUrl = true; + + rv = NS_OK; + } + } + } + return rv; +} +//////////////////////////////////////////////////////////////////////////////////// +// Utility Functions for MsgFolders +//////////////////////////////////////////////////////////////////////////////////// +nsresult LocateMessageFolder(nsIMsgIdentity* userIdentity, + nsMsgDeliverMode aFolderType, + const char* aFolderURI, nsIMsgFolder** msgFolder) { + nsresult rv = NS_OK; + + if (!msgFolder) return NS_ERROR_NULL_POINTER; + *msgFolder = nullptr; + + if (!aFolderURI || !*aFolderURI) return NS_ERROR_INVALID_ARG; + + // as long as it doesn't start with anyfolder:// + if (PL_strncasecmp(ANY_SERVER, aFolderURI, strlen(aFolderURI)) != 0) { + nsCOMPtr<nsIMsgFolder> folder; + rv = GetOrCreateFolder(nsDependentCString(aFolderURI), + getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + // Don't check validity of folder - caller will handle creating it. + nsCOMPtr<nsIMsgIncomingServer> server; + // make sure that folder hierarchy is built so that legitimate parent-child + // relationship is established. + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + return server->GetMsgFolderFromURI(folder, nsDependentCString(aFolderURI), + msgFolder); + } else { + if (!userIdentity) return NS_ERROR_INVALID_ARG; + + // get the account manager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // If any folder will do, go look for one. + nsTArray<RefPtr<nsIMsgIncomingServer>> servers; + rv = accountManager->GetServersForIdentity(userIdentity, servers); + NS_ENSURE_SUCCESS(rv, rv); + + // Ok, we have to look through the servers and try to find the server that + // has a valid folder of the type that interests us... + for (auto inServer : servers) { + // Now that we have the server...we need to get the named message folder + + // If aFolderURI is passed in, then the user has chosen a specific + // mail folder to save the message, but if it is null, just find the + // first one and make that work. The folder is specified as a URI, like + // the following: + // + // mailbox://nobody@Local Folders/Sent + // imap://rhp@nsmail-2/Drafts + // newsgroup://news.mozilla.org/netscape.test + // + nsCString serverURI; + rv = inServer->GetServerURI(serverURI); + if (NS_FAILED(rv) || serverURI.IsEmpty()) continue; + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = inServer->GetRootFolder(getter_AddRefs(rootFolder)); + + if (NS_FAILED(rv) || (!rootFolder)) continue; + + // use the defaults by getting the folder by flags + if (aFolderType == nsIMsgSend::nsMsgQueueForLater || + aFolderType == nsIMsgSend::nsMsgDeliverBackground) { + // QueueForLater (Outbox) + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Queue, msgFolder); + } else if (aFolderType == + nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts, msgFolder); + } else if (aFolderType == + nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Templates, msgFolder); + } else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail, msgFolder); + } + + if (*msgFolder) { + return NS_OK; + } + } + } + return NS_ERROR_FAILURE; +} + +// +// Figure out if a folder is local or not and return a boolean to +// say so. +// +nsresult MessageFolderIsLocal(nsIMsgIdentity* userIdentity, + nsMsgDeliverMode aFolderType, + const char* aFolderURI, bool* aResult) { + nsresult rv; + + if (!aFolderURI) return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIURL> url; + rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec(nsDependentCString(aFolderURI)) + .Finalize(url); + if (NS_FAILED(rv)) return rv; + + /* mailbox:/ means its local (on disk) */ + rv = url->SchemeIs("mailbox", aResult); + if (NS_FAILED(rv)) return rv; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgCopy.h b/comm/mailnews/compose/src/nsMsgCopy.h new file mode 100644 index 0000000000..4fa50bd8ac --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgCopy.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgCopy_H_ +#define _nsMsgCopy_H_ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsIFile.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsITransactionManager.h" +#include "nsIMsgCopy.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIMsgCopyService.h" + +// Forward declarations... +class nsMsgCopy; + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the copy operation. We have to create this +// class to listen for message copy completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +class CopyListener : public nsIMsgCopyServiceListener { + public: + CopyListener(void); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD OnStartCopy() override; + + NS_IMETHOD OnProgress(uint32_t aProgress, uint32_t aProgressMax) override; + + NS_IMETHOD SetMessageKey(nsMsgKey aMessageKey) override; + + NS_IMETHOD GetMessageId(nsACString& aMessageId) override; + + NS_IMETHOD OnStopCopy(nsresult aStatus) override; + + NS_IMETHOD SetMsgComposeAndSendObject(nsIMsgSend* obj); + + bool mCopyInProgress; + + private: + virtual ~CopyListener(); + nsCOMPtr<nsIMsgSend> mComposeAndSend; +}; + +// +// This is a class that deals with processing remote attachments. It implements +// an nsIStreamListener interface to deal with incoming data +// +class nsMsgCopy : public nsIMsgCopy, public nsIUrlListener { + public: + nsMsgCopy(); + + // nsISupports interface + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOPY + NS_DECL_NSIURLLISTENER + + ////////////////////////////////////////////////////////////////////// + // Object methods... + ////////////////////////////////////////////////////////////////////// + // + nsresult DoCopy(nsIFile* aDiskFile, nsIMsgFolder* dstFolder, + nsIMsgDBHdr* aMsgToReplace, bool aIsDraft, uint32_t aMsgFlags, + nsIMsgWindow* msgWindow, nsIMsgSend* aMsgSendObj); + + nsresult GetUnsentMessagesFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** msgFolder, bool* waitForUrl); + nsresult GetDraftsFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** msgFolder, bool* waitForUrl); + nsresult GetTemplatesFolder(nsIMsgIdentity* userIdentity, + nsIMsgFolder** msgFolder, bool* waitForUrl); + nsresult GetSentFolder(nsIMsgIdentity* userIdentity, nsIMsgFolder** msgFolder, + bool* waitForUrl); + nsresult CreateIfMissing(nsIMsgFolder** folder, bool* waitForUrl); + + // + // Vars for implementation... + // + nsIFile* mFile; // the file we are sending... + nsMsgDeliverMode mMode; + nsCOMPtr<nsIMsgFolder> mDstFolder; + nsCOMPtr<nsIMsgDBHdr> mMsgToReplace; + bool mIsDraft; + uint32_t mMsgFlags; + nsCOMPtr<nsIMsgSend> mMsgSendObj; + char* mSavePref; + + private: + virtual ~nsMsgCopy(); +}; + +// Useful function for the back end... +nsresult LocateMessageFolder(nsIMsgIdentity* userIdentity, + nsMsgDeliverMode aFolderType, const char* aSaveURI, + nsIMsgFolder** msgFolder); + +nsresult MessageFolderIsLocal(nsIMsgIdentity* userIdentity, + nsMsgDeliverMode aFolderType, + const char* aSaveURI, bool* aResult); + +#endif /* _nsMsgCopy_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgPrompts.cpp b/comm/mailnews/compose/src/nsMsgPrompts.cpp new file mode 100644 index 0000000000..ff0f133285 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgPrompts.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsMsgPrompts.h" + +#include "nsMsgCopy.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsComposeStrings.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Components.h" +#include "nsIPromptService.h" +#include "nsEmbedCID.h" + +nsresult nsMsgGetMessageByName(const char* aName, nsString& aResult) { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + return bundle->GetStringFromName(aName, aResult); +} + +static nsresult nsMsgBuildMessageByName(const char* aName, nsIFile* aFile, + nsString& aResult) { + NS_ENSURE_ARG_POINTER(aFile); + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString path; + aFile->GetPath(path); + + AutoTArray<nsString, 1> params = {path}; + return bundle->FormatStringFromName(aName, params, aResult); +} + +nsresult nsMsgBuildMessageWithFile(nsIFile* aFile, nsString& aResult) { + return nsMsgBuildMessageByName("unableToOpenFile", aFile, aResult); +} + +nsresult nsMsgBuildMessageWithTmpFile(nsIFile* aFile, nsString& aResult) { + return nsMsgBuildMessageByName("unableToOpenTmpFile", aFile, aResult); +} + +nsresult nsMsgDisplayMessageByName(mozIDOMWindowProxy* window, + const char* aName, + const char16_t* windowTitle) { + nsString msg; + nsMsgGetMessageByName(aName, msg); + return nsMsgDisplayMessageByString(window, msg.get(), windowTitle); +} + +nsresult nsMsgDisplayMessageByString(mozIDOMWindowProxy* window, + const char16_t* msg, + const char16_t* windowTitle) { + NS_ENSURE_ARG_POINTER(msg); + + nsresult rv; + nsCOMPtr<nsIPromptService> dlgService( + do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return dlgService->Alert(window, windowTitle, msg); +} + +nsresult nsMsgAskBooleanQuestionByString(mozIDOMWindowProxy* window, + const char16_t* msg, bool* answer, + const char16_t* windowTitle) { + NS_ENSURE_TRUE(msg && *msg, NS_ERROR_INVALID_ARG); + + nsresult rv; + nsCOMPtr<nsIPromptService> dlgService( + do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return dlgService->Confirm(window, windowTitle, msg, answer); +} diff --git a/comm/mailnews/compose/src/nsMsgPrompts.h b/comm/mailnews/compose/src/nsMsgPrompts.h new file mode 100644 index 0000000000..300b6ffee2 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgPrompts.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgPrompts_H_ +#define _nsMsgPrompts_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" + +class mozIDOMWindowProxy; + +nsresult nsMsgGetMessageByName(const char* aName, nsString& aResult); +nsresult nsMsgBuildMessageWithFile(nsIFile* aFile, nsString& aResult); +nsresult nsMsgBuildMessageWithTmpFile(nsIFile* aFile, nsString& aResult); +nsresult nsMsgDisplayMessageByName(mozIDOMWindowProxy* window, + const char* aName, + const char16_t* windowTitle = nullptr); +nsresult nsMsgDisplayMessageByString(mozIDOMWindowProxy* window, + const char16_t* msg, + const char16_t* windowTitle = nullptr); +nsresult nsMsgAskBooleanQuestionByString(mozIDOMWindowProxy* window, + const char16_t* msg, bool* answer, + const char16_t* windowTitle = nullptr); + +#endif /* _nsMsgPrompts_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgQuote.cpp b/comm/mailnews/compose/src/nsMsgQuote.cpp new file mode 100644 index 0000000000..75efbf67ca --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgQuote.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIURL.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIServiceManager.h" +#include "nsIStreamListener.h" +#include "nsIStreamConverter.h" +#include "nsIStreamConverterService.h" +#include "nsIMimeStreamConverter.h" +#include "nsMimeTypes.h" +#include "nsICharsetConverterManager.h" +#include "prprf.h" +#include "nsMsgQuote.h" +#include "nsMsgCompUtils.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsNetUtil.h" +#include "nsMsgCompose.h" +#include "nsMsgMailNewsUrl.h" +#include "mozilla/Components.h" +#include "nsContentUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgQuoteListener, nsIMsgQuoteListener, + nsIMimeStreamConverterListener) + +nsMsgQuoteListener::nsMsgQuoteListener() {} + +nsMsgQuoteListener::~nsMsgQuoteListener() {} + +NS_IMETHODIMP nsMsgQuoteListener::SetMsgQuote(nsIMsgQuote* msgQuote) { + mMsgQuote = do_GetWeakReference(msgQuote); + return NS_OK; +} + +NS_IMETHODIMP nsMsgQuoteListener::GetMsgQuote(nsIMsgQuote** aMsgQuote) { + nsresult rv = NS_OK; + if (aMsgQuote) { + nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote); + msgQuote.forget(aMsgQuote); + } else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +nsresult nsMsgQuoteListener::OnHeadersReady(nsIMimeHeaders* headers) { + nsCOMPtr<nsIMsgQuotingOutputStreamListener> quotingOutputStreamListener; + nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote); + + if (msgQuote) + msgQuote->GetStreamListener(getter_AddRefs(quotingOutputStreamListener)); + + if (quotingOutputStreamListener) + quotingOutputStreamListener->SetMimeHeaders(headers); + return NS_OK; +} + +// +// Implementation... +// +nsMsgQuote::nsMsgQuote() { + mQuoteHeaders = false; + mQuoteListener = nullptr; +} + +nsMsgQuote::~nsMsgQuote() {} + +NS_IMPL_ISUPPORTS(nsMsgQuote, nsIMsgQuote, nsISupportsWeakReference) + +NS_IMETHODIMP nsMsgQuote::GetStreamListener( + nsIMsgQuotingOutputStreamListener** aStreamListener) { + if (!aStreamListener) { + return NS_ERROR_NULL_POINTER; + } + nsCOMPtr<nsIMsgQuotingOutputStreamListener> streamListener = + do_QueryReferent(mStreamListener); + if (!streamListener) { + return NS_ERROR_FAILURE; + } + NS_IF_ADDREF(*aStreamListener = streamListener); + return NS_OK; +} + +nsresult nsMsgQuote::QuoteMessage( + const nsACString& msgURI, bool quoteHeaders, + nsIMsgQuotingOutputStreamListener* aQuoteMsgStreamListener, + bool aAutodetectCharset, bool headersOnly, nsIMsgDBHdr* aMsgHdr) { + nsresult rv; + + mQuoteHeaders = quoteHeaders; + mStreamListener = do_GetWeakReference(aQuoteMsgStreamListener); + + nsAutoCString msgUri(msgURI); + bool fileUrl = StringBeginsWith(msgUri, "file:"_ns); + bool forwardedMessage = msgUri.Find("&realtype=message/rfc822") >= 0; + nsCOMPtr<nsIURI> newURI; + if (fileUrl) { + msgUri.Replace(0, 5, "mailbox:"_ns); + msgUri.AppendLiteral("?number=0"); + rv = NS_NewURI(getter_AddRefs(newURI), msgUri); + } else if (forwardedMessage) + rv = NS_NewURI(getter_AddRefs(newURI), msgURI); + else { + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(msgURI, getter_AddRefs(msgService)); + if (NS_FAILED(rv)) return rv; + rv = msgService->GetUrlForUri(msgURI, nullptr, getter_AddRefs(newURI)); + } + if (NS_FAILED(rv)) return rv; + + nsAutoCString queryPart; + rv = newURI->GetQuery(queryPart); + if (!queryPart.IsEmpty()) queryPart.Append('&'); + + if (headersOnly) /* We don't need to quote the message body but we still need + to extract the headers */ + queryPart.AppendLiteral("header=only"); + else if (quoteHeaders) + queryPart.AppendLiteral("header=quote"); + else + queryPart.AppendLiteral("header=quotebody"); + rv = NS_MutateURI(newURI).SetQuery(queryPart).Finalize(newURI); + NS_ENSURE_SUCCESS(rv, rv); + + // if we were told to auto-detect the charset, pass that on. + if (aAutodetectCharset) { + nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(newURI)); + if (i18nUrl) i18nUrl->SetAutodetectCharset(true); + } + + mQuoteListener = + do_CreateInstance("@mozilla.org/messengercompose/quotinglistener;1", &rv); + if (NS_FAILED(rv)) return rv; + mQuoteListener->SetMsgQuote(this); + + // funky magic go get the isupports for this class which inherits from + // multiple interfaces. + nsISupports* supports; + QueryInterface(NS_GET_IID(nsISupports), (void**)&supports); + nsCOMPtr<nsISupports> quoteSupport = supports; + NS_IF_RELEASE(supports); + + // now we want to create a necko channel for this url and we want to open it + mQuoteChannel = nullptr; + nsCOMPtr<nsIIOService> netService = mozilla::components::IO::Service(); + NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED); + rv = netService->NewChannelFromURI( + newURI, nullptr, nsContentUtils::GetSystemPrincipal(), nullptr, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER, getter_AddRefs(mQuoteChannel)); + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIStreamConverterService> streamConverterService = + do_GetService("@mozilla.org/streamConverters;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStreamListener> convertedListener; + nsCOMPtr<nsIMsgQuotingOutputStreamListener> streamListener = + do_QueryReferent(mStreamListener); + rv = streamConverterService->AsyncConvertData( + "message/rfc822", "application/xhtml+xml", streamListener, quoteSupport, + getter_AddRefs(convertedListener)); + if (NS_FAILED(rv)) return rv; + + // now try to open the channel passing in our display consumer as the + // listener + rv = mQuoteChannel->AsyncOpen(convertedListener); + return rv; +} + +NS_IMETHODIMP +nsMsgQuote::GetQuoteListener(nsIMimeStreamConverterListener** aQuoteListener) { + if (!aQuoteListener || !mQuoteListener) return NS_ERROR_NULL_POINTER; + NS_ADDREF(*aQuoteListener = mQuoteListener); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuote::GetQuoteChannel(nsIChannel** aQuoteChannel) { + if (!aQuoteChannel || !mQuoteChannel) return NS_ERROR_NULL_POINTER; + NS_ADDREF(*aQuoteChannel = mQuoteChannel); + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgQuote.h b/comm/mailnews/compose/src/nsMsgQuote.h new file mode 100644 index 0000000000..27525a6dc1 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgQuote.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef __nsMsgQuote_h__ +#define __nsMsgQuote_h__ + +#include "nsIMsgQuote.h" +#include "nsIMsgMessageService.h" +#include "nsIStreamListener.h" +#include "nsIMimeStreamConverter.h" +#include "nsIChannel.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +class nsMsgQuote; + +class nsMsgQuoteListener : public nsIMsgQuoteListener { + public: + nsMsgQuoteListener(); + + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIMimeStreamConverterListener support + NS_DECL_NSIMIMESTREAMCONVERTERLISTENER + NS_DECL_NSIMSGQUOTELISTENER + + private: + virtual ~nsMsgQuoteListener(); + nsWeakPtr mMsgQuote; +}; + +class nsMsgQuote : public nsIMsgQuote, public nsSupportsWeakReference { + public: + nsMsgQuote(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGQUOTE + + private: + virtual ~nsMsgQuote(); + // + // Implementation data... + // + nsWeakPtr mStreamListener; + bool mQuoteHeaders; + nsCOMPtr<nsIMsgQuoteListener> mQuoteListener; + nsCOMPtr<nsIChannel> mQuoteChannel; +}; + +#endif /* __nsMsgQuote_h__ */ diff --git a/comm/mailnews/compose/src/nsMsgSendLater.cpp b/comm/mailnews/compose/src/nsMsgSendLater.cpp new file mode 100644 index 0000000000..8e3b8b0a5d --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgSendLater.cpp @@ -0,0 +1,1406 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsMsgSendLater.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMsgCopy.h" +#include "nsIMsgSend.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMsgMessageService.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgCompUtils.h" +#include "nsMsgUtils.h" +#include "nsMailHeaders.h" +#include "nsMsgPrompts.h" +#include "nsISmtpUrl.h" +#include "nsIChannel.h" +#include "nsNetUtil.h" +#include "prlog.h" +#include "prmem.h" +#include "nsIMimeConverter.h" +#include "nsComposeStrings.h" +#include "nsIObserverService.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgDatabase.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIMsgWindow.h" +#include "nsMsgMessageFlags.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/Services.h" + +// Consts for checking and sending mail in milliseconds + +// 1 second from mail into the unsent messages folder to initially trying to +// send it. +const uint32_t kInitialMessageSendTime = 1000; + +NS_IMPL_ISUPPORTS(nsMsgSendLater, nsIMsgSendLater, nsIFolderListener, + nsIRequestObserver, nsIStreamListener, nsIObserver, + nsIUrlListener, nsIMsgShutdownTask) + +nsMsgSendLater::nsMsgSendLater() { + mSendingMessages = false; + mTimerSet = false; + mTotalSentSuccessfully = 0; + mTotalSendCount = 0; + mLeftoverBuffer = nullptr; + + m_to = nullptr; + m_bcc = nullptr; + m_fcc = nullptr; + m_newsgroups = nullptr; + m_newshost = nullptr; + m_headers = nullptr; + m_flags = 0; + m_headersFP = 0; + m_inhead = true; + m_headersPosition = 0; + + m_bytesRead = 0; + m_position = 0; + m_flagsPosition = 0; + m_headersSize = 0; + + mIdentityKey = nullptr; + mAccountKey = nullptr; + + mUserInitiated = false; +} + +nsMsgSendLater::~nsMsgSendLater() { + PR_Free(m_to); + PR_Free(m_fcc); + PR_Free(m_bcc); + PR_Free(m_newsgroups); + PR_Free(m_newshost); + PR_Free(m_headers); + PR_Free(mLeftoverBuffer); + PR_Free(mIdentityKey); + PR_Free(mAccountKey); +} + +nsresult nsMsgSendLater::Init() { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool sendInBackground; + rv = prefs->GetBoolPref("mailnews.sendInBackground", &sendInBackground); + // If we're not sending in the background, don't do anything else + if (NS_FAILED(rv) || !sendInBackground) return NS_OK; + + // We need to know when we're shutting down. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + rv = observerService->AddObserver(this, "xpcom-shutdown", false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->AddObserver(this, "quit-application", false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->AddObserver(this, "msg-shutdown", false); + NS_ENSURE_SUCCESS(rv, rv); + + // Subscribe to the unsent messages folder + // XXX This code should be set up for multiple unsent folders, however we + // don't support that at the moment, so for now just assume one folder. + nsCOMPtr<nsIMsgFolder> folder; + rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(folder)); + // There doesn't have to be a nsMsgQueueForLater flagged folder. + if (NS_FAILED(rv) || !folder) return NS_OK; + + rv = folder->AddFolderListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX may want to send messages X seconds after startup if there are any. + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (aSubject == mTimer && !strcmp(aTopic, "timer-callback")) { + if (mTimer) + mTimer->Cancel(); + else + NS_ERROR("mTimer was null in nsMsgSendLater::Observe"); + + mTimerSet = false; + // If we've already started a send since the timer fired, don't start + // another + if (!mSendingMessages) InternalSendMessages(false, nullptr); + } else if (!strcmp(aTopic, "quit-application")) { + // If the timer is set, cancel it - we're quitting, the shutdown service + // interfaces will sort out sending etc. + if (mTimer) mTimer->Cancel(); + + mTimerSet = false; + } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + // We're shutting down. Unsubscribe from the unsentFolder notifications + // they aren't any use to us now, we don't want to start sending more + // messages. + nsresult rv; + if (mMessageFolder) { + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv); + if (folder) { + rv = folder->RemoveFolderListener(this); + NS_ENSURE_SUCCESS(rv, rv); + folder->ForceDBClosed(); + } + } + + // Now remove ourselves from the observer service as well. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + rv = observerService->RemoveObserver(this, "xpcom-shutdown"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->RemoveObserver(this, "quit-application"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->RemoveObserver(this, "msg-shutdown"); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::SetStatusFeedback(nsIMsgStatusFeedback* aFeedback) { + mFeedback = aFeedback; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::GetStatusFeedback(nsIMsgStatusFeedback** aFeedback) { + NS_ENSURE_ARG_POINTER(aFeedback); + NS_IF_ADDREF(*aFeedback = mFeedback); + return NS_OK; +} + +// Stream is done...drive on! +NS_IMETHODIMP +nsMsgSendLater::OnStopRequest(nsIRequest* request, nsresult status) { + nsresult rv; + + // First, this shouldn't happen, but if + // it does, flush the buffer and move on. + if (mLeftoverBuffer) { + DeliverQueuedLine(mLeftoverBuffer, PL_strlen(mLeftoverBuffer)); + } + + if (mOutFile) mOutFile->Close(); + + // See if we succeeded on reading the message from the message store? + // + if (NS_SUCCEEDED(status)) { + // Message is done...send it! + rv = CompleteMailFileSend(); + +#ifdef NS_DEBUG + printf("nsMsgSendLater: Success on getting message...\n"); +#endif + + // If the send operation failed..try the next one... + if (NS_FAILED(rv)) { + rv = StartNextMailFileSend(rv); + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); + } + } else { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (!channel) return NS_ERROR_FAILURE; + + // extract the prompt object to use for the alert from the url.... + nsCOMPtr<nsIURI> uri; + nsCOMPtr<mozIDOMWindowProxy> domWindow; + if (channel) { + channel->GetURI(getter_AddRefs(uri)); + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(uri)); + nsCOMPtr<nsIMsgWindow> msgWindow; + if (msgUrl) msgUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) msgWindow->GetDomWindow(getter_AddRefs(domWindow)); + } + + nsMsgDisplayMessageByName(domWindow, "errorQueuedDeliveryFailed"); + + // Getting the data failed, but we will still keep trying to send the + // rest... + rv = StartNextMailFileSend(status); + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); + } + + return rv; +} + +char* FindEOL(char* inBuf, char* buf_end) { + char* buf = inBuf; + char* findLoc = nullptr; + + while (buf <= buf_end) + if (*buf == 0) + return buf; + else if ((*buf == '\n') || (*buf == '\r')) { + findLoc = buf; + break; + } else + ++buf; + + if (!findLoc) + return nullptr; + else if ((findLoc + 1) > buf_end) + return buf; + + if ((*findLoc == '\n' && *(findLoc + 1) == '\r') || + (*findLoc == '\r' && *(findLoc + 1) == '\n')) + findLoc++; // possibly a pair. + return findLoc; +} + +nsresult nsMsgSendLater::RebufferLeftovers(char* startBuf, uint32_t aLen) { + PR_FREEIF(mLeftoverBuffer); + mLeftoverBuffer = (char*)PR_Malloc(aLen + 1); + if (!mLeftoverBuffer) return NS_ERROR_OUT_OF_MEMORY; + + memcpy(mLeftoverBuffer, startBuf, aLen); + mLeftoverBuffer[aLen] = '\0'; + return NS_OK; +} + +nsresult nsMsgSendLater::BuildNewBuffer(const char* aBuf, uint32_t aCount, + uint32_t* totalBufSize) { + // Only build a buffer when there are leftovers... + if (!mLeftoverBuffer) { + return NS_ERROR_FAILURE; + } + + int32_t leftoverSize = PL_strlen(mLeftoverBuffer); + char* newBuffer = (char*)PR_Realloc(mLeftoverBuffer, aCount + leftoverSize); + NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY); + mLeftoverBuffer = newBuffer; + + memcpy(mLeftoverBuffer + leftoverSize, aBuf, aCount); + *totalBufSize = aCount + leftoverSize; + return NS_OK; +} + +// Got data? +NS_IMETHODIMP +nsMsgSendLater::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, + uint64_t sourceOffset, uint32_t count) { + NS_ENSURE_ARG_POINTER(inStr); + + // This is a little bit tricky since we have to chop random + // buffers into lines and deliver the lines...plus keeping the + // leftovers for next time...some fun, eh? + // + nsresult rv = NS_OK; + char* startBuf; + char* endBuf; + char* lineEnd; + char* newbuf = nullptr; + uint32_t size; + + uint32_t aCount = count; + char* aBuf = (char*)PR_Malloc(aCount + 1); + + inStr->Read(aBuf, count, &aCount); + + // First, create a new work buffer that will + if (NS_FAILED(BuildNewBuffer(aBuf, aCount, &size))) // no leftovers... + { + startBuf = (char*)aBuf; + endBuf = (char*)(aBuf + aCount - 1); + } else // yum, leftovers...new buffer created...sitting in mLeftoverBuffer + { + newbuf = mLeftoverBuffer; + startBuf = newbuf; + endBuf = startBuf + size - 1; + mLeftoverBuffer = nullptr; // null out this + } + + while (startBuf <= endBuf) { + lineEnd = FindEOL(startBuf, endBuf); + if (!lineEnd) { + rv = RebufferLeftovers(startBuf, (endBuf - startBuf) + 1); + break; + } + + rv = DeliverQueuedLine(startBuf, (lineEnd - startBuf) + 1); + if (NS_FAILED(rv)) break; + + startBuf = lineEnd + 1; + } + + PR_Free(newbuf); + PR_Free(aBuf); + return rv; +} + +NS_IMETHODIMP +nsMsgSendLater::OnStartRunningUrl(nsIURI* url) { return NS_OK; } + +NS_IMETHODIMP +nsMsgSendLater::OnStopRunningUrl(nsIURI* url, nsresult aExitCode) { + if (NS_SUCCEEDED(aExitCode)) InternalSendMessages(mUserInitiated, mIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnStartRequest(nsIRequest* request) { return NS_OK; } + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the send operation. We have to create this +// class to listen for message send completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +NS_IMPL_ISUPPORTS(SendOperationListener, nsIMsgSendListener, + nsIMsgCopyServiceListener) + +SendOperationListener::SendOperationListener(nsMsgSendLater* aSendLater) + : mSendLater(aSendLater) {} + +SendOperationListener::~SendOperationListener(void) {} + +NS_IMETHODIMP +SendOperationListener::OnGetDraftFolderURI(const char* aMsgID, + const nsACString& aFolderURI) { + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnStartSending(const char* aMsgID, uint32_t aMsgSize) { +#ifdef NS_DEBUG + printf("SendOperationListener::OnStartSending()\n"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnProgress(const char* aMsgID, uint32_t aProgress, + uint32_t aProgressMax) { +#ifdef NS_DEBUG + printf("SendOperationListener::OnProgress()\n"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnStatus(const char* aMsgID, const char16_t* aMsg) { +#ifdef NS_DEBUG + printf("SendOperationListener::OnStatus()\n"); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnSendNotPerformed(const char* aMsgID, + nsresult aStatus) { + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnStopSending(const char* aMsgID, nsresult aStatus, + const char16_t* aMsg, + nsIFile* returnFile) { + if (mSendLater && !mSendLater->OnSendStepFinished(aStatus)) + mSendLater = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnTransportSecurityError( + const char* msgID, nsresult status, nsITransportSecurityInfo* secInfo, + nsACString const& location) { + return NS_OK; +} + +// nsIMsgCopyServiceListener + +NS_IMETHODIMP +SendOperationListener::OnStartCopy(void) { return NS_OK; } + +NS_IMETHODIMP +SendOperationListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) { + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::SetMessageKey(nsMsgKey aKey) { + MOZ_ASSERT_UNREACHABLE("SendOperationListener::SetMessageKey()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SendOperationListener::GetMessageId(nsACString& messageId) { + MOZ_ASSERT_UNREACHABLE("SendOperationListener::GetMessageId()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SendOperationListener::OnStopCopy(nsresult aStatus) { + if (mSendLater) { + mSendLater->OnCopyStepFinished(aStatus); + mSendLater = nullptr; + } + + return NS_OK; +} + +nsresult nsMsgSendLater::CompleteMailFileSend() { + // get the identity from the key + // if no key, or we fail to find the identity + // use the default identity on the default account + nsCOMPtr<nsIMsgIdentity> identity; + nsresult rv = GetIdentityFromKey(mIdentityKey, getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + if (!identity) return NS_ERROR_UNEXPECTED; + + // If for some reason the tmp file didn't get created, we've failed here + bool created; + mTempFile->Exists(&created); + if (!created) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgCompFields> compFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSend> pMsgSend = + do_CreateInstance("@mozilla.org/messengercompose/send;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Since we have already parsed all of the headers, we are simply going to + // set the composition fields and move on. + nsCString author; + mMessage->GetAuthor(getter_Copies(author)); + + nsMsgCompFields* fields = (nsMsgCompFields*)compFields.get(); + + fields->SetFrom(author.get()); + + if (m_to) { + fields->SetTo(m_to); + } + + if (m_bcc) { + fields->SetBcc(m_bcc); + } + + if (m_fcc) { + fields->SetFcc(m_fcc); + } + + if (m_newsgroups) fields->SetNewsgroups(m_newsgroups); + +#if 0 + // needs cleanup. Is this needed? + if (m_newshost) + fields->SetNewspostUrl(m_newshost); +#endif + + // Create the listener for the send operation... + RefPtr<SendOperationListener> sendListener = new SendOperationListener(this); + + RefPtr<mozilla::dom::Promise> promise; + rv = pMsgSend->SendMessageFile( + identity, mAccountKey, + compFields, // nsIMsgCompFields *fields, + mTempFile, // nsIFile *sendFile, + true, // bool deleteSendFileOnCompletion, + false, // bool digest_p, + nsIMsgSend::nsMsgSendUnsent, // nsMsgDeliverMode mode, + nullptr, // nsIMsgDBHdr *msgToReplace, + sendListener, mFeedback, nullptr, getter_AddRefs(promise)); + return rv; +} + +nsresult nsMsgSendLater::StartNextMailFileSend(nsresult prevStatus) { + if (mTotalSendCount >= (uint32_t)mMessagesToSend.Count()) { + // Notify that this message has finished being sent. + NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, + 100); + + // EndSendMessages resets everything for us + EndSendMessages(prevStatus, nullptr, mTotalSendCount, + mTotalSentSuccessfully); + + // XXX Should we be releasing references so that we don't hold onto items + // unnecessarily. + return NS_OK; + } + + // If we've already sent a message, and are sending more, send out a progress + // update with 100% for both send and copy as we must have finished by now. + if (mTotalSendCount > 0) { + NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, + 100); + } + + mMessage = mMessagesToSend[mTotalSendCount++]; + + if (!mMessageFolder) return NS_ERROR_UNEXPECTED; + + nsCString messageURI; + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + folder->GetUriForMsg(mMessage, messageURI); + + rv = nsMsgCreateTempFile("nsqmail.tmp", getter_AddRefs(mTempFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMessageService> messageService; + rv = GetMessageServiceFromURI(messageURI, getter_AddRefs(messageService)); + if (NS_FAILED(rv) && !messageService) return NS_ERROR_FACTORY_NOT_LOADED; + + nsCString identityKey; + rv = mMessage->GetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIdentity> identity; + rv = GetIdentityFromKey(identityKey.get(), getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + if (!identity) return NS_ERROR_UNEXPECTED; + + // Notify that we're just about to start sending this message + NotifyListenersOnMessageStartSending(mTotalSendCount, mMessagesToSend.Count(), + identity); + + // Setup what we need to parse the data stream correctly + m_inhead = true; + m_headersFP = 0; + m_headersPosition = 0; + m_bytesRead = 0; + m_position = 0; + m_flagsPosition = 0; + m_headersSize = 0; + PR_FREEIF(mLeftoverBuffer); + + // Now, get our stream listener interface and plug it into the LoadMessage + // operation + rv = messageService->LoadMessage(messageURI, + static_cast<nsIStreamListener*>(this), + nullptr, nullptr, false); + + return rv; +} + +NS_IMETHODIMP +nsMsgSendLater::GetUnsentMessagesFolder(nsIMsgIdentity* aIdentity, + nsIMsgFolder** aFolder) { + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder); + if (!folder) { + nsCString uri; + GetFolderURIFromUserPrefs(nsIMsgSend::nsMsgQueueForLater, aIdentity, uri); + rv = LocateMessageFolder(aIdentity, nsIMsgSend::nsMsgQueueForLater, + uri.get(), getter_AddRefs(folder)); + mMessageFolder = do_GetWeakReference(folder); + if (!mMessageFolder) return NS_ERROR_FAILURE; + } + if (folder) folder.forget(aFolder); + return rv; +} + +NS_IMETHODIMP +nsMsgSendLater::HasUnsentMessages(nsIMsgIdentity* aIdentity, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + nsresult rv; + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgAccount>> accounts; + rv = accountManager->GetAccounts(accounts); + NS_ENSURE_SUCCESS(rv, rv); + + if (accounts.IsEmpty()) + return NS_OK; // no account set up -> no unsent messages + + // XXX This code should be set up for multiple unsent folders, however we + // don't support that at the moment, so for now just assume one folder. + if (!mMessageFolder) { + nsCOMPtr<nsIMsgFolder> folder; + rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(folder)); + // There doesn't have to be a nsMsgQueueForLater flagged folder. + if (NS_FAILED(rv) || !folder) return NS_OK; + } + rv = ReparseDBIfNeeded(nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t totalMessages; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->GetTotalMessages(false, &totalMessages); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = totalMessages > 0; + return NS_OK; +} + +// +// To really finalize this capability, we need to have the ability to get +// the message from the mail store in a stream for processing. The flow +// would be something like this: +// +// foreach (message in Outbox folder) +// get stream of Nth message +// if (not done with headers) +// Tack on to current buffer of headers +// when done with headers +// BuildHeaders() +// Write Headers to Temp File +// after done with headers +// write rest of message body to temp file +// +// when done with the message +// do send operation +// +// when send is complete +// Copy from Outbox to FCC folder +// Delete from Outbox folder +// +// +NS_IMETHODIMP +nsMsgSendLater::SendUnsentMessages(nsIMsgIdentity* aIdentity) { + return InternalSendMessages(true, aIdentity); +} + +// Returns NS_OK if the db is OK, an error otherwise, e.g., we had to reparse. +nsresult nsMsgSendLater::ReparseDBIfNeeded(nsIUrlListener* aListener) { + // This will kick off a reparse, if needed. So the next time we check if + // there are unsent messages, the db will be up to date. + nsCOMPtr<nsIMsgDatabase> unsentDB; + nsresult rv; + nsCOMPtr<nsIMsgLocalMailFolder> locFolder( + do_QueryReferent(mMessageFolder, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return locFolder->GetDatabaseWithReparse(aListener, nullptr, + getter_AddRefs(unsentDB)); +} + +nsresult nsMsgSendLater::InternalSendMessages(bool aUserInitiated, + nsIMsgIdentity* aIdentity) { + if (WeAreOffline()) return NS_MSG_ERROR_OFFLINE; + + // Protect against being called whilst we're already sending. + if (mSendingMessages) { + NS_ERROR("nsMsgSendLater is already sending messages"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + + // XXX This code should be set up for multiple unsent folders, however we + // don't support that at the moment, so for now just assume one folder. + if (!mMessageFolder) { + nsCOMPtr<nsIMsgFolder> folder; + rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(folder)); + if (NS_FAILED(rv) || !folder) return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIMsgDatabase> unsentDB; + // Remember these in case we need to reparse the db. + mUserInitiated = aUserInitiated; + mIdentity = aIdentity; + rv = ReparseDBIfNeeded(this); + NS_ENSURE_SUCCESS(rv, rv); + mIdentity = nullptr; // don't hold onto the identity since we're a service. + + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgEnumerator> enumerator; + rv = folder->GetMessages(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + // Build mMessagesToSend array. + bool hasMoreElements = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && + hasMoreElements) { + nsCOMPtr<nsIMsgDBHdr> messageHeader; + rv = enumerator->GetNext(getter_AddRefs(messageHeader)); + if (NS_SUCCEEDED(rv)) { + if (aUserInitiated) { + // If the user initiated the send, add all messages + mMessagesToSend.AppendObject(messageHeader); + } else { + // Else just send those that are NOT marked as Queued. + uint32_t flags; + rv = messageHeader->GetFlags(&flags); + if (NS_SUCCEEDED(rv) && !(flags & nsMsgMessageFlags::Queued)) + mMessagesToSend.AppendObject(messageHeader); + } + } + } + + // We're now sending messages so its time to signal that and reset our counts. + mSendingMessages = true; + mTotalSentSuccessfully = 0; + mTotalSendCount = 0; + + // Notify the listeners that we are starting a send. + NotifyListenersOnStartSending(mMessagesToSend.Count()); + + return StartNextMailFileSend(NS_OK); +} + +nsresult nsMsgSendLater::SetOrigMsgDisposition() { + if (!mMessage) return NS_ERROR_NULL_POINTER; + + // We're finished sending a queued message. We need to look at mMessage + // and see if we need to set replied/forwarded + // flags for the original message that this message might be a reply to + // or forward of. + nsCString originalMsgURIs; + nsCString queuedDisposition; + mMessage->GetStringProperty(ORIG_URI_PROPERTY, originalMsgURIs); + mMessage->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, queuedDisposition); + if (!queuedDisposition.IsEmpty()) { + nsTArray<nsCString> uriArray; + ParseString(originalMsgURIs, ',', uriArray); + for (uint32_t i = 0; i < uriArray.Length(); i++) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgDBHdrFromURI(uriArray[i], getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + if (msgHdr) { + // get the folder for the message resource + nsCOMPtr<nsIMsgFolder> msgFolder; + msgHdr->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) { + nsMsgDispositionState dispositionSetting = + nsIMsgFolder::nsMsgDispositionState_None; + if (queuedDisposition.EqualsLiteral("replied")) + dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied; + else if (queuedDisposition.EqualsLiteral("forwarded")) + dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded; + else if (queuedDisposition.EqualsLiteral("redirected")) + dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Redirected; + + msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting); + } + } + } + } + return NS_OK; +} + +nsresult nsMsgSendLater::DeleteCurrentMessage() { + if (!mMessage) { + NS_ERROR("nsMsgSendLater: Attempt to delete an already deleted message"); + return NS_OK; + } + + // Get the composition fields interface + if (!mMessageFolder) return NS_ERROR_UNEXPECTED; + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = folder->DeleteMessages({&*mMessage}, nullptr, true, false, nullptr, + false /*allowUndo*/); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + // Null out the message so we don't try and delete it again. + mMessage = nullptr; + + return NS_OK; +} + +// +// This function parses the headers, and also deletes from the header block +// any headers which should not be delivered in mail, regardless of whether +// they were present in the queue file. Such headers include: BCC, FCC, +// Sender, X-Mozilla-Status, X-Mozilla-News-Host, and Content-Length. +// (Content-Length is for the disk file only, and must not be allowed to +// escape onto the network, since it depends on the local linebreak +// representation. Arguably, we could allow Lines to escape, but it's not +// required by NNTP.) +// +nsresult nsMsgSendLater::BuildHeaders() { + char* buf = m_headers; + char* buf_end = buf + m_headersFP; + + PR_FREEIF(m_to); + PR_FREEIF(m_bcc); + PR_FREEIF(m_newsgroups); + PR_FREEIF(m_newshost); + PR_FREEIF(m_fcc); + PR_FREEIF(mIdentityKey); + PR_FREEIF(mAccountKey); + m_flags = 0; + + while (buf < buf_end) { + bool prune_p = false; + bool do_flags_p = false; + char* colon = PL_strchr(buf, ':'); + char* end; + char* value = 0; + char** header = 0; + char* header_start = buf; + + if (!colon) break; + + end = colon; + while (end > buf && (*end == ' ' || *end == '\t')) end--; + + switch (buf[0]) { + case 'B': + case 'b': + if (!PL_strncasecmp("BCC", buf, end - buf)) { + header = &m_bcc; + } + break; + case 'C': + case 'c': + if (!PL_strncasecmp("CC", buf, end - buf)) + header = &m_to; + else if (!PL_strncasecmp(HEADER_CONTENT_LENGTH, buf, end - buf)) + prune_p = true; + break; + case 'F': + case 'f': + if (!PL_strncasecmp("FCC", buf, end - buf)) { + header = &m_fcc; + prune_p = true; + } + break; + case 'L': + case 'l': + if (!PL_strncasecmp("Lines", buf, end - buf)) prune_p = true; + break; + case 'N': + case 'n': + if (!PL_strncasecmp("Newsgroups", buf, end - buf)) + header = &m_newsgroups; + break; + case 'S': + case 's': + if (!PL_strncasecmp("Sender", buf, end - buf)) prune_p = true; + break; + case 'T': + case 't': + if (!PL_strncasecmp("To", buf, end - buf)) header = &m_to; + break; + case 'X': + case 'x': { + if (buf + strlen(HEADER_X_MOZILLA_STATUS2) == end && + !PL_strncasecmp(HEADER_X_MOZILLA_STATUS2, buf, end - buf)) + prune_p = true; + else if (buf + strlen(HEADER_X_MOZILLA_STATUS) == end && + !PL_strncasecmp(HEADER_X_MOZILLA_STATUS, buf, end - buf)) + prune_p = do_flags_p = true; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_DRAFT_INFO, buf, end - buf)) + prune_p = true; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf)) + prune_p = true; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_NEWSHOST, buf, end - buf)) { + prune_p = true; + header = &m_newshost; + } else if (!PL_strncasecmp(HEADER_X_MOZILLA_IDENTITY_KEY, buf, + end - buf)) { + prune_p = true; + header = &mIdentityKey; + } else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf, + end - buf)) { + prune_p = true; + header = &mAccountKey; + } + break; + } + } + + buf = colon + 1; + while (*buf == ' ' || *buf == '\t') buf++; + + value = buf; + + SEARCH_NEWLINE: + while (*buf != 0 && *buf != '\r' && *buf != '\n') buf++; + + if (buf + 1 >= buf_end) + ; + // If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. + else if (buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') && + (buf[2] == ' ' || buf[2] == '\t')) { + buf += 3; + goto SEARCH_NEWLINE; + } + // If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate + // the header either. + else if ((buf[0] == '\r' || buf[0] == '\n') && + (buf[1] == ' ' || buf[1] == '\t')) { + buf += 2; + goto SEARCH_NEWLINE; + } + + if (header) { + int L = buf - value; + if (*header) { + char* newh = (char*)PR_Realloc((*header), PL_strlen(*header) + L + 10); + if (!newh) return NS_ERROR_OUT_OF_MEMORY; + *header = newh; + newh = (*header) + PL_strlen(*header); + *newh++ = ','; + *newh++ = ' '; + memcpy(newh, value, L); + newh[L] = 0; + } else { + *header = (char*)PR_Malloc(L + 1); + if (!*header) return NS_ERROR_OUT_OF_MEMORY; + memcpy((*header), value, L); + (*header)[L] = 0; + } + } else if (do_flags_p) { + char* s = value; + PR_ASSERT(*s != ' ' && *s != '\t'); + NS_ASSERTION(MsgIsHex(s, 4), "Expected 4 hex digits for flags."); + m_flags = MsgUnhex(s, 4); + } + + if (*buf == '\r' || *buf == '\n') { + if (*buf == '\r' && buf[1] == '\n') buf++; + buf++; + } + + if (prune_p) { + char* to = header_start; + char* from = buf; + while (from < buf_end) *to++ = *from++; + buf = header_start; + buf_end = to; + m_headersFP = buf_end - m_headers; + } + } + + m_headers[m_headersFP++] = '\r'; + m_headers[m_headersFP++] = '\n'; + + // Now we have parsed out all of the headers we need and we + // can proceed. + return NS_OK; +} + +nsresult DoGrowBuffer(int32_t desired_size, int32_t element_size, + int32_t quantum, char** buffer, int32_t* size) { + if (*size <= desired_size) { + char* new_buf; + int32_t increment = desired_size - *size; + if (increment < quantum) // always grow by a minimum of N bytes + increment = quantum; + + new_buf = + (*buffer ? (char*)PR_Realloc(*buffer, (*size + increment) * + (element_size / sizeof(char))) + : (char*)PR_Malloc((*size + increment) * + (element_size / sizeof(char)))); + if (!new_buf) return NS_ERROR_OUT_OF_MEMORY; + *buffer = new_buf; + *size += increment; + } + return NS_OK; +} + +#define do_grow_headers(desired_size) \ + (((desired_size) >= m_headersSize) \ + ? DoGrowBuffer((desired_size), sizeof(char), 1024, &m_headers, \ + &m_headersSize) \ + : NS_OK) + +nsresult nsMsgSendLater::DeliverQueuedLine(const char* line, int32_t length) { + int32_t flength = length; + + m_bytesRead += length; + + // convert existing newline to CRLF + // Don't need this because the calling routine is taking care of it. + // if (length > 0 && (line[length-1] == '\r' || + // (line[length-1] == '\n' && (length < 2 || line[length-2] != '\r')))) + // { + // line[length-1] = '\r'; + // line[length++] = '\n'; + // } + // + // + // We are going to check if we are looking at a "From - " line. If so, + // then just eat it and return NS_OK + // + if (!PL_strncasecmp(line, "From - ", 7)) return NS_OK; + + if (m_inhead) { + if (m_headersPosition == 0) { + // This line is the first line in a header block. + // Remember its position. + m_headersPosition = m_position; + + // Also, since we're now processing the headers, clear out the + // slots which we will parse data into, so that the values that + // were used the last time around do not persist. + + // We must do that here, and not in the previous clause of this + // `else' (the "I've just seen a `From ' line clause") because + // that clause happens before delivery of the previous message is + // complete, whereas this clause happens after the previous msg + // has been delivered. If we did this up there, then only the + // last message in the folder would ever be able to be both + // mailed and posted (or fcc'ed.) + PR_FREEIF(m_to); + PR_FREEIF(m_bcc); + PR_FREEIF(m_newsgroups); + PR_FREEIF(m_newshost); + PR_FREEIF(m_fcc); + PR_FREEIF(mIdentityKey); + } + + if (line[0] == '\r' || line[0] == '\n' || line[0] == 0) { + // End of headers. Now parse them; open the temp file; + // and write the appropriate subset of the headers out. + m_inhead = false; + + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), + mTempFile, -1, 00600); + if (NS_FAILED(rv)) return NS_MSG_ERROR_WRITING_FILE; + + nsresult status = BuildHeaders(); + if (NS_FAILED(status)) return status; + + uint32_t n; + rv = mOutFile->Write(m_headers, m_headersFP, &n); + if (NS_FAILED(rv) || n != (uint32_t)m_headersFP) + return NS_MSG_ERROR_WRITING_FILE; + } else { + // Otherwise, this line belongs to a header. So append it to the + // header data. + + if (!PL_strncasecmp(line, HEADER_X_MOZILLA_STATUS, + PL_strlen(HEADER_X_MOZILLA_STATUS))) + // Notice the position of the flags. + m_flagsPosition = m_position; + else if (m_headersFP == 0) + m_flagsPosition = 0; + + nsresult status = do_grow_headers(length + m_headersFP + 10); + if (NS_FAILED(status)) return status; + + memcpy(m_headers + m_headersFP, line, length); + m_headersFP += length; + } + } else { + // This is a body line. Write it to the file. + PR_ASSERT(mOutFile); + if (mOutFile) { + uint32_t wrote; + nsresult rv = mOutFile->Write(line, length, &wrote); + if (NS_FAILED(rv) || wrote < (uint32_t)length) + return NS_MSG_ERROR_WRITING_FILE; + } + } + + m_position += flength; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::AddListener(nsIMsgSendLaterListener* aListener) { + NS_ENSURE_ARG_POINTER(aListener); + mListenerArray.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::RemoveListener(nsIMsgSendLaterListener* aListener) { + NS_ENSURE_ARG_POINTER(aListener); + return mListenerArray.RemoveElement(aListener) ? NS_OK : NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP +nsMsgSendLater::GetSendingMessages(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mSendingMessages; + return NS_OK; +} + +#define NOTIFY_LISTENERS(propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener>>::ForwardIterator iter( \ + mListenerArray); \ + nsCOMPtr<nsIMsgSendLaterListener> listener; \ + while (iter.HasMore()) { \ + listener = iter.GetNext(); \ + listener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +void nsMsgSendLater::NotifyListenersOnStartSending( + uint32_t aTotalMessageCount) { + NOTIFY_LISTENERS(OnStartSending, (aTotalMessageCount)); +} + +void nsMsgSendLater::NotifyListenersOnMessageStartSending( + uint32_t aCurrentMessage, uint32_t aTotalMessage, + nsIMsgIdentity* aIdentity) { + NOTIFY_LISTENERS(OnMessageStartSending, + (aCurrentMessage, aTotalMessage, mMessage, aIdentity)); +} + +void nsMsgSendLater::NotifyListenersOnProgress(uint32_t aCurrentMessage, + uint32_t aTotalMessage, + uint32_t aSendPercent, + uint32_t aCopyPercent) { + NOTIFY_LISTENERS(OnMessageSendProgress, (aCurrentMessage, aTotalMessage, + aSendPercent, aCopyPercent)); +} + +void nsMsgSendLater::NotifyListenersOnMessageSendError(uint32_t aCurrentMessage, + nsresult aStatus, + const char16_t* aMsg) { + NOTIFY_LISTENERS(OnMessageSendError, + (aCurrentMessage, mMessage, aStatus, aMsg)); +} + +/** + * This function is called to end sending of messages, it resets the send later + * system and notifies the relevant parties that we have finished. + */ +void nsMsgSendLater::EndSendMessages(nsresult aStatus, const char16_t* aMsg, + uint32_t aTotalTried, + uint32_t aSuccessful) { + // Catch-all, we may have had an issue sending, so we may not be calling + // StartNextMailFileSend to fully finish the sending. Therefore set + // mSendingMessages to false here so that we don't think we're still trying + // to send messages + mSendingMessages = false; + + // Clear out our array of messages. + mMessagesToSend.Clear(); + + nsresult rv; + nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv); + NS_ENSURE_SUCCESS(rv, ); + // We don't need to keep hold of the database now we've finished sending. + (void)folder->SetMsgDatabase(nullptr); + + // or temp file or output stream + mTempFile = nullptr; + mOutFile = nullptr; + + NOTIFY_LISTENERS(OnStopSending, (aStatus, aMsg, aTotalTried, aSuccessful)); + + // If we've got a shutdown listener, notify it that we've finished. + if (mShutdownListener) { + mShutdownListener->OnStopRunningUrl(nullptr, NS_OK); + mShutdownListener = nullptr; + } +} + +/** + * Called when the send part of sending a message is finished. This will set up + * for the next step or "end" depending on the status. + * + * @param aStatus The success or fail result of the send step. + * @return True if the copy process will continue, false otherwise. + */ +bool nsMsgSendLater::OnSendStepFinished(nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { + SetOrigMsgDisposition(); + DeleteCurrentMessage(); + + // Send finished, so that is now 100%, copy to proceed... + NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 0); + + ++mTotalSentSuccessfully; + return true; + } else { + // XXX we don't currently get a message string from the send service. + NotifyListenersOnMessageSendError(mTotalSendCount, aStatus, nullptr); + nsresult rv = StartNextMailFileSend(aStatus); + // if this is the last message we're sending, we should report + // the status failure. + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); + } + return false; +} + +/** + * Called when the copy part of sending a message is finished. This will send + * the next message or handle failure as appropriate. + * + * @param aStatus The success or fail result of the copy step. + */ +void nsMsgSendLater::OnCopyStepFinished(nsresult aStatus) { + // Regardless of the success of the copy we will still keep trying + // to send the rest... + nsresult rv = StartNextMailFileSend(aStatus); + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); +} + +// XXX todo +// maybe this should just live in the account manager? +nsresult nsMsgSendLater::GetIdentityFromKey(const char* aKey, + nsIMsgIdentity** aIdentity) { + NS_ENSURE_ARG_POINTER(aIdentity); + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (aKey) { + nsTArray<RefPtr<nsIMsgIdentity>> identities; + if (NS_SUCCEEDED(accountManager->GetAllIdentities(identities))) { + for (auto lookupIdentity : identities) { + nsCString key; + lookupIdentity->GetKey(key); + if (key.Equals(aKey)) { + lookupIdentity.forget(aIdentity); + return NS_OK; + } + } + } + } + + // If no aKey, or we failed to find the identity from the key + // use the identity from the default account. + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, rv); + + if (defaultAccount) + rv = defaultAccount->GetDefaultIdentity(aIdentity); + else + *aIdentity = nullptr; + + return rv; +} + +nsresult nsMsgSendLater::StartTimer() { + // No need to trigger if timer is already set + if (mTimerSet) return NS_OK; + + // XXX only trigger for non-queued headers + + // Items from this function return NS_OK because the callee won't care about + // the result anyway. + nsresult rv; + if (!mTimer) { + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + } + + rv = mTimer->Init(static_cast<nsIObserver*>(this), kInitialMessageSendTime, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, NS_OK); + + mTimerSet = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderAdded(nsIMsgFolder* /*parent*/, + nsIMsgFolder* /*child*/) { + return StartTimer(); +} + +NS_IMETHODIMP +nsMsgSendLater::OnMessageAdded(nsIMsgFolder* /*parent*/, nsIMsgDBHdr* /*msg*/) { + return StartTimer(); +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderRemoved(nsIMsgFolder* /*parent*/, + nsIMsgFolder* /*child*/) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnMessageRemoved(nsIMsgFolder* /*parent*/, + nsIMsgDBHdr* /*msg*/) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderPropertyChanged(nsIMsgFolder* aFolder, + const nsACString& aProperty, + const nsACString& aOldValue, + const nsACString& aNewValue) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderIntPropertyChanged(nsIMsgFolder* aFolder, + const nsACString& aProperty, + int64_t aOldValue, + int64_t aNewValue) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderBoolPropertyChanged(nsIMsgFolder* aFolder, + const nsACString& aProperty, + bool aOldValue, bool aNewValue) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderUnicharPropertyChanged(nsIMsgFolder* aFolder, + const nsACString& aProperty, + const nsAString& aOldValue, + const nsAString& aNewValue) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderPropertyFlagChanged(nsIMsgDBHdr* aMsg, + const nsACString& aProperty, + uint32_t aOldValue, + uint32_t aNewValue) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnFolderEvent(nsIMsgFolder* aFolder, const nsACString& aEvent) { + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::GetNeedsToRunTask(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mSendingMessages; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::DoShutdownTask(nsIUrlListener* aListener, nsIMsgWindow* aWindow, + bool* aResult) { + if (mTimer) mTimer->Cancel(); + // If we're already sending messages, nothing to do, but save the shutdown + // listener until we've finished. + if (mSendingMessages) { + mShutdownListener = aListener; + return NS_OK; + } + // Else we have pending messages, we need to throw up a dialog to find out + // if to send them or not. + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgSendLater::GetCurrentTaskName(nsAString& aResult) { + // XXX Bug 440794 will localize this, left as non-localized whilst we decide + // on the actual strings and try out the UI. + aResult = u"Sending Messages"_ns; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgSendLater.h b/comm/mailnews/compose/src/nsMsgSendLater.h new file mode 100644 index 0000000000..fcd8c53e38 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgSendLater.h @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgSendLater_H_ +#define _nsMsgSendLater_H_ + +#include "nsCOMArray.h" +#include "nsIMsgFolder.h" +#include "nsIMsgSendListener.h" +#include "nsIMsgSendLaterListener.h" +#include "nsIMsgSendLater.h" +#include "nsIMsgStatusFeedback.h" +#include "nsTObserverArray.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsIMsgShutdown.h" +#include "nsIWeakReferenceUtils.h" + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the send operation. We have to create this +// class to listen for message send completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +class nsMsgSendLater; + +class SendOperationListener : public nsIMsgSendListener, + public nsIMsgCopyServiceListener { + public: + explicit SendOperationListener(nsMsgSendLater* aSendLater); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSENDLISTENER + NS_DECL_NSIMSGCOPYSERVICELISTENER + + private: + virtual ~SendOperationListener(); + RefPtr<nsMsgSendLater> mSendLater; +}; + +class nsMsgSendLater : public nsIMsgSendLater, + public nsIFolderListener, + public nsIObserver, + public nsIUrlListener, + public nsIMsgShutdownTask + +{ + public: + nsMsgSendLater(); + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSENDLATER + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIOBSERVER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGSHUTDOWNTASK + + // Methods needed for implementing interface... + nsresult StartNextMailFileSend(nsresult prevStatus); + nsresult CompleteMailFileSend(); + + nsresult DeleteCurrentMessage(); + nsresult SetOrigMsgDisposition(); + // Necessary for creating a valid list of recipients + nsresult BuildHeaders(); + nsresult DeliverQueuedLine(const char* line, int32_t length); + nsresult RebufferLeftovers(char* startBuf, uint32_t aLen); + nsresult BuildNewBuffer(const char* aBuf, uint32_t aCount, + uint32_t* totalBufSize); + + // methods for listener array processing... + void NotifyListenersOnStartSending(uint32_t aTotalMessageCount); + void NotifyListenersOnMessageStartSending(uint32_t aCurrentMessage, + uint32_t aTotalMessage, + nsIMsgIdentity* aIdentity); + void NotifyListenersOnProgress(uint32_t aCurrentMessage, + uint32_t aTotalMessage, uint32_t aSendPercent, + uint32_t aCopyPercent); + void NotifyListenersOnMessageSendError(uint32_t aCurrentMessage, + nsresult aStatus, + const char16_t* aMsg); + void EndSendMessages(nsresult aStatus, const char16_t* aMsg, + uint32_t aTotalTried, uint32_t aSuccessful); + + bool OnSendStepFinished(nsresult aStatus); + void OnCopyStepFinished(nsresult aStatus); + + private: + // counters and things for enumeration + uint32_t mTotalSentSuccessfully; + uint32_t mTotalSendCount; + nsCOMArray<nsIMsgDBHdr> mMessagesToSend; + nsWeakPtr mMessageFolder; + nsCOMPtr<nsIMsgStatusFeedback> mFeedback; + + virtual ~nsMsgSendLater(); + nsresult GetIdentityFromKey(const char* aKey, nsIMsgIdentity** aIdentity); + nsresult ReparseDBIfNeeded(nsIUrlListener* aListener); + nsresult InternalSendMessages(bool aUserInitiated, nsIMsgIdentity* aIdentity); + nsresult StartTimer(); + + nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener> > mListenerArray; + nsCOMPtr<nsIMsgDBHdr> mMessage; + nsCOMPtr<nsITimer> mTimer; + bool mTimerSet; + nsCOMPtr<nsIUrlListener> mShutdownListener; + + // + // File output stuff... + // + nsCOMPtr<nsIFile> mTempFile; + nsCOMPtr<nsIOutputStream> mOutFile; + + // For building headers and stream parsing... + char* m_to; + char* m_bcc; + char* m_fcc; + char* m_newsgroups; + char* m_newshost; + char* m_headers; + int32_t m_flags; + int32_t m_headersFP; + bool m_inhead; + int32_t m_headersPosition; + int32_t m_bytesRead; + int32_t m_position; + int32_t m_flagsPosition; + int32_t m_headersSize; + char* mLeftoverBuffer; + char* mIdentityKey; + char* mAccountKey; + + bool mSendingMessages; + bool mUserInitiated; + nsCOMPtr<nsIMsgIdentity> mIdentity; +}; + +#endif /* _nsMsgSendLater_H_ */ diff --git a/comm/mailnews/compose/src/nsMsgSendReport.cpp b/comm/mailnews/compose/src/nsMsgSendReport.cpp new file mode 100644 index 0000000000..5cab724810 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgSendReport.cpp @@ -0,0 +1,385 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgSendReport.h" + +#include "msgCore.h" +#include "nsIMsgCompose.h" +#include "nsMsgPrompts.h" +#include "nsError.h" +#include "nsComposeStrings.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Components.h" + +NS_IMPL_ISUPPORTS(nsMsgProcessReport, nsIMsgProcessReport) + +nsMsgProcessReport::nsMsgProcessReport() { Reset(); } + +nsMsgProcessReport::~nsMsgProcessReport() {} + +/* attribute boolean proceeded; */ +NS_IMETHODIMP nsMsgProcessReport::GetProceeded(bool* aProceeded) { + NS_ENSURE_ARG_POINTER(aProceeded); + *aProceeded = mProceeded; + return NS_OK; +} +NS_IMETHODIMP nsMsgProcessReport::SetProceeded(bool aProceeded) { + mProceeded = aProceeded; + return NS_OK; +} + +/* attribute nsresult error; */ +NS_IMETHODIMP nsMsgProcessReport::GetError(nsresult* aError) { + NS_ENSURE_ARG_POINTER(aError); + *aError = mError; + return NS_OK; +} +NS_IMETHODIMP nsMsgProcessReport::SetError(nsresult aError) { + mError = aError; + return NS_OK; +} + +/* attribute wstring message; */ +NS_IMETHODIMP nsMsgProcessReport::GetMessage(char16_t** aMessage) { + NS_ENSURE_ARG_POINTER(aMessage); + *aMessage = ToNewUnicode(mMessage); + return NS_OK; +} +NS_IMETHODIMP nsMsgProcessReport::SetMessage(const char16_t* aMessage) { + mMessage = aMessage; + return NS_OK; +} + +/* void Reset (); */ +NS_IMETHODIMP nsMsgProcessReport::Reset() { + mProceeded = false; + mError = NS_OK; + mMessage.Truncate(); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsMsgSendReport, nsIMsgSendReport) + +nsMsgSendReport::nsMsgSendReport() { + uint32_t i; + for (i = 0; i <= SEND_LAST_PROCESS; i++) + mProcessReport[i] = new nsMsgProcessReport(); + + Reset(); +} + +nsMsgSendReport::~nsMsgSendReport() { + uint32_t i; + for (i = 0; i <= SEND_LAST_PROCESS; i++) mProcessReport[i] = nullptr; +} + +/* attribute long currentProcess; */ +NS_IMETHODIMP nsMsgSendReport::GetCurrentProcess(int32_t* aCurrentProcess) { + NS_ENSURE_ARG_POINTER(aCurrentProcess); + *aCurrentProcess = mCurrentProcess; + return NS_OK; +} +NS_IMETHODIMP nsMsgSendReport::SetCurrentProcess(int32_t aCurrentProcess) { + if (aCurrentProcess < 0 || aCurrentProcess > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + mCurrentProcess = aCurrentProcess; + if (mProcessReport[mCurrentProcess]) + mProcessReport[mCurrentProcess]->SetProceeded(true); + + return NS_OK; +} + +/* attribute long deliveryMode; */ +NS_IMETHODIMP nsMsgSendReport::GetDeliveryMode(int32_t* aDeliveryMode) { + NS_ENSURE_ARG_POINTER(aDeliveryMode); + *aDeliveryMode = mDeliveryMode; + return NS_OK; +} +NS_IMETHODIMP nsMsgSendReport::SetDeliveryMode(int32_t aDeliveryMode) { + mDeliveryMode = aDeliveryMode; + return NS_OK; +} + +/* void Reset (); */ +NS_IMETHODIMP nsMsgSendReport::Reset() { + uint32_t i; + for (i = 0; i <= SEND_LAST_PROCESS; i++) + if (mProcessReport[i]) mProcessReport[i]->Reset(); + + mCurrentProcess = 0; + mDeliveryMode = 0; + mAlreadyDisplayReport = false; + + return NS_OK; +} + +/* void setProceeded (in long process, in boolean proceeded); */ +NS_IMETHODIMP nsMsgSendReport::SetProceeded(int32_t process, bool proceeded) { + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) process = mCurrentProcess; + + if (!mProcessReport[process]) return NS_ERROR_NOT_INITIALIZED; + + return mProcessReport[process]->SetProceeded(proceeded); +} + +/* void setError (in long process, in nsresult error, in boolean + * overwriteError); */ +NS_IMETHODIMP nsMsgSendReport::SetError(int32_t process, nsresult newError, + bool overwriteError) { + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) { + if (mCurrentProcess == process_Current) + // We don't know what we're currently trying to do + return NS_ERROR_ILLEGAL_VALUE; + + process = mCurrentProcess; + } + + if (!mProcessReport[process]) return NS_ERROR_NOT_INITIALIZED; + + nsresult currError = NS_OK; + mProcessReport[process]->GetError(&currError); + if (overwriteError || NS_SUCCEEDED(currError)) + return mProcessReport[process]->SetError(newError); + else + return NS_OK; +} + +/* void setMessage (in long process, in wstring message, in boolean + * overwriteMessage); */ +NS_IMETHODIMP nsMsgSendReport::SetMessage(int32_t process, + const char16_t* message, + bool overwriteMessage) { + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) { + if (mCurrentProcess == process_Current) + // We don't know what we're currently trying to do + return NS_ERROR_ILLEGAL_VALUE; + + process = mCurrentProcess; + } + + if (!mProcessReport[process]) return NS_ERROR_NOT_INITIALIZED; + + nsString currMessage; + mProcessReport[process]->GetMessage(getter_Copies(currMessage)); + if (overwriteMessage || currMessage.IsEmpty()) + return mProcessReport[process]->SetMessage(message); + else + return NS_OK; +} + +/* nsIMsgProcessReport getProcessReport (in long process); */ +NS_IMETHODIMP nsMsgSendReport::GetProcessReport(int32_t process, + nsIMsgProcessReport** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) { + if (mCurrentProcess == process_Current) + // We don't know what we're currently trying to do + return NS_ERROR_ILLEGAL_VALUE; + + process = mCurrentProcess; + } + + NS_IF_ADDREF(*_retval = mProcessReport[process]); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSendReport::DisplayReport(mozIDOMWindowProxy* window, + bool showErrorOnly, + bool dontShowReportTwice, + nsresult* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + NS_ENSURE_TRUE(mCurrentProcess >= 0 && mCurrentProcess <= SEND_LAST_PROCESS, + NS_ERROR_NOT_INITIALIZED); + + nsresult currError = NS_OK; + mProcessReport[mCurrentProcess]->GetError(&currError); + *_retval = currError; + + if (dontShowReportTwice && mAlreadyDisplayReport) return NS_OK; + + if (showErrorOnly && NS_SUCCEEDED(currError)) return NS_OK; + + nsString currMessage; + mProcessReport[mCurrentProcess]->GetMessage(getter_Copies(currMessage)); + + nsresult rv; // don't step on currError. + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + if (NS_FAILED(rv)) { + // TODO need to display a generic hardcoded message + mAlreadyDisplayReport = true; + return NS_OK; + } + + nsString dialogTitle; + nsString dialogMessage; + + if (NS_SUCCEEDED(currError)) { + // TODO display a success error message + return NS_OK; + } + + // Do we have an explanation of the error? if no, try to build one... + if (currMessage.IsEmpty()) { +#ifdef __GNUC__ +// Temporary workaround until bug 783526 is fixed. +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wswitch" +#endif + switch (currError) { + case NS_BINDING_ABORTED: + case NS_MSG_UNABLE_TO_SEND_LATER: + case NS_MSG_UNABLE_TO_SAVE_DRAFT: + case NS_MSG_UNABLE_TO_SAVE_TEMPLATE: + // Ignore, don't need to repeat ourself. + break; + default: + const char* errorString = errorStringNameForErrorCode(currError); + nsMsgGetMessageByName(errorString, currMessage); + break; + } +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif + } + + if (mDeliveryMode == nsIMsgCompDeliverMode::Now || + mDeliveryMode == nsIMsgCompDeliverMode::SendUnsent) { + // SMTP is taking care of it's own error message and will return + // NS_ERROR_BUT_DONT_SHOW_ALERT as error code. In that case, we must not + // show an alert ourself. + if (currError == NS_ERROR_BUT_DONT_SHOW_ALERT) { + mAlreadyDisplayReport = true; + return NS_OK; + } + + bundle->GetStringFromName("sendMessageErrorTitle", dialogTitle); + + const char* preStrName = "sendFailed"; + bool askToGoBackToCompose = false; + switch (mCurrentProcess) { + case process_BuildMessage: + preStrName = "sendFailed"; + askToGoBackToCompose = false; + break; + case process_NNTP: + preStrName = "sendFailed"; + askToGoBackToCompose = false; + break; + case process_SMTP: + bool nntpProceeded; + mProcessReport[process_NNTP]->GetProceeded(&nntpProceeded); + if (nntpProceeded) + preStrName = "sendFailedButNntpOk"; + else + preStrName = "sendFailed"; + askToGoBackToCompose = false; + break; + case process_Copy: + preStrName = "failedCopyOperation"; + askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now); + break; + case process_FCC: + preStrName = "failedCopyOperation"; + askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now); + break; + } + bundle->GetStringFromName(preStrName, dialogMessage); + + // Do we already have an error message? + if (!askToGoBackToCompose && currMessage.IsEmpty()) { + // we don't have an error description but we can put a generic explanation + bundle->GetStringFromName("genericFailureExplanation", currMessage); + } + + if (!currMessage.IsEmpty()) { + // Don't need to repeat ourself! + if (!currMessage.Equals(dialogMessage)) { + if (!dialogMessage.IsEmpty()) dialogMessage.Append(char16_t('\n')); + dialogMessage.Append(currMessage); + } + } + + if (askToGoBackToCompose) { + bool oopsGiveMeBackTheComposeWindow = true; + nsString text1; + bundle->GetStringFromName("returnToComposeWindowQuestion", text1); + if (!dialogMessage.IsEmpty()) dialogMessage.AppendLiteral("\n"); + dialogMessage.Append(text1); + nsMsgAskBooleanQuestionByString(window, dialogMessage.get(), + &oopsGiveMeBackTheComposeWindow, + dialogTitle.get()); + if (!oopsGiveMeBackTheComposeWindow) *_retval = NS_OK; + } else + nsMsgDisplayMessageByString(window, dialogMessage.get(), + dialogTitle.get()); + } else { + const char* title; + const char* messageName; + + switch (mDeliveryMode) { + case nsIMsgCompDeliverMode::Later: + title = "sendLaterErrorTitle"; + messageName = "unableToSendLater"; + break; + + case nsIMsgCompDeliverMode::AutoSaveAsDraft: + case nsIMsgCompDeliverMode::SaveAsDraft: + title = "saveDraftErrorTitle"; + messageName = "unableToSaveDraft"; + break; + + case nsIMsgCompDeliverMode::SaveAsTemplate: + title = "saveTemplateErrorTitle"; + messageName = "unableToSaveTemplate"; + break; + + default: + /* This should never happen! */ + title = "sendMessageErrorTitle"; + messageName = "sendFailed"; + break; + } + + bundle->GetStringFromName(title, dialogTitle); + bundle->GetStringFromName(messageName, dialogMessage); + + // Do we have an error message... + if (currMessage.IsEmpty()) { + // we don't have an error description but we can put a generic explanation + bundle->GetStringFromName("genericFailureExplanation", currMessage); + } + + if (!currMessage.IsEmpty()) { + if (!dialogMessage.IsEmpty()) dialogMessage.Append(char16_t('\n')); + dialogMessage.Append(currMessage); + } + nsMsgDisplayMessageByString(window, dialogMessage.get(), dialogTitle.get()); + } + + mAlreadyDisplayReport = true; + return NS_OK; +} diff --git a/comm/mailnews/compose/src/nsMsgSendReport.h b/comm/mailnews/compose/src/nsMsgSendReport.h new file mode 100644 index 0000000000..0fcc2f1c9f --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgSendReport.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsMsgSendReport_h__ +#define __nsMsgSendReport_h__ + +#include "nsIMsgSendReport.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +class nsMsgProcessReport : public nsIMsgProcessReport { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPROCESSREPORT + + nsMsgProcessReport(); + + private: + virtual ~nsMsgProcessReport(); + bool mProceeded; + nsresult mError; + nsString mMessage; +}; + +class nsMsgSendReport : public nsIMsgSendReport { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSENDREPORT + + nsMsgSendReport(); + + protected: + virtual ~nsMsgSendReport(); + + private: +#define SEND_LAST_PROCESS process_FCC + nsCOMPtr<nsIMsgProcessReport> mProcessReport[SEND_LAST_PROCESS + 1]; + int32_t mDeliveryMode; + int32_t mCurrentProcess; + bool mAlreadyDisplayReport; +}; + +#endif diff --git a/comm/mailnews/compose/src/nsSmtpUrl.cpp b/comm/mailnews/compose/src/nsSmtpUrl.cpp new file mode 100644 index 0000000000..b9ac451650 --- /dev/null +++ b/comm/mailnews/compose/src/nsSmtpUrl.cpp @@ -0,0 +1,755 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" + +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsSmtpUrl.h" +#include "nsString.h" +#include "nsMsgUtils.h" +#include "nsIMimeConverter.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCRT.h" +#include "mozilla/Encoding.h" + +///////////////////////////////////////////////////////////////////////////////////// +// mailto url definition +///////////////////////////////////////////////////////////////////////////////////// +nsMailtoUrl::nsMailtoUrl() { mFormat = nsIMsgCompFormat::Default; } + +nsMailtoUrl::~nsMailtoUrl() {} + +NS_IMPL_ISUPPORTS(nsMailtoUrl, nsIMailtoUrl, nsIURI) + +static void UnescapeAndConvert(nsIMimeConverter* mimeConverter, + const nsACString& escaped, nsACString& out) { + NS_ASSERTION(mimeConverter, "Set mimeConverter before calling!"); + // If the string is empty, do absolutely nothing. + if (escaped.IsEmpty()) return; + + MsgUnescapeString(escaped, 0, out); + nsAutoCString decodedString; + nsresult rv = mimeConverter->DecodeMimeHeaderToUTF8(out, "UTF_8", false, true, + decodedString); + if (NS_SUCCEEDED(rv) && !decodedString.IsEmpty()) out = decodedString; +} + +nsresult nsMailtoUrl::ParseMailtoUrl(char* searchPart) { + char* rest = searchPart; + nsCString escapedInReplyToPart; + nsCString escapedToPart; + nsCString escapedCcPart; + nsCString escapedSubjectPart; + nsCString escapedNewsgroupPart; + nsCString escapedNewsHostPart; + nsCString escapedReferencePart; + nsCString escapedBodyPart; + nsCString escapedBccPart; + nsCString escapedFollowUpToPart; + nsCString escapedFromPart; + nsCString escapedHtmlPart; + nsCString escapedOrganizationPart; + nsCString escapedReplyToPart; + nsCString escapedPriorityPart; + + // okay, first, free up all of our old search part state..... + CleanupMailtoState(); + // m_toPart has the escaped address from before the query string, copy it + // over so we can add on any additional to= addresses and unescape them all. + escapedToPart = m_toPart; + + if (rest && *rest == '?') { + /* start past the '?' */ + rest++; + } + + if (rest) { + char* token = NS_strtok("&", &rest); + while (token && *token) { + char* value = 0; + char* eq = PL_strchr(token, '='); + if (eq) { + value = eq + 1; + *eq = 0; + } + + nsCString decodedName; + MsgUnescapeString(nsDependentCString(token), 0, decodedName); + + if (decodedName.IsEmpty()) break; + + switch (NS_ToUpper(decodedName.First())) { + /* DO NOT support attachment= in mailto urls. This poses a security + fire hole!!! case 'A': if (!PL_strcasecmp (token, "attachment")) + m_attachmentPart = value; + break; + */ + case 'B': + if (decodedName.LowerCaseEqualsLiteral("bcc")) { + if (!escapedBccPart.IsEmpty()) { + escapedBccPart += ", "; + escapedBccPart += value; + } else + escapedBccPart = value; + } else if (decodedName.LowerCaseEqualsLiteral("body")) { + if (!escapedBodyPart.IsEmpty()) { + escapedBodyPart += "\n"; + escapedBodyPart += value; + } else + escapedBodyPart = value; + } + break; + case 'C': + if (decodedName.LowerCaseEqualsLiteral("cc")) { + if (!escapedCcPart.IsEmpty()) { + escapedCcPart += ", "; + escapedCcPart += value; + } else + escapedCcPart = value; + } + break; + case 'F': + if (decodedName.LowerCaseEqualsLiteral("followup-to")) + escapedFollowUpToPart = value; + else if (decodedName.LowerCaseEqualsLiteral("from")) + escapedFromPart = value; + break; + case 'H': + if (decodedName.LowerCaseEqualsLiteral("html-part") || + decodedName.LowerCaseEqualsLiteral("html-body")) { + // escapedHtmlPart holds the body for both html-part and html-body. + escapedHtmlPart = value; + mFormat = nsIMsgCompFormat::HTML; + } + break; + case 'I': + if (decodedName.LowerCaseEqualsLiteral("in-reply-to")) + escapedInReplyToPart = value; + break; + + case 'N': + if (decodedName.LowerCaseEqualsLiteral("newsgroups")) + escapedNewsgroupPart = value; + else if (decodedName.LowerCaseEqualsLiteral("newshost")) + escapedNewsHostPart = value; + break; + case 'O': + if (decodedName.LowerCaseEqualsLiteral("organization")) + escapedOrganizationPart = value; + break; + case 'R': + if (decodedName.LowerCaseEqualsLiteral("references")) + escapedReferencePart = value; + else if (decodedName.LowerCaseEqualsLiteral("reply-to")) + escapedReplyToPart = value; + break; + case 'S': + if (decodedName.LowerCaseEqualsLiteral("subject")) + escapedSubjectPart = value; + break; + case 'P': + if (decodedName.LowerCaseEqualsLiteral("priority")) + escapedPriorityPart = PL_strdup(value); + break; + case 'T': + if (decodedName.LowerCaseEqualsLiteral("to")) { + if (!escapedToPart.IsEmpty()) { + escapedToPart += ", "; + escapedToPart += value; + } else + escapedToPart = value; + } + break; + default: + break; + } // end of switch statement... + + if (eq) *eq = '='; /* put it back */ + token = NS_strtok("&", &rest); + } // while we still have part of the url to parse... + } // if rest && *rest + + nsresult rv; + // Get a global converter + nsCOMPtr<nsIMimeConverter> mimeConverter = + do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Now unescape everything, and mime-decode the things that can be encoded. + UnescapeAndConvert(mimeConverter, escapedToPart, m_toPart); + UnescapeAndConvert(mimeConverter, escapedCcPart, m_ccPart); + UnescapeAndConvert(mimeConverter, escapedBccPart, m_bccPart); + UnescapeAndConvert(mimeConverter, escapedSubjectPart, m_subjectPart); + UnescapeAndConvert(mimeConverter, escapedNewsgroupPart, m_newsgroupPart); + UnescapeAndConvert(mimeConverter, escapedReferencePart, m_referencePart); + if (!escapedBodyPart.IsEmpty()) + MsgUnescapeString(escapedBodyPart, 0, m_bodyPart); + if (!escapedHtmlPart.IsEmpty()) + MsgUnescapeString(escapedHtmlPart, 0, m_htmlPart); + UnescapeAndConvert(mimeConverter, escapedNewsHostPart, m_newsHostPart); + UnescapeAndConvert(mimeConverter, escapedFollowUpToPart, m_followUpToPart); + UnescapeAndConvert(mimeConverter, escapedFromPart, m_fromPart); + UnescapeAndConvert(mimeConverter, escapedOrganizationPart, + m_organizationPart); + UnescapeAndConvert(mimeConverter, escapedReplyToPart, m_replyToPart); + UnescapeAndConvert(mimeConverter, escapedPriorityPart, m_priorityPart); + + nsCString inReplyToPart; // Not a member like the others... + UnescapeAndConvert(mimeConverter, escapedInReplyToPart, inReplyToPart); + + if (!inReplyToPart.IsEmpty()) { + // Ensure that References and In-Reply-To are consistent... The last + // reference will be used as In-Reply-To header. + if (m_referencePart.IsEmpty()) { + // If References is not set, set it to be the In-Reply-To. + m_referencePart = inReplyToPart; + } else { + // References is set. Add the In-Reply-To as last header unless it's + // set as last reference already. + int32_t lastRefStart = m_referencePart.RFindChar('<'); + nsAutoCString lastReference; + if (lastRefStart != -1) + lastReference = StringTail(m_referencePart, lastRefStart); + else + lastReference = m_referencePart; + + if (lastReference != inReplyToPart) { + m_referencePart += " "; + m_referencePart += inReplyToPart; + } + } + } + + return NS_OK; +} + +nsresult nsMailtoUrl::SetSpecInternal(const nsACString& aSpec) { + nsresult rv = NS_MutateURI(NS_SIMPLEURIMUTATOR_CONTRACTID) + .SetSpec(aSpec) + .Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +nsresult nsMailtoUrl::CleanupMailtoState() { + m_ccPart = ""; + m_subjectPart = ""; + m_newsgroupPart = ""; + m_newsHostPart = ""; + m_referencePart = ""; + m_bodyPart = ""; + m_bccPart = ""; + m_followUpToPart = ""; + m_fromPart = ""; + m_htmlPart = ""; + m_organizationPart = ""; + m_replyToPart = ""; + m_priorityPart = ""; + return NS_OK; +} + +nsresult nsMailtoUrl::ParseUrl() { + // we can get the path from the simple url..... + nsCString escapedPath; + m_baseURL->GetPathQueryRef(escapedPath); + + int32_t startOfSearchPart = escapedPath.FindChar('?'); + if (startOfSearchPart >= 0) { + // now parse out the search field... + nsAutoCString searchPart(Substring(escapedPath, startOfSearchPart)); + + if (!searchPart.IsEmpty()) { + // now we need to strip off the search part from the + // to part.... + escapedPath.SetLength(startOfSearchPart); + MsgUnescapeString(escapedPath, 0, m_toPart); + ParseMailtoUrl(searchPart.BeginWriting()); + } + } else if (!escapedPath.IsEmpty()) { + MsgUnescapeString(escapedPath, 0, m_toPart); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetMessageContents(nsACString& aToPart, nsACString& aCcPart, + nsACString& aBccPart, nsACString& aSubjectPart, + nsACString& aBodyPart, nsACString& aHtmlPart, + nsACString& aReferencePart, + nsACString& aNewsgroupPart, + MSG_ComposeFormat* aFormat) { + NS_ENSURE_ARG_POINTER(aFormat); + + aToPart = m_toPart; + aCcPart = m_ccPart; + aBccPart = m_bccPart; + aSubjectPart = m_subjectPart; + aBodyPart = m_bodyPart; + aHtmlPart = m_htmlPart; + aReferencePart = m_referencePart; + aNewsgroupPart = m_newsgroupPart; + *aFormat = mFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetFromPart(nsACString& aResult) { + aResult = m_fromPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetFollowUpToPart(nsACString& aResult) { + aResult = m_followUpToPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetOrganizationPart(nsACString& aResult) { + aResult = m_organizationPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetReplyToPart(nsACString& aResult) { + aResult = m_replyToPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetPriorityPart(nsACString& aResult) { + aResult = m_priorityPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetNewsHostPart(nsACString& aResult) { + aResult = m_newsHostPart; + return NS_OK; +} + +////////////////////////////////////////////////////////////////////////////// +// Begin nsIURI support +////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsMailtoUrl::GetSpec(nsACString& aSpec) { + return m_baseURL->GetSpec(aSpec); +} + +NS_IMETHODIMP nsMailtoUrl::GetPrePath(nsACString& aPrePath) { + return m_baseURL->GetPrePath(aPrePath); +} + +NS_IMETHODIMP nsMailtoUrl::GetScheme(nsACString& aScheme) { + return m_baseURL->GetScheme(aScheme); +} + +nsresult nsMailtoUrl::SetScheme(const nsACString& aScheme) { + nsresult rv = NS_MutateURI(m_baseURL).SetScheme(aScheme).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetUserPass(nsACString& aUserPass) { + return m_baseURL->GetUserPass(aUserPass); +} + +nsresult nsMailtoUrl::SetUserPass(const nsACString& aUserPass) { + nsresult rv = + NS_MutateURI(m_baseURL).SetUserPass(aUserPass).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetUsername(nsACString& aUsername) { + return m_baseURL->GetUsername(aUsername); +} + +nsresult nsMailtoUrl::SetUsername(const nsACString& aUsername) { + nsresult rv = + NS_MutateURI(m_baseURL).SetUsername(aUsername).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetPassword(nsACString& aPassword) { + return m_baseURL->GetPassword(aPassword); +} + +nsresult nsMailtoUrl::SetPassword(const nsACString& aPassword) { + nsresult rv = + NS_MutateURI(m_baseURL).SetPassword(aPassword).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetHostPort(nsACString& aHostPort) { + return m_baseURL->GetHost(aHostPort); +} + +nsresult nsMailtoUrl::SetHostPort(const nsACString& aHostPort) { + nsresult rv = + NS_MutateURI(m_baseURL).SetHostPort(aHostPort).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetHost(nsACString& aHost) { + return m_baseURL->GetHost(aHost); +} + +nsresult nsMailtoUrl::SetHost(const nsACString& aHost) { + nsresult rv = NS_MutateURI(m_baseURL).SetHost(aHost).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetPort(int32_t* aPort) { + return m_baseURL->GetPort(aPort); +} + +nsresult nsMailtoUrl::SetPort(int32_t aPort) { + nsresult rv = NS_MutateURI(m_baseURL).SetPort(aPort).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetPathQueryRef(nsACString& aPath) { + return m_baseURL->GetPathQueryRef(aPath); +} + +nsresult nsMailtoUrl::SetPathQueryRef(const nsACString& aPath) { + nsresult rv = + NS_MutateURI(m_baseURL).SetPathQueryRef(aPath).Finalize(m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetAsciiHost(nsACString& aHostA) { + return m_baseURL->GetAsciiHost(aHostA); +} + +NS_IMETHODIMP nsMailtoUrl::GetAsciiHostPort(nsACString& aHostPortA) { + return m_baseURL->GetAsciiHostPort(aHostPortA); +} + +NS_IMETHODIMP nsMailtoUrl::GetAsciiSpec(nsACString& aSpecA) { + return m_baseURL->GetAsciiSpec(aSpecA); +} + +NS_IMETHODIMP nsMailtoUrl::SchemeIs(const char* aScheme, bool* _retval) { + return m_baseURL->SchemeIs(aScheme, _retval); +} + +NS_IMETHODIMP nsMailtoUrl::Equals(nsIURI* other, bool* _retval) { + // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its + // Equals method. The other nsMailtoUrl will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) return other->Equals(m_baseURL, _retval); + + return m_baseURL->Equals(other, _retval); +} + +nsresult nsMailtoUrl::Clone(nsIURI** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + RefPtr<nsMailtoUrl> clone = new nsMailtoUrl(); + + NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_MutateURI(m_baseURL).Finalize(clone->m_baseURL); + NS_ENSURE_SUCCESS(rv, rv); + clone->ParseUrl(); + clone.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP nsMailtoUrl::Resolve(const nsACString& relativePath, + nsACString& result) { + return m_baseURL->Resolve(relativePath, result); +} + +nsresult nsMailtoUrl::SetRef(const nsACString& aRef) { + return NS_MutateURI(m_baseURL).SetRef(aRef).Finalize(m_baseURL); +} + +NS_IMETHODIMP +nsMailtoUrl::GetRef(nsACString& result) { return m_baseURL->GetRef(result); } + +NS_IMETHODIMP nsMailtoUrl::EqualsExceptRef(nsIURI* other, bool* result) { + // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its + // Equals method. The other nsMailtoUrl will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) return other->EqualsExceptRef(m_baseURL, result); + + return m_baseURL->EqualsExceptRef(other, result); +} + +NS_IMETHODIMP +nsMailtoUrl::GetSpecIgnoringRef(nsACString& result) { + return m_baseURL->GetSpecIgnoringRef(result); +} + +NS_IMETHODIMP +nsMailtoUrl::GetDisplaySpec(nsACString& aUnicodeSpec) { + return m_baseURL->GetDisplaySpec(aUnicodeSpec); +} + +NS_IMETHODIMP +nsMailtoUrl::GetDisplayHostPort(nsACString& aUnicodeHostPort) { + return m_baseURL->GetDisplayHostPort(aUnicodeHostPort); +} + +NS_IMETHODIMP +nsMailtoUrl::GetDisplayHost(nsACString& aUnicodeHost) { + return m_baseURL->GetDisplayHost(aUnicodeHost); +} + +NS_IMETHODIMP +nsMailtoUrl::GetDisplayPrePath(nsACString& aPrePath) { + return m_baseURL->GetDisplayPrePath(aPrePath); +} + +NS_IMETHODIMP +nsMailtoUrl::GetHasRef(bool* result) { return m_baseURL->GetHasRef(result); } + +NS_IMETHODIMP +nsMailtoUrl::GetFilePath(nsACString& aFilePath) { + return m_baseURL->GetFilePath(aFilePath); +} + +nsresult nsMailtoUrl::SetFilePath(const nsACString& aFilePath) { + return NS_MutateURI(m_baseURL).SetFilePath(aFilePath).Finalize(m_baseURL); +} + +NS_IMETHODIMP +nsMailtoUrl::GetQuery(nsACString& aQuery) { + return m_baseURL->GetQuery(aQuery); +} + +nsresult nsMailtoUrl::SetQuery(const nsACString& aQuery) { + return NS_MutateURI(m_baseURL).SetQuery(aQuery).Finalize(m_baseURL); +} + +nsresult nsMailtoUrl::SetQueryWithEncoding(const nsACString& aQuery, + const mozilla::Encoding* aEncoding) { + return NS_MutateURI(m_baseURL) + .SetQueryWithEncoding(aQuery, aEncoding) + .Finalize(m_baseURL); +} + +NS_IMETHODIMP_(void) +nsMailtoUrl::Serialize(mozilla::ipc::URIParams& aParams) { + m_baseURL->Serialize(aParams); +} + +NS_IMPL_ISUPPORTS(nsMailtoUrl::Mutator, nsIURISetters, nsIURIMutator) + +NS_IMETHODIMP +nsMailtoUrl::Mutate(nsIURIMutator** aMutator) { + RefPtr<nsMailtoUrl::Mutator> mutator = new nsMailtoUrl::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} + +nsresult nsMailtoUrl::NewMailtoURI(const nsACString& aSpec, nsIURI* aBaseURI, + nsIURI** _retval) { + nsresult rv; + + nsCOMPtr<nsIURI> mailtoUrl; + rv = NS_MutateURI(new Mutator()).SetSpec(aSpec).Finalize(mailtoUrl); + NS_ENSURE_SUCCESS(rv, rv); + + mailtoUrl.forget(_retval); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////////// +// smtp url definition +///////////////////////////////////////////////////////////////////////////////////// + +nsSmtpUrl::nsSmtpUrl() : nsMsgMailNewsUrl() { + // nsISmtpUrl specific state... + + m_isPostMessage = true; + m_requestDSN = false; + m_verifyLogon = false; +} + +nsSmtpUrl::~nsSmtpUrl() {} + +NS_IMPL_ISUPPORTS_INHERITED(nsSmtpUrl, nsMsgMailNewsUrl, nsISmtpUrl) + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsISmtpUrl specific support + +//////////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsSmtpUrl::SetRecipients(const char* aRecipientsList) { + NS_ENSURE_ARG(aRecipientsList); + MsgUnescapeString(nsDependentCString(aRecipientsList), 0, m_toPart); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetRecipients(char** aRecipientsList) { + NS_ENSURE_ARG_POINTER(aRecipientsList); + if (aRecipientsList) *aRecipientsList = ToNewCString(m_toPart); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetSender(const char* aSender) { + NS_ENSURE_ARG(aSender); + MsgUnescapeString(nsDependentCString(aSender), 0, m_fromPart); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetSender(char** aSender) { + NS_ENSURE_ARG_POINTER(aSender); + if (aSender) *aSender = ToNewCString(m_fromPart); + return NS_OK; +} + +NS_IMPL_GETSET(nsSmtpUrl, PostMessage, bool, m_isPostMessage) + +NS_IMPL_GETSET(nsSmtpUrl, VerifyLogon, bool, m_verifyLogon) + +// the message can be stored in a file....allow accessors for getting and +// setting the file name to post... +NS_IMETHODIMP nsSmtpUrl::SetPostMessageFile(nsIFile* aFile) { + NS_ENSURE_ARG_POINTER(aFile); + m_fileName = aFile; + return NS_OK; +} + +NS_IMETHODIMP nsSmtpUrl::GetPostMessageFile(nsIFile** aFile) { + NS_ENSURE_ARG_POINTER(aFile); + if (m_fileName) { + // Clone the file so nsLocalFile stat caching doesn't make the caller get + // the wrong file size. + m_fileName->Clone(aFile); + return *aFile ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMPL_GETSET(nsSmtpUrl, RequestDSN, bool, m_requestDSN) + +NS_IMETHODIMP +nsSmtpUrl::SetDsnEnvid(const nsACString& aDsnEnvid) { + m_dsnEnvid = aDsnEnvid; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetDsnEnvid(nsACString& aDsnEnvid) { + aDsnEnvid = m_dsnEnvid; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetSenderIdentity(nsIMsgIdentity** aSenderIdentity) { + NS_ENSURE_ARG_POINTER(aSenderIdentity); + NS_ADDREF(*aSenderIdentity = m_senderIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetSenderIdentity(nsIMsgIdentity* aSenderIdentity) { + NS_ENSURE_ARG_POINTER(aSenderIdentity); + m_senderIdentity = aSenderIdentity; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetPrompt(nsIPrompt* aNetPrompt) { + NS_ENSURE_ARG_POINTER(aNetPrompt); + m_netPrompt = aNetPrompt; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetPrompt(nsIPrompt** aNetPrompt) { + NS_ENSURE_ARG_POINTER(aNetPrompt); + NS_ENSURE_TRUE(m_netPrompt, NS_ERROR_NULL_POINTER); + NS_ADDREF(*aNetPrompt = m_netPrompt); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetAuthPrompt(nsIAuthPrompt* aNetAuthPrompt) { + NS_ENSURE_ARG_POINTER(aNetAuthPrompt); + m_netAuthPrompt = aNetAuthPrompt; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetAuthPrompt(nsIAuthPrompt** aNetAuthPrompt) { + NS_ENSURE_ARG_POINTER(aNetAuthPrompt); + NS_ENSURE_TRUE(m_netAuthPrompt, NS_ERROR_NULL_POINTER); + NS_ADDREF(*aNetAuthPrompt = m_netAuthPrompt); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { + NS_ENSURE_ARG_POINTER(aCallbacks); + m_callbacks = aCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) { + NS_ENSURE_ARG_POINTER(aCallbacks); + NS_ENSURE_TRUE(m_callbacks, NS_ERROR_NULL_POINTER); + NS_ADDREF(*aCallbacks = m_callbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetSmtpServer(nsISmtpServer* aSmtpServer) { + NS_ENSURE_ARG_POINTER(aSmtpServer); + m_smtpServer = aSmtpServer; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetSmtpServer(nsISmtpServer** aSmtpServer) { + NS_ENSURE_ARG_POINTER(aSmtpServer); + NS_ENSURE_TRUE(m_smtpServer, NS_ERROR_NULL_POINTER); + NS_ADDREF(*aSmtpServer = m_smtpServer); + return NS_OK; +} + +nsresult nsSmtpUrl::NewSmtpURI(const nsACString& aSpec, nsIURI* aBaseURI, + nsIURI** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = 0; + nsresult rv; + nsCOMPtr<nsIMsgMailNewsUrl> aSmtpUri = new nsSmtpUrl(); + if (aBaseURI) { + nsAutoCString newSpec; + rv = aBaseURI->Resolve(aSpec, newSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = aSmtpUri->SetSpecInternal(newSpec); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = aSmtpUri->SetSpecInternal(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + aSmtpUri.forget(_retval); + + return rv; +} diff --git a/comm/mailnews/compose/src/nsSmtpUrl.h b/comm/mailnews/compose/src/nsSmtpUrl.h new file mode 100644 index 0000000000..bd205f119c --- /dev/null +++ b/comm/mailnews/compose/src/nsSmtpUrl.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSmtpUrl_h__ +#define nsSmtpUrl_h__ + +#include "nsISmtpUrl.h" +#include "nsIURI.h" +#include "nsMsgMailNewsUrl.h" +#include "nsIMsgIdentity.h" +#include "nsCOMPtr.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsISmtpServer.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIURIMutator.h" + +class nsMailtoUrl : public nsIMailtoUrl, public nsIURI { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIMAILTOURL + + nsMailtoUrl(); + static nsresult NewMailtoURI(const nsACString& aSpec, nsIURI* aBaseURI, + nsIURI** _retval); + + protected: + virtual nsresult Clone(nsIURI** _retval); + virtual nsresult SetSpecInternal(const nsACString& aSpec); + virtual nsresult SetScheme(const nsACString& aScheme); + virtual nsresult SetUserPass(const nsACString& aUserPass); + virtual nsresult SetUsername(const nsACString& aUsername); + virtual nsresult SetPassword(const nsACString& aPassword); + virtual nsresult SetHostPort(const nsACString& aHostPort); + virtual nsresult SetHost(const nsACString& aHost); + virtual nsresult SetPort(int32_t aPort); + virtual nsresult SetPathQueryRef(const nsACString& aPath); + virtual nsresult SetRef(const nsACString& aRef); + virtual nsresult SetFilePath(const nsACString& aFilePath); + virtual nsresult SetQuery(const nsACString& aQuery); + virtual nsresult SetQueryWithEncoding(const nsACString& aQuery, + const mozilla::Encoding* aEncoding); + + public: + class Mutator : public nsIURIMutator, public BaseURIMutator<nsMailtoUrl> { + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI) + + NS_IMETHOD Deserialize(const mozilla::ipc::URIParams& aParams) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD Finalize(nsIURI** aURI) override { + mURI.forget(aURI); + return NS_OK; + } + + NS_IMETHOD SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) override { + if (aMutator) NS_ADDREF(*aMutator = this); + return InitFromSpec(aSpec); + } + + explicit Mutator() {} + + private: + virtual ~Mutator() {} + + friend class nsMailtoUrl; + }; + friend BaseURIMutator<nsMailtoUrl>; + + protected: + virtual ~nsMailtoUrl(); + nsresult ParseUrl(); + nsresult CleanupMailtoState(); + nsresult ParseMailtoUrl(char* searchPart); + + nsCOMPtr<nsIURI> m_baseURL; + + // data retrieved from parsing the url: (Note the url could be a post from + // file or it could be in the url) + nsCString m_toPart; + nsCString m_ccPart; + nsCString m_subjectPart; + nsCString m_newsgroupPart; + nsCString m_newsHostPart; + nsCString m_referencePart; + nsCString m_bodyPart; + nsCString m_bccPart; + nsCString m_followUpToPart; + nsCString m_fromPart; + nsCString m_htmlPart; + nsCString m_organizationPart; + nsCString m_replyToPart; + nsCString m_priorityPart; + + MSG_ComposeFormat mFormat; +}; + +class nsSmtpUrl : public nsISmtpUrl, public nsMsgMailNewsUrl { + public: + NS_DECL_ISUPPORTS_INHERITED + + // From nsISmtpUrl + NS_DECL_NSISMTPURL + + // nsSmtpUrl + nsSmtpUrl(); + static nsresult NewSmtpURI(const nsACString& aSpec, nsIURI* aBaseURI, + nsIURI** _retval); + + protected: + virtual ~nsSmtpUrl(); + + // data retrieved from parsing the url: (Note the url could be a post from + // file or it could be in the url) + nsCString m_toPart; + nsCString m_fromPart; + + bool m_isPostMessage; + bool m_requestDSN; + nsCString m_dsnEnvid; + bool m_verifyLogon; + + // Smtp specific event sinks + nsCOMPtr<nsIFile> m_fileName; + nsCOMPtr<nsIMsgIdentity> m_senderIdentity; + nsCOMPtr<nsIPrompt> m_netPrompt; + nsCOMPtr<nsIAuthPrompt> m_netAuthPrompt; + nsCOMPtr<nsIInterfaceRequestor> m_callbacks; + nsCOMPtr<nsISmtpServer> m_smtpServer; + + // it is possible to encode the message to parse in the form of a url. + // This function is used to decompose the search and path part into the bare + // message components (to, fcc, bcc, etc.) + nsresult ParseMessageToPost(char* searchPart); +}; + +#endif // nsSmtpUrl_h__ |