/* -*- 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 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 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* outParent, int32_t* outOffset) { NS_ASSERTION((outParent && outOffset), "bad args"); nsresult result = NS_ERROR_NULL_POINTER; if (inChild && outParent && outOffset) { nsCOMPtr 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 image = HTMLImageElement::FromNode(element); RefPtr anchor = HTMLAnchorElement::FromNode(element); if (image) image->GetSrc(objURL); else if (anchor) anchor->GetHref(objURL); else return false; if (!objURL.IsEmpty()) { nsCOMPtr 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; aEditor->GetDocument(getter_AddRefs(document)); if (!document) return NS_ERROR_FAILURE; nsCOMPtr aNodeList = GetEmbeddedObjects(document); if (!aNodeList) return NS_ERROR_FAILURE; if (NS_FAILED(aNodeList->GetLength(&count))) return NS_ERROR_FAILURE; nsCOMPtr originalUrl; nsCString originalScheme; nsCString originalHost; nsCString originalPath; // first, convert the rdf original msg uri into a url that represents the // message... nsCOMPtr 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 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 divElem; nsCOMPtr 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 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
nodes. int32_t start = 0; int32_t end = aText.Length(); for (;;) { int32_t delimiter = aText.FindChar('\n', start); if (delimiter == kNotFound) delimiter = end; RefPtr 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 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 parent; int32_t offset; rv = GetNodeLocation(divElem, address_of(parent), &offset); if (NS_SUCCEEDED(rv)) { RefPtr selection; m_editor->GetSelection(getter_AddRefs(selection)); if (selection) selection->CollapseInLimiter(parent, offset + 1); } if (divElem) { RefPtr divElem2 = divElem; IgnoredErrorResult rv2; divElem2->SetAttribute(u"class"_ns, classStr, rv2); } } /* * The following function replaces 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(&quotingToFollow); 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; }