diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mailnews/compose/src/nsMsgCompose.cpp | 5094 |
1 files changed, 5094 insertions, 0 deletions
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; +} |