diff options
Diffstat (limited to 'mailnews/compose/src/nsMsgCompose.cpp')
-rw-r--r-- | mailnews/compose/src/nsMsgCompose.cpp | 6052 |
1 files changed, 6052 insertions, 0 deletions
diff --git a/mailnews/compose/src/nsMsgCompose.cpp b/mailnews/compose/src/nsMsgCompose.cpp new file mode 100644 index 000000000..58340bffa --- /dev/null +++ b/mailnews/compose/src/nsMsgCompose.cpp @@ -0,0 +1,6052 @@ +/* -*- 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 "nsIDOMDocument.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMText.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsISelectionController.h" +#include "nsMsgI18N.h" +#include "nsMsgCompCID.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 "nsIPlaintextEditor.h" +#include "nsIHTMLEditor.h" +#include "nsIEditorMailSupport.h" +#include "plstr.h" +#include "prmem.h" +#include "nsIDocShell.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsAbBaseCID.h" +#include "nsIAbMDBDirectory.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIWindowMediator.h" +#include "nsIURL.h" +#include "nsIMsgMailSession.h" +#include "nsMsgBaseCID.h" +#include "nsMsgMimeCID.h" +#include "nsDateTimeFormatCID.h" +#include "nsIDateTimeFormat.h" +#include "nsILocaleService.h" +#include "nsILocale.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 "nsUConvCID.h" +#include "nsIUnicodeNormalizer.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgProgress.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgDatabase.h" +#include "nsStringStream.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIMsgWindow.h" +#include "nsITextToSubURI.h" +#include "nsIAbManager.h" +#include "nsCRT.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Preferences.h" +#include "nsStreamConverter.h" +#include "nsISelection.h" +#include "nsJSEnvironment.h" +#include "nsIObserverService.h" +#include "nsIProtocolHandler.h" +#include "nsContentUtils.h" +#include "nsIFileURL.h" + +using namespace mozilla; +using namespace mozilla::mailnews; + +static nsresult GetReplyHeaderInfo(int32_t* reply_header_type, + nsString& reply_header_locale, + nsString& reply_header_authorwrote, + nsString& reply_header_ondateauthorwrote, + nsString& reply_header_authorwroteondate, + nsString& reply_header_originalmessage) +{ + nsresult rv; + *reply_header_type = 0; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // If fetching any of the preferences fails, + // we return early with header_type = 0 meaning "no header". + rv = NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_locale", EmptyString(), reply_header_locale); + NS_ENSURE_SUCCESS(rv, rv); + + 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); +} + +static void GetTopmostMsgWindowCharacterSet(nsCString& charset, bool* charsetOverride) +{ + // HACK: if we are replying to a message and that message used a charset over ride + // (as specified in the top most window (assuming the reply originated from that window) + // then use that over ride charset instead of the charset specified in the message + nsCOMPtr <nsIMsgMailSession> mailSession (do_GetService(NS_MSGMAILSESSION_CONTRACTID)); + if (mailSession) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + msgWindow->GetMailCharacterSet(charset); + msgWindow->GetCharsetOverride(charsetOverride); + } + } +} + +nsMsgCompose::nsMsgCompose() +{ + + mQuotingToFollow = false; + mInsertingQuotedContent = false; + mWhatHolder = 1; + m_window = nullptr; + m_editor = nullptr; + mQuoteStreamListener=nullptr; + mCharsetOverride = false; + mAnswerDefaultCharset = false; + mDeleteDraft = false; + m_compFields = nullptr; //m_compFields will be set during nsMsgCompose::Initialize + mType = nsIMsgCompType::New; + + // For TagConvertible + // Read and cache pref + mConvertStructs = false; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) + prefBranch->GetBoolPref("converter.html2txt.structs", &mConvertStructs); + + m_composeHTML = false; +} + + +nsMsgCompose::~nsMsgCompose() +{ + NS_IF_RELEASE(m_compFields); + NS_IF_RELEASE(mQuoteStreamListener); +} + +/* 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(nsIDOMNode *aChild, nsIDOMNode *aParent, int32_t &aOffset) +{ + NS_ASSERTION((aChild && aParent), "bad args"); + nsresult result = NS_ERROR_NULL_POINTER; + if (aChild && aParent) + { + nsCOMPtr<nsIDOMNodeList> childNodes; + result = aParent->GetChildNodes(getter_AddRefs(childNodes)); + if ((NS_SUCCEEDED(result)) && (childNodes)) + { + int32_t i=0; + for ( ; NS_SUCCEEDED(result); i++) + { + nsCOMPtr<nsIDOMNode> childNode; + result = childNodes->Item(i, getter_AddRefs(childNode)); + if ((NS_SUCCEEDED(result)) && (childNode)) + { + if (childNode.get()==aChild) + { + aOffset = i; + break; + } + } + else if (!childNode) + result = NS_ERROR_NULL_POINTER; + } + } + else if (!childNodes) + result = NS_ERROR_NULL_POINTER; + } + return result; +} + +nsresult +GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, int32_t *outOffset) +{ + NS_ASSERTION((outParent && outOffset), "bad args"); + nsresult result = NS_ERROR_NULL_POINTER; + if (inChild && outParent && outOffset) + { + result = inChild->GetParentNode(getter_AddRefs(*outParent)); + if ( (NS_SUCCEEDED(result)) && (*outParent) ) + { + result = GetChildOffset(inChild, *outParent, *outOffset); + } + } + + return result; +} + +bool nsMsgCompose::IsEmbeddedObjectSafe(const char * originalScheme, + const char * originalHost, + const char * originalPath, + nsIDOMNode * object) +{ + nsresult rv; + + nsCOMPtr<nsIDOMHTMLImageElement> image; + nsCOMPtr<nsIDOMHTMLLinkElement> link; + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor; + nsAutoString objURL; + + if (!object || !originalScheme || !originalPath) //having a null host is ok... + return false; + + if ((image = do_QueryInterface(object))) + { + if (NS_FAILED(image->GetSrc(objURL))) + return false; + } + else if ((link = do_QueryInterface(object))) + { + if (NS_FAILED(link->GetHref(objURL))) + return false; + } + else if ((anchor = do_QueryInterface(object))) + { + if (NS_FAILED(anchor->GetHref(objURL))) + return false; + } + else + return false; + + if (!objURL.IsEmpty()) + { + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), objURL); + if (NS_SUCCEEDED(rv) && uri) + { + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + if (NS_SUCCEEDED(rv) && scheme.Equals(originalScheme, nsCaseInsensitiveCStringComparator())) + { + nsAutoCString host; + rv = uri->GetAsciiHost(host); + // mailbox url don't have a host therefore don't be too strict. + if (NS_SUCCEEDED(rv) && (host.IsEmpty() || originalHost || host.Equals(originalHost, nsCaseInsensitiveCStringComparator()))) + { + nsAutoCString path; + rv = uri->GetPath(path); + if (NS_SUCCEEDED(rv)) + { + const char * query = strrchr(path.get(), '?'); + if (query && PL_strncasecmp(path.get(), originalPath, query - path.get()) == 0) + return true; //This object is a part of the original message, we can send it safely. + } + } + } + } + } + + return false; +} + +/* Reset the uri's of embedded objects because we've saved the draft message, and the + original message doesn't exist anymore. + */ +nsresult nsMsgCompose::ResetUrisForEmbeddedObjects() +{ + nsCOMPtr<nsIArray> aNodeList; + uint32_t numNodes; + uint32_t i; + + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor)); + if (!mailEditor) + return NS_ERROR_FAILURE; + + nsresult rv = mailEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList)); + if (NS_FAILED(rv) || !aNodeList) + return NS_ERROR_FAILURE; + + if (NS_FAILED(aNodeList->GetLength(&numNodes))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDOMNode> node; + nsCString curDraftIdURL; + + rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL)); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && mMsgSend && !curDraftIdURL.IsEmpty()) + { + nsCOMPtr <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer."); + if (NS_SUCCEEDED(rv) && msgDBHdr) + { + // build up the old and new ?number= parts. This code assumes it is + // called *before* RemoveCurrentDraftMessage, so that curDraftIdURL + // is the previous draft. + // This code works for both imap and local messages. + nsMsgKey newMsgKey; + nsCString folderUri; + nsCString baseMsgUri; + mMsgSend->GetMessageKey(&newMsgKey); + mMsgSend->GetFolderUri(folderUri); + nsCOMPtr<nsIMsgFolder> folder; + rv = GetExistingFolder(folderUri, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + folder->GetBaseMessageURI(baseMsgUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMElement> domElement; + for (i = 0; i < numNodes; i ++) + { + domElement = do_QueryElementAt(aNodeList, i); + if (!domElement) + continue; + + nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(domElement); + if (!image) + continue; + nsCString partNum; + mMsgSend->GetPartForDomIndex(i, partNum); + // do we care about anything besides images? + nsAutoString objURL; + image->GetSrc(objURL); + + // First we need to make sure that the URL is associated with a message + // protocol so we don't accidentally manipulate a URL like: + // http://www.site.com/retrieve.html?C=image.jpg. + nsCOMPtr<nsIIOService> ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString scheme; + ioService->ExtractScheme(NS_ConvertUTF16toUTF8(objURL), scheme); + + // Detect message protocols where attachments can occur. + nsCOMPtr<nsIProtocolHandler> handler; + ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (!handler) + continue; + nsCOMPtr<nsIMsgMessageFetchPartService> mailHandler = do_QueryInterface(handler); + if (!mailHandler) + continue; + + // the objURL is the full path to the embedded content. We need + // to update it with uri for the folder we just saved to, and the new + // msg key. + int32_t restOfUrlIndex = objURL.Find("?number="); + if (restOfUrlIndex == kNotFound) + restOfUrlIndex = objURL.FindChar('?'); + else + restOfUrlIndex = objURL.FindChar('&', restOfUrlIndex); + + if (restOfUrlIndex == kNotFound) + continue; + + nsCString newURI(baseMsgUri); + newURI.Append('#'); + newURI.AppendInt(newMsgKey); + nsString restOfUrl(Substring(objURL, restOfUrlIndex, objURL.Length() - restOfUrlIndex)); + int32_t partIndex = restOfUrl.Find("part="); + if (partIndex != kNotFound) + { + partIndex += 5; + int32_t endPart = restOfUrl.FindChar('&', partIndex); + int32_t existingPartLen = (endPart == kNotFound) ? -1 : endPart - partIndex; + restOfUrl.Replace(partIndex, existingPartLen, NS_ConvertASCIItoUTF16(partNum)); + } + + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(newURI, getter_AddRefs(msgService)); + if (NS_FAILED(rv)) + continue; + nsCOMPtr<nsIURI> newUrl; + rv = msgService->GetUrlForUri(newURI.get(), getter_AddRefs(newUrl), nullptr); + if (!newUrl) + continue; + nsCString spec; + rv = newUrl->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + nsString newSrc; + // mailbox urls will have ?number=xxx; imap urls won't. We need to + // handle both cases because we may be going from a mailbox url to + // and imap url, or vice versa, depending on the original folder, + // and the destination drafts folder. + bool specHasQ = (spec.FindChar('?') != kNotFound); + if (specHasQ && restOfUrl.CharAt(0) == '?') + restOfUrl.SetCharAt('&', 0); + else if (!specHasQ && restOfUrl.CharAt(0) == '&') + restOfUrl.SetCharAt('?', 0); + AppendUTF8toUTF16(spec, newSrc); + newSrc.Append(restOfUrl); + image->SetSrc(newSrc); + } + } + } + + return NS_OK; +} + + +/* 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(nsIEditorMailSupport *aEditor) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIArray> aNodeList; + uint32_t count; + uint32_t i; + + if (!aEditor) + return NS_ERROR_FAILURE; + + rv = aEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList)); + if (NS_FAILED(rv) || !aNodeList) + return NS_ERROR_FAILURE; + + if (NS_FAILED(aNodeList->GetLength(&count))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURI> originalUrl; + nsCString originalScheme; + nsCString originalHost; + nsCString originalPath; + + // first, convert the rdf original msg uri into a url that represents the message... + nsCOMPtr <nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(mOriginalMsgURI, getter_AddRefs(msgService)); + if (NS_SUCCEEDED(rv)) + { + rv = msgService->GetUrlForUri(mOriginalMsgURI.get(), getter_AddRefs(originalUrl), nullptr); + if (NS_SUCCEEDED(rv) && originalUrl) + { + originalUrl->GetScheme(originalScheme); + originalUrl->GetAsciiHost(originalHost); + originalUrl->GetPath(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<nsIDOMNode> node = do_QueryElementAt(aNodeList, i); + if (!node) + continue; + if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(), + originalPath.get(), node)) + continue; //Don't need to tag this object, it safe to send it. + + //The source of this object should not be sent with the message + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(node); + if (domElement) + domElement->SetAttribute(NS_LITERAL_STRING("moz-do-not-send"), NS_LITERAL_STRING("true")); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::GetInsertingQuotedContent(bool * aInsertingQuotedText) +{ + NS_ENSURE_ARG_POINTER(aInsertingQuotedText); + *aInsertingQuotedText = mInsertingQuotedContent; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::SetInsertingQuotedContent(bool aInsertingQuotedText) +{ + mInsertingQuotedContent = aInsertingQuotedText; + return NS_OK; +} + +void +nsMsgCompose::InsertDivWrappedTextAtSelection(const nsAString &aText, + const nsAString &classStr) +{ + NS_ASSERTION(m_editor, "InsertDivWrappedTextAtSelection called, but no editor exists\n"); + if (!m_editor) + return; + + nsCOMPtr<nsIDOMElement> divElem; + nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor)); + + nsresult rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"), + getter_AddRefs(divElem)); + + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIDOMNode> divNode (do_QueryInterface(divElem)); + + // We need the document + nsCOMPtr<nsIDOMDocument> doc; + rv = m_editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS_VOID(rv); + + // Break up the text by newlines, and then insert text nodes followed + // by <br> nodes. + int32_t start = 0; + int32_t end = aText.Length(); + + for (;;) + { + int32_t delimiter = aText.FindChar('\n', start); + if (delimiter == kNotFound) + delimiter = end; + + nsCOMPtr<nsIDOMText> textNode; + rv = doc->CreateTextNode(Substring(aText, start, delimiter - start), getter_AddRefs(textNode)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIDOMNode> newTextNode = do_QueryInterface(textNode); + nsCOMPtr<nsIDOMNode> resultNode; + rv = divElem->AppendChild(newTextNode, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS_VOID(rv); + + // Now create and insert a BR + nsCOMPtr<nsIDOMElement> brElem; + rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"), + getter_AddRefs(brElem)); + rv = divElem->AppendChild(brElem, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS_VOID(rv); + + if (delimiter == end) + break; + start = ++delimiter; + if (start == end) + break; + } + + htmlEditor->InsertElementAtSelection(divElem, true); + nsCOMPtr<nsIDOMNode> parent; + int32_t offset; + + rv = GetNodeLocation(divNode, address_of(parent), &offset); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + + if (selection) + selection->Collapse(parent, offset + 1); + } + if (divElem) + divElem->SetAttribute(NS_LITERAL_STRING("class"), classStr); +} + +/* + * The following function replaces <plaintext> tags with <x-plaintext>. + * <plaintext> is a funny beast: It leads to everything following it + * being displayed verbatim, even a </plaintext> tag is ignored. + */ +static void +remove_plaintext_tag(nsString &body) +{ + // Replace all <plaintext> and </plaintext> tags. + int32_t index = 0; + bool replaced = false; + while ((index = body.Find("<plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + body.Insert(u"x-", index+1); + index += 12; + replaced = true; + } + if (replaced) { + index = 0; + while ((index = body.Find("</plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + body.Insert(u"x-", index+2); + index += 13; + } + } +} + +NS_IMETHODIMP +nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix, + nsString& aBuf, + nsString& aSignature, + bool aQuoted, + bool aHTMLEditor) +{ + NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor\n"); + NS_ENSURE_TRUE(m_editor && m_identity, NS_ERROR_NOT_INITIALIZED); + + // First, get the nsIEditor interface for future use + nsCOMPtr<nsIDOMNode> 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... + nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor)); + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor)); + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor)); + 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); + + if (aQuoted) + { + mInsertingQuotedContent = true; + 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 + if (aHTMLEditor) + textEditor->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 (includeSignature && !sig_bottom && + ((NS_SUCCEEDED(rv) && attachFile) || !prefSigText.IsEmpty())) + textEditor->InsertLineBreak(); + else { + textEditor->InsertLineBreak(); + textEditor->InsertLineBreak(); + } + } + + InsertDivWrappedTextAtSelection(aPrefix, + NS_LITERAL_STRING("moz-cite-prefix")); + } + + if (!aBuf.IsEmpty() && mailEditor) + { + // This leaves the caret at the right place to insert a bottom signature. + if (aHTMLEditor) { + nsAutoString body(aBuf); + remove_plaintext_tag(body); + mailEditor->InsertAsCitedQuotation(body, + mCiteReference, + true, + getter_AddRefs(nodeInserted)); + } else { + mailEditor->InsertAsQuotation(aBuf, + getter_AddRefs(nodeInserted)); + } + } + + mInsertingQuotedContent = false; + + (void)TagEmbeddedObjects(mailEditor); + + 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 ) + m_editor->BeginningOfDocument(); + + if (aHTMLEditor && htmlEditor) + htmlEditor->InsertHTML(aSignature); + else if (htmlEditor) + { + textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, + NS_LITERAL_STRING("moz-signature")); + } + + if( sigOnTop ) + m_editor->EndOfDocument(); + } + } + else + { + if (aHTMLEditor && htmlEditor) + { + mInsertingQuotedContent = true; + 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 newBody(aBuf); + nsString divTag; + divTag.AssignLiteral("<div class=\"moz-forward-container\">"); + newBody.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX)-1-8); + remove_plaintext_tag(newBody); + htmlEditor->RebuildDocumentFromSource(newBody); + } else { + htmlEditor->RebuildDocumentFromSource(aBuf); + } + mInsertingQuotedContent = false; + + // when forwarding a message as inline, tag any embedded objects + // which refer to local images or files so we know not to include + // send them + if (isForwarded) + (void)TagEmbeddedObjects(mailEditor); + + 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(); + } + htmlEditor->InsertHTML(aSignature); + if (isForwarded && sigOnTop) + m_editor->EndOfDocument(); + } + else + m_editor->EndOfDocument(); + } + else if (htmlEditor) + { + bool sigOnTopInserted = false; + if (isForwarded && sigOnTop && !aSignature.IsEmpty()) + { + textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, + NS_LITERAL_STRING("moz-signature")); + m_editor->EndOfDocument(); + sigOnTopInserted = true; + } + + if (!aBuf.IsEmpty()) + { + nsresult rv; + nsCOMPtr<nsIDOMElement> divElem; + nsCOMPtr<nsIDOMNode> extraBr; + + if (isForwarded) { + // Special treatment for forwarded messages: Part 1. + // Create a <div> of the required class. + rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"), + getter_AddRefs(divElem)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString attributeName; + nsAutoString attributeValue; + attributeName.AssignLiteral("class"); + attributeValue.AssignLiteral("moz-forward-container"); + divElem->SetAttribute(attributeName, attributeValue); + + // We can't insert an empty <div>, so fill it with something. + nsCOMPtr<nsIDOMElement> brElem; + rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"), + getter_AddRefs(brElem)); + NS_ENSURE_SUCCESS(rv, rv); + rv = divElem->AppendChild(brElem, getter_AddRefs(extraBr)); + NS_ENSURE_SUCCESS(rv, rv); + + // 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. + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + rv = selection->Collapse(divElem, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mailEditor) { + rv = mailEditor->InsertTextWithQuotations(aBuf); + } else { + // Will we ever get here? + rv = textEditor->InsertText(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<nsIDOMNode> brBeforeDiv; + nsAutoString tagLocalName; + rv = divElem->GetPreviousSibling(getter_AddRefs(brBeforeDiv)); + if (NS_SUCCEEDED(rv) && brBeforeDiv) { + brBeforeDiv->GetLocalName(tagLocalName); + if (tagLocalName.EqualsLiteral("br")) { + rv = m_editor->DeleteNode(brBeforeDiv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + // Clean up the <br> we inserted. + rv = m_editor->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()) { + textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, + NS_LITERAL_STRING("moz-signature")); + } + } + } + + if (aBuf.IsEmpty()) + m_editor->BeginningOfDocument(); + else + { + switch (reply_on_top) + { + // This should set the cursor after the body but before the sig + case 0: + { + if (!textEditor) + { + m_editor->BeginningOfDocument(); + break; + } + + nsCOMPtr<nsISelection> selection = nullptr; + nsCOMPtr<nsIDOMNode> parent = nullptr; + int32_t offset; + nsresult rv; + + // get parent and offset of mailcite + rv = GetNodeLocation(nodeInserted, address_of(parent), &offset); + if (NS_FAILED(rv) || (!parent)) + { + m_editor->BeginningOfDocument(); + break; + } + + // get selection + m_editor->GetSelection(getter_AddRefs(selection)); + if (!selection) + { + m_editor->BeginningOfDocument(); + break; + } + + // place selection after mailcite + selection->Collapse(parent, offset+1); + + // insert a break at current selection + textEditor->InsertLineBreak(); + + // i'm not sure if you need to move the selection back to before the + // break. expirement. + selection->Collapse(parent, offset+1); + + break; + } + + case 2: + { + m_editor->SelectAll(); + break; + } + + // This should set the cursor to the top! + default: + { + m_editor->BeginningOfDocument(); + break; + } + } + } + + nsCOMPtr<nsISelectionController> selCon; + m_editor->GetSelectionController(getter_AddRefs(selCon)); + + if (selCon) + selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_ANCHOR_REGION, true); + + m_editor->EnableUndo(true); + SetBodyModified(false); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID)); + 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 = + do_QueryInterface(window->GetDocShell()); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_FAILED(rv)) return rv; + + m_baseWindow = do_QueryInterface(treeOwner); + } + + MSG_ComposeFormat format; + aParams->GetFormat(&format); + + MSG_ComposeType type; + aParams->GetType(&type); + + nsCString originalMsgURI; + aParams->GetOriginalMsgURI(getter_Copies(originalMsgURI)); + aParams->GetOrigMsgHdr(getter_AddRefs(mOrigMsgHdr)); + + nsCOMPtr<nsIMsgCompFields> composeFields; + aParams->GetComposeFields(getter_AddRefs(composeFields)); + + nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = composeService->DetermineComposeHTML(m_identity, format, &m_composeHTML); + NS_ENSURE_SUCCESS(rv,rv); + + if (composeFields) + { + nsAutoCString draftId; // will get set for drafts and templates + rv = composeFields->GetDraftId(getter_Copies(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 ); + + nsCString smtpPassword; + aParams->GetSmtpPassword(getter_Copies(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.get(), type, composeFields); +} + +nsresult nsMsgCompose::SetDocumentCharset(const char *aCharset) +{ + NS_ENSURE_TRUE(m_compFields && m_editor, NS_ERROR_NOT_INITIALIZED); + + // Set charset, this will be used for the MIME charset labeling. + m_compFields->SetCharacterSet(aCharset); + + // notify the change to editor + nsCString charset; + if (aCharset) + charset = nsDependentCString(aCharset); + if (m_editor) + m_editor->SetDocumentCharacterSet(charset); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::RegisterStateListener(nsIMsgComposeStateListener *aStateListener) +{ + NS_ENSURE_ARG_POINTER(aStateListener); + + return mStateListeners.AppendElement(aStateListener) ? NS_OK : NS_ERROR_FAILURE; +} + +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); + return mExternalSendListeners.AppendElement(aMsgSendListener) ? NS_OK : NS_ERROR_FAILURE; +} + +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) +{ + 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.AppendLiteral(","); + + // Second parameter: deliverMode. + sendParms.AppendInt(deliverMode); + sendParms.AppendLiteral(","); + + // 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(NS_MSGSEND_CONTRACTID); + + if (mMsgSend) + { + nsCString bodyString(m_compFields->GetBody()); + + // Create the listener for the send operation... + nsCOMPtr<nsIMsgComposeSendListener> composeSendListener = do_CreateInstance(NS_MSGCOMPOSESENDLISTENER_CONTRACTID); + 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); + 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, + nullptr, + nullptr, + m_window, + mProgress, + sendListener, + mSmtpPassword.get(), + mOriginalMsgURI, + mType); + } + else + rv = NS_ERROR_FAILURE; + } + else + rv = NS_ERROR_NOT_INITIALIZED; + + if (NS_FAILED(rv)) + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, rv); + + return rv; +} + +NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, const char *accountKey, nsIMsgWindow *aMsgWindow, nsIMsgProgress *progress) +{ + NS_ENSURE_TRUE(m_compFields, NS_ERROR_NOT_INITIALIZED); + nsresult rv = NS_OK; + nsCOMPtr<nsIPrompt> prompt; + + // i'm assuming the compose window is still up at this point... + if (m_window) { + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(m_window); + window->GetPrompter(getter_AddRefs(prompt)); + } + + // Set content type based on which type of compose window we had. + nsString contentType = (m_composeHTML) ? NS_LITERAL_STRING("text/html"): + NS_LITERAL_STRING("text/plain"); + nsString msgBody; + if (m_editor) + { + // Reset message body previously stored in the compose fields + // There is 2 nsIMsgCompFields::SetBody() functions using a pointer as argument, + // therefore a casting is required. + m_compFields->SetBody((const char *)nullptr); + + const char *charset = m_compFields->GetCharacterSet(); + + uint32_t flags = nsIDocumentEncoder::OutputCRLineBreak | + nsIDocumentEncoder::OutputLFLineBreak; + + if (m_composeHTML) { + flags |= nsIDocumentEncoder::OutputFormatted | + nsIDocumentEncoder::OutputDisallowLineBreaking; + } else { + bool flowed, delsp, formatted, disallowBreaks; + GetSerialiserFlags(charset, &flowed, &delsp, &formatted, &disallowBreaks); + if (flowed) + flags |= nsIDocumentEncoder::OutputFormatFlowed; + if (delsp) + flags |= nsIDocumentEncoder::OutputFormatDelSp; + if (formatted) + flags |= nsIDocumentEncoder::OutputFormatted; + if (disallowBreaks) + flags |= nsIDocumentEncoder::OutputDisallowLineBreaking; + // Don't lose NBSP in the plain text encoder. + flags |= nsIDocumentEncoder::OutputPersistNBSP; + } + rv = m_editor->OutputToString(contentType, flags, msgBody); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + m_compFields->GetBody(msgBody); + } + if (!msgBody.IsEmpty()) + { + // Convert body to mail charset + nsCString outCString; + rv = nsMsgI18NConvertFromUnicode(m_compFields->GetCharacterSet(), + msgBody, outCString, false, true); + bool isAsciiOnly = NS_IsAscii(outCString.get()) && + !nsMsgI18Nstateful_charset(m_compFields->GetCharacterSet()); + if (m_compFields->GetForceMsgEncoding()) + isAsciiOnly = false; + if (NS_SUCCEEDED(rv) && !outCString.IsEmpty()) + { + // If the body contains characters outside the repertoire of the current + // charset, just convert to UTF-8 and be done with it + // unless disable_fallback_to_utf8 is set for this charset. + if (NS_ERROR_UENC_NOMAPPING == rv) + { + bool needToCheckCharset; + m_compFields->GetNeedToCheckCharset(&needToCheckCharset); + if (needToCheckCharset) + { + bool disableFallback = false; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (prefBranch) + { + nsCString prefName("mailnews.disable_fallback_to_utf8."); + prefName.Append(m_compFields->GetCharacterSet()); + prefBranch->GetBoolPref(prefName.get(), &disableFallback); + } + if (!disableFallback) + { + CopyUTF16toUTF8(msgBody, outCString); + m_compFields->SetCharacterSet("UTF-8"); + SetDocumentCharset("UTF-8"); + } + } + } + m_compFields->SetBodyIsAsciiOnly(isAsciiOnly); + m_compFields->SetBody(outCString.get()); + } + else + { + m_compFields->SetBody(NS_ConvertUTF16toUTF8(msgBody).get()); + m_compFields->SetCharacterSet("UTF-8"); + SetDocumentCharset("UTF-8"); + } + } + + // 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(NS_MSGCOMPOSEPROGRESSPARAMS_CONTRACTID, &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.xul", + 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/x-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(NS_MSGATTACHMENT_CONTRACTID, &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(NS_LITERAL_STRING("vcard.vcf")); + else + { + // Replace any dot with underscore to stop vCards + // generating false positives with some heuristic scanners + MsgReplaceChar(userid, '.', '_'); + 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; + + rv = SendMsgToServer(deliverMode, identity, accountKey); + if (NS_FAILED(rv)) + { + nsCOMPtr<nsIMsgSendReport> sendReport; + if (mMsgSend) + mMsgSend->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) + { + nsresult theError; + sendReport->DisplayReport(prompt, true, true, &theError); + } + else + { + /* If we come here it's because we got an error before we could intialize a + send report! Let's try our best... + */ + switch (deliverMode) + { + case nsIMsgCompDeliverMode::Later: + nsMsgDisplayMessageByName(prompt, u"unableToSendLater"); + break; + case nsIMsgCompDeliverMode::AutoSaveAsDraft: + case nsIMsgCompDeliverMode::SaveAsDraft: + nsMsgDisplayMessageByName(prompt, u"unableToSaveDraft"); + break; + case nsIMsgCompDeliverMode::SaveAsTemplate: + nsMsgDisplayMessageByName(prompt, u"unableToSaveTemplate"); + break; + + default: + nsMsgDisplayMessageByName(prompt, u"sendFailed"); + break; + } + } + + if (progress) + progress->CloseProgressDialog(true); + } + + return rv; +} + +/* attribute boolean deleteDraft */ +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(NS_MSGCOMPOSESERVICE_CONTRACTID, &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; + } + nsIBaseWindow * window = m_baseWindow; + m_baseWindow = nullptr; + 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; +} + +static nsresult fixCharset(nsCString &aCharset) +{ + // No matter what, we should block x-windows-949 (our internal name) + // from being used for outgoing emails (bug 234958). + if (aCharset.Equals("x-windows-949", nsCaseInsensitiveCStringComparator())) + aCharset = "EUC-KR"; + + // Convert to a canonical charset name. + // Bug 1297118 will revisit this call site. + nsresult rv; + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString charset(aCharset); + rv = ccm->GetCharsetAlias(charset.get(), aCharset); + + // Don't accept UTF-16 ever. UTF-16 should never be selected as an + // outgoing encoding for e-mail. MIME can't handle those messages + // encoded in ASCII-incompatible encodings. + if (NS_FAILED(rv) || + StringBeginsWith(aCharset, NS_LITERAL_CSTRING("UTF-16"))) { + aCharset.AssignLiteral("UTF-8"); + } + return NS_OK; +} + +// This used to be called BEFORE editor was created +// (it did the loadUrl that triggered editor creation) +// It is called from JS after editor creation +// (loadUrl is done in JS) +NS_IMETHODIMP nsMsgCompose::InitEditor(nsIEditor* aEditor, mozIDOMWindowProxy* aContentWindow) +{ + NS_ENSURE_ARG_POINTER(aEditor); + NS_ENSURE_ARG_POINTER(aContentWindow); + nsresult rv; + + m_editor = aEditor; + + nsAutoCString msgCharSet(m_compFields->GetCharacterSet()); + rv = fixCharset(msgCharSet); + NS_ENSURE_SUCCESS(rv, rv); + m_compFields->SetCharacterSet(msgCharSet.get()); + m_editor->SetDocumentCharacterSet(msgCharSet); + + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aContentWindow); + + nsIDocShell *docShell = window->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIContentViewer> childCV; + NS_ENSURE_SUCCESS(docShell->GetContentViewer(getter_AddRefs(childCV)), NS_ERROR_FAILURE); + if (childCV) + { + // SetForceCharacterSet will complain about "UTF-7" or "x-mac-croatian" + // (see test-charset-edit.js), but we deal with this elsewhere. + rv = childCV->SetForceCharacterSet(msgCharSet); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetForceCharacterSet() failed"); + } + + // This is what used to be done in mDocumentListener, + // nsMsgDocumentStateListener::NotifyDocumentCreated() + bool quotingToFollow = false; + GetQuotingToFollow("ingToFollow); + if (quotingToFollow) + return BuildQuotedMessageAndSignature(); + else + { + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK); + rv = BuildBodyMessageAndSignature(); + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK); + return rv; + } +} + +NS_IMETHODIMP nsMsgCompose::GetBodyRaw(nsACString& aBodyRaw) +{ + aBodyRaw.Assign((char *)m_compFields->GetBody()); + return NS_OK; +} + +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; +} + +nsresult nsMsgCompose::SetBodyModified(bool modified) +{ + nsresult rv = NS_OK; + + if (m_editor) + { + if (modified) + { + int32_t modCount = 0; + m_editor->GetModificationCount(&modCount); + if (modCount == 0) + m_editor->IncrementModificationCount(1); + } + else + m_editor->ResetModificationCount(); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::GetDomWindow(mozIDOMWindowProxy * *aDomWindow) +{ + NS_IF_ADDREF(*aDomWindow = m_window); + return NS_OK; +} + +nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields * *aCompFields) +{ + *aCompFields = (nsIMsgCompFields*)m_compFields; + NS_IF_ADDREF(*aCompFields); + 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 char * 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, NS_LITERAL_CSTRING("file:")); + 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.Append("&realtype=message/rfc822"); + } + originalMsgURI = msgUri.get(); + } + + if (compFields) + { + NS_IF_RELEASE(m_compFields); + m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields); + NS_ADDREF(m_compFields); + } + else + { + m_compFields = new nsMsgCompFields(); + if (m_compFields) + NS_ADDREF(m_compFields); + else + return NS_ERROR_OUT_OF_MEMORY; + } + + 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(getter_Copies(curDraftIdURL)); + NS_ASSERTION(NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty(), "CreateMessage can't get draft id"); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) + { + nsCOMPtr <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), 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, getter_Copies(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, getter_Copies(originalMsgURIfromDB)); + mOriginalMsgURI = originalMsgURIfromDB; + if (!queuedDisposition.IsEmpty()) + { + if (queuedDisposition.Equals("replied")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied; + else if (queuedDisposition.Equals("forward")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded; + } + } + } + } + + // If we don't have an original message URI, nothing else to do... + if (!originalMsgURI || *originalMsgURI == 0) + 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 = originalMsgURI; + + 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) + { + // Use charset set up in the compose fields by MIME unless we should + // use the default charset. + bool replyInDefault = false; + prefs->GetBoolPref("mailnews.reply_in_default_charset", + &replyInDefault); + // Use send_default_charset if reply_in_default_charset is on. + if (replyInDefault) + { + nsString str; + nsCString charset; + NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset", + EmptyString(), str); + if (!str.IsEmpty()) + { + LossyCopyUTF16toASCII(str, charset); + m_compFields->SetCharacterSet(charset.get()); + mAnswerDefaultCharset = true; + } + } + + // We want to treat this message as a reference too + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(originalMsgURI, 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.AppendLiteral("<"); + reference.Append(ref); + reference.AppendLiteral("> "); + } + } + reference.Trim(" ", false, true); + } + msgHdr->GetMessageId(getter_Copies(messageId)); + reference.AppendLiteral("<"); + reference.Append(messageId); + reference.AppendLiteral(">"); + m_compFields->SetReferences(reference.get()); + } + + // Early return for "Forward inline" and "Reply with template" processing. + return NS_OK; + } + + // All other processing. + char *uriList = PL_strdup(originalMsgURI); + if (!uriList) + return NS_ERROR_OUT_OF_MEMORY; + + // Resulting charset for this message. + nsCString charset; + + // Check for the charset of the last displayed message, it + // will be used for quoting and as override. + nsCString windowCharset; + mCharsetOverride = false; + mAnswerDefaultCharset = false; + GetTopmostMsgWindowCharacterSet(windowCharset, &mCharsetOverride); + if (!windowCharset.IsEmpty()) { + // Although the charset in which to send the message might change, + // the original message will be parsed for quoting using the charset it is + // now displayed with. + mQuoteCharset = windowCharset; + + if (mCharsetOverride) { + // Use override charset. + charset = windowCharset; + } + } + + // 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 *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(uri, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + } + if (msgHdr) + { + nsCString decodedCString; + + bool replyInDefault = false; + prefs->GetBoolPref("mailnews.reply_in_default_charset", + &replyInDefault); + // Use send_default_charset if reply_in_default_charset is on. + if (replyInDefault) + { + nsString str; + NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset", + EmptyString(), str); + if (!str.IsEmpty()) { + LossyCopyUTF16toASCII(str, charset); + mAnswerDefaultCharset = true; + } + } + + // Set the charset we determined, if any, in the comp fields. + // For replies, the charset will be set after processing the message + // through MIME in QuotingOutputStreamListener::OnStopRequest(). + if (isFirstPass && !charset.IsEmpty()) + m_compFields->SetCharacterSet(charset.get()); + + 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(NS_LITERAL_STRING(" (was:")); + 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(getter_Copies(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::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.Insert(NS_LITERAL_STRING("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(NS_LITERAL_CSTRING("<")); + reference.Append(messageId); + reference.Append(NS_LITERAL_CSTRING(">")); + m_compFields->SetReferences(reference.get()); + } + else + { + nsAutoCString references; + m_compFields->GetReferences(getter_Copies(references)); + references.Append(NS_LITERAL_CSTRING(" <")); + references.Append(messageId); + references.Append(NS_LITERAL_CSTRING(">")); + m_compFields->SetReferences(references.get()); + } + + uint32_t flags; + + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::HasRe) + subject.Insert(NS_LITERAL_STRING("Re: "), 0); + + // Setup quoting callbacks for later... + mQuotingToFollow = false; //We don't need to quote the original message. + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &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::services::GetStringBundleService(); + 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(u"messageAttachmentSafeName", + getter_Copies(sanitizedSubj)); + } + else + sanitizedSubj.Assign(subject); + + // set the file size + uint32_t messageSize; + msgHdr->GetMessageSize(&messageSize); + attachment->SetSize(messageSize); + + // change all '.' to '_' see bug #271211 + MsgReplaceChar(sanitizedSubj, ".", '_'); + if (addExtension) + sanitizedSubj.AppendLiteral(".eml"); + attachment->SetName(sanitizedSubj); + attachment->SetUrl(nsDependentCString(uri)); + m_compFields->AddAttachment(attachment); + } + + if (isFirstPass) + { + nsCString fwdPrefix; + prefs->GetCharPref("mail.forward_subject_prefix", getter_Copies(fwdPrefix)); + if (!fwdPrefix.IsEmpty()) + { + nsString unicodeFwdPrefix; + CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix); + unicodeFwdPrefix.AppendLiteral(": "); + subject.Insert(unicodeFwdPrefix, 0); + } + else + { + subject.Insert(NS_LITERAL_STRING("Fwd: "), 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->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); + break; + } + } + } + isFirstPass = false; + uri = nextUri + 1; + } + while (nextUri); + PR_Free(uriList); + return rv; +} + +NS_IMETHODIMP nsMsgCompose::GetProgress(nsIMsgProgress **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mProgress; + NS_IF_ADDREF(*_retval); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mMsgSend; + NS_IF_ADDREF(*_retval); + 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 char *folderURI) +{ + m_folderName = folderURI; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetSavedFolderURI(char ** folderURI) +{ + NS_ENSURE_ARG_POINTER(folderURI); + *folderURI = ToNewCString(m_folderName); + return (*folderURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompose::GetOriginalMsgURI(char ** originalMsgURI) +{ + NS_ENSURE_ARG_POINTER(originalMsgURI); + *originalMsgURI = ToNewCString(mOriginalMsgURI); + return (*originalMsgURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +//////////////////////////////////////////////////////////////////////////////////// +// THIS IS THE CLASS THAT IS THE STREAM CONSUMER OF THE HTML OUPUT +// FROM LIBMIME. THIS IS FOR QUOTING +//////////////////////////////////////////////////////////////////////////////////// +QuotingOutputStreamListener::~QuotingOutputStreamListener() +{ + if (mUnicodeConversionBuffer) + free(mUnicodeConversionBuffer); +} + +QuotingOutputStreamListener::QuotingOutputStreamListener(const char * originalMsgURI, + nsIMsgDBHdr *originalMsgHdr, + bool quoteHeaders, + bool headersOnly, + nsIMsgIdentity *identity, + nsIMsgQuote* msgQuote, + bool charsetFixed, + bool quoteOriginal, + const nsACString& htmlToQuote) +{ + nsresult rv; + mQuoteHeaders = quoteHeaders; + mHeadersOnly = headersOnly; + mIdentity = identity; + mOrigMsgHdr = originalMsgHdr; + mUnicodeBufferCharacterLength = 0; + mUnicodeConversionBuffer = nullptr; + mQuoteOriginal = quoteOriginal; + mHtmlToQuote = htmlToQuote; + mQuote = msgQuote; + mCharsetFixed = charsetFixed; + + if (!mHeadersOnly || !mHtmlToQuote.IsEmpty()) + { + // Get header type, locale and strings from pref. + int32_t replyHeaderType; + nsAutoString replyHeaderLocale; + nsString replyHeaderAuthorWrote; + nsString replyHeaderOnDateAuthorWrote; + nsString replyHeaderAuthorWroteOnDate; + nsString replyHeaderOriginalmessage; + GetReplyHeaderInfo(&replyHeaderType, + replyHeaderLocale, + 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) + { + nsCOMPtr<nsIDateTimeFormat> dateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + PRTime originalMsgDate; + rv = originalMsgHdr->GetDate(&originalMsgDate); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsILocale> locale; + nsCOMPtr<nsILocaleService> localeService(do_GetService(NS_LOCALESERVICE_CONTRACTID)); + + // Format date using "mailnews.reply_header_locale", if empty then use application default locale. + if (!replyHeaderLocale.IsEmpty()) + rv = localeService->NewLocale(replyHeaderLocale, getter_AddRefs(locale)); + if (NS_SUCCEEDED(rv)) + { + nsAutoString citeDatePart; + if ((placeholderIndex = mCitePrefix.Find("#2")) != kNotFound) + { + rv = dateFormatter->FormatPRTime(locale, + kDateFormatShort, + kTimeFormatNone, + originalMsgDate, + citeDatePart); + if (NS_SUCCEEDED(rv)) + mCitePrefix.Replace(placeholderIndex, 2, citeDatePart); + } + if ((placeholderIndex = mCitePrefix.Find("#3")) != kNotFound) + { + rv = dateFormatter->FormatPRTime(locale, + kDateFormatNone, + kTimeFormatNoSeconds, + originalMsgDate, + citeDatePart); + if (NS_SUCCEEDED(rv)) + mCitePrefix.Replace(placeholderIndex, 2, citeDatePart); + } + } + } + } + } + + if ((placeholderIndex = mCitePrefix.Find("#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 delsp, + bool formatted, + bool disallowBreaks) +{ + nsresult rv = ConvertBufToPlainText(mMsgBody, formatflowed, + delsp, + formatted, + disallowBreaks); + NS_ENSURE_SUCCESS (rv, rv); + return ConvertBufToPlainText(mSignature, formatflowed, + delsp, + formatted, + disallowBreaks); +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnStartRequest(nsIRequest *request, nsISupports * /* ctxt */) +{ + return NS_OK; +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt, 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(NS_MIME_CONVERTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCString charset; + compFields->GetCharacterSet(getter_Copies(charset)); + + if (!mCharsetFixed) { + // Get the charset from the channel where MIME left it. + if (mQuote) { + nsCOMPtr<nsIChannel> quoteChannel; + mQuote->GetQuoteChannel(getter_AddRefs(quoteChannel)); + if (quoteChannel) { + quoteChannel->GetContentCharset(charset); + if (!charset.IsEmpty()) { + rv = fixCharset(charset); + NS_ENSURE_SUCCESS(rv, rv); + compFields->SetCharacterSet(charset.get()); + } + } + } + } + + mHeaders->ExtractHeader(HEADER_FROM, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), from); + + mHeaders->ExtractHeader(HEADER_TO, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), to); + + mHeaders->ExtractHeader(HEADER_CC, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), cc); + + mHeaders->ExtractHeader(HEADER_BCC, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), bcc); + + mHeaders->ExtractHeader(HEADER_MAIL_FOLLOWUP_TO, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), mailFollowupTo); + + mHeaders->ExtractHeader(HEADER_REPLY_TO, false, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), replyTo); + + mHeaders->ExtractHeader(HEADER_MAIL_REPLY_TO, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), 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("<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(EncodedHeader(NS_ConvertUTF16toUTF8(from)), fromEmailAddress); + + nsTArray<nsCString> toEmailAddresses; + ExtractEmails(EncodedHeader(NS_ConvertUTF16toUTF8(to)), + UTF16ArrayAdapter<>(toEmailAddresses)); + + nsTArray<nsCString> ccEmailAddresses; + ExtractEmails(EncodedHeader(NS_ConvertUTF16toUTF8(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(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIArray> identities; + nsCString accountKey; + mOrigMsgHdr->GetAccountKey(getter_Copies(accountKey)); + if (replyToSelfCheckAll) + { + // Check all avaliable identities if the pref was set. + accountManager->GetAllIdentities(getter_AddRefs(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) + account->GetIdentities(getter_AddRefs(identities)); + } + 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) + accountManager->GetIdentitiesForServer(nsIMsgIncomingServer, getter_AddRefs(identities)); + } + } + + bool isReplyToSelf = false; + nsCOMPtr<nsIMsgIdentity> selfIdentity; + if (identities) + { + // Go through the identities to see if any of them is the author of + // the email. + nsCOMPtr<nsIMsgIdentity> lookupIdentity; + + uint32_t count = 0; + identities->GetLength(&count); + + for (uint32_t i = 0; i < count; i++) + { + lookupIdentity = do_QueryElementAt(identities, i, &rv); + if (NS_FAILED(rv)) + continue; + + 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)) + { + 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 (uint32_t j = 0; j < count; j++) + { + nsCOMPtr<nsIMsgIdentity> lookupIdentity2; + rv = identities->QueryElementAt(j, NS_GET_IID(nsIMsgIdentity), + getter_AddRefs(lookupIdentity2)); + if (NS_FAILED(rv)) + continue; + + nsCString curIdentityEmail2; + lookupIdentity2->GetEmail(curIdentityEmail2); + if (toEmailAddresses.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 (ccEmailAddresses.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::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; + nsCOMPtr<nsIPrompt> prompt; + compose->GetDomWindow(getter_AddRefs(domWindow)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> composeWindow = nsPIDOMWindowOuter::From(domWindow); + if (composeWindow) + composeWindow->GetPrompter(getter_AddRefs(prompt)); + nsMsgDisplayMessageByName(prompt, u"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; + 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(NS_MSGCOMPOSESERVICE_CONTRACTID)); + 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, + false, // delsp makes no sense in this context + 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, + nsISupports *ctxt, 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; +} + +NS_IMETHODIMP QuotingOutputStreamListener::AppendToMsgBody(const nsCString &inStr) +{ + nsresult rv = NS_OK; + + if (!inStr.IsEmpty()) + { + // Create unicode decoder. + if (!mUnicodeDecoder) + { + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = ccm->GetUnicodeDecoderRaw("UTF-8", + getter_AddRefs(mUnicodeDecoder)); + } + } + + if (NS_SUCCEEDED(rv)) + { + int32_t unicharLength; + int32_t inputLength = inStr.Length(); + rv = mUnicodeDecoder->GetMaxLength(inStr.get(), inStr.Length(), &unicharLength); + if (NS_SUCCEEDED(rv)) + { + // Use this local buffer if possible. + const int32_t kLocalBufSize = 4096; + char16_t localBuf[kLocalBufSize]; + char16_t *unichars = localBuf; + + if (unicharLength > kLocalBufSize) + { + // Otherwise, use the buffer of the class. + if (!mUnicodeConversionBuffer || + unicharLength > mUnicodeBufferCharacterLength) + { + if (mUnicodeConversionBuffer) + free(mUnicodeConversionBuffer); + mUnicodeConversionBuffer = (char16_t *) moz_xmalloc(unicharLength * sizeof(char16_t)); + if (!mUnicodeConversionBuffer) + { + mUnicodeBufferCharacterLength = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + mUnicodeBufferCharacterLength = unicharLength; + } + unichars = mUnicodeConversionBuffer; + } + + int32_t consumedInputLength = 0; + int32_t originalInputLength = inputLength; + const char *inputBuffer = inStr.get(); + int32_t convertedOutputLength = 0; + int32_t outputBufferLength = unicharLength; + char16_t *originalOutputBuffer = unichars; + do + { + rv = mUnicodeDecoder->Convert(inputBuffer, &inputLength, unichars, &unicharLength); + if (NS_SUCCEEDED(rv)) + { + convertedOutputLength += unicharLength; + break; + } + + // if we failed, we consume one byte, replace it with a question mark + // and try the conversion again. + unichars += unicharLength; + *unichars = (char16_t)'?'; + unichars++; + unicharLength++; + + mUnicodeDecoder->Reset(); + + inputBuffer += ++inputLength; + consumedInputLength += inputLength; + inputLength = originalInputLength - consumedInputLength; // update input length to convert + convertedOutputLength += unicharLength; + unicharLength = outputBufferLength - unicharLength; // update output length + + } while (NS_FAILED(rv) && + (originalInputLength > consumedInputLength) && + (outputBufferLength > convertedOutputLength)); + + if (convertedOutputLength > 0) + mMsgBody.Append(originalOutputBuffer, convertedOutputLength); + } + } + } + + return rv; +} + +nsresult +QuotingOutputStreamListener::SetComposeObj(nsIMsgCompose *obj) +{ + mWeakComposeObj = do_GetWeakReference(obj); + return NS_OK; +} + +nsresult +QuotingOutputStreamListener::SetMimeHeaders(nsIMimeHeaders * headers) +{ + mHeaders = headers; + return NS_OK; +} + +NS_IMETHODIMP +QuotingOutputStreamListener::InsertToCompose(nsIEditor *aEditor, + bool aHTMLEditor) +{ + // First, get the nsIEditor interface for future use + nsCOMPtr<nsIDOMNode> nodeInserted; + + TranslateLineEnding(mMsgBody); + + // Now, insert it into the editor... + if (aEditor) + aEditor->EnableUndo(true); + + nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj); + if (!mMsgBody.IsEmpty() && compose) + { + compose->SetInsertingQuotedContent(true); + if (!mCitePrefix.IsEmpty()) + { + if (!aHTMLEditor) + mCitePrefix.AppendLiteral("\n"); + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(aEditor)); + if (textEditor) + textEditor->InsertText(mCitePrefix); + } + + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(aEditor)); + if (mailEditor) + { + if (aHTMLEditor) { + nsAutoString body(mMsgBody); + remove_plaintext_tag(body); + mailEditor->InsertAsCitedQuotation(body, EmptyString(), true, + getter_AddRefs(nodeInserted)); + } else { + mailEditor->InsertAsQuotation(mMsgBody, getter_AddRefs(nodeInserted)); + } + } + compose->SetInsertingQuotedContent(false); + } + + if (aEditor) + { + nsCOMPtr<nsIPlaintextEditor> textEditor = do_QueryInterface(aEditor); + if (textEditor) + { + nsCOMPtr<nsISelection> selection; + nsCOMPtr<nsIDOMNode> 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->Collapse(parent, offset+1); + // insert a break at current selection + textEditor->InsertLineBreak(); + selection->Collapse(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 comman 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(NS_LITERAL_STRING(".")); + dotDomain.Append(domain); + if (StringEndsWith(aDomain, dotDomain, nsCaseInsensitiveStringComparator())) + return true; + + left = right + 1; + } + return false; +} + +NS_IMPL_ISUPPORTS(QuotingOutputStreamListener, + nsIMsgQuotingOutputStreamListener, + nsIRequestObserver, + nsIStreamListener) + +//////////////////////////////////////////////////////////////////////////////////// +// 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 char *msgURI) +{ + NS_ENSURE_ARG_POINTER(msgURI); + + nsresult rv; + mQuotingToFollow = false; + + // Create a mime parser (nsIStreamConverter)! + mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &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(msgURI, + msgHdr, + false, + !mHtmlToQuote.IsEmpty(), + m_identity, + mQuote, + mCharsetOverride || mAnswerDefaultCharset, + false, + mHtmlToQuote); + + if (!mQuoteStreamListener) + return NS_ERROR_FAILURE; + NS_ADDREF(mQuoteStreamListener); + + mQuoteStreamListener->SetComposeObj(this); + + rv = mQuote->QuoteMessage(msgURI, false, mQuoteStreamListener, + mCharsetOverride ? m_compFields->GetCharacterSet() : "", + false, msgHdr); + return rv; +} + +nsresult +nsMsgCompose::QuoteOriginalMessage() // New template +{ + nsresult rv; + + mQuotingToFollow = false; + + // Create a mime parser (nsIStreamConverter)! + mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &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.get(), getter_AddRefs(originalMsgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool fileUrl = StringBeginsWith(mOriginalMsgURI, NS_LITERAL_CSTRING("file:")); + if (fileUrl) + { + mOriginalMsgURI.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:")); + mOriginalMsgURI.AppendLiteral("?number=0"); + } + + // Create the consumer output stream.. this will receive all the HTML from libmime + mQuoteStreamListener = + new QuotingOutputStreamListener(mOriginalMsgURI.get(), + originalMsgHdr, + mWhatHolder != 1, + !bAutoQuote || !mHtmlToQuote.IsEmpty(), + m_identity, + mQuote, + mCharsetOverride || mAnswerDefaultCharset, + true, + mHtmlToQuote); + + if (!mQuoteStreamListener) + return NS_ERROR_FAILURE; + NS_ADDREF(mQuoteStreamListener); + + mQuoteStreamListener->SetComposeObj(this); + + rv = mQuote->QuoteMessage(mOriginalMsgURI.get(), mWhatHolder != 1, mQuoteStreamListener, + mCharsetOverride ? mQuoteCharset.get() : "", + !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::Draft) + { + nsAutoCString curDraftIdURL; + rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL)); + NS_ENSURE_SUCCESS(rv, rv); + if (!curDraftIdURL.IsEmpty()) { + nsCOMPtr <nsIMsgDBHdr> draftHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(draftHdr)); + NS_ENSURE_SUCCESS(rv, rv); + draftHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(dispositionSetting)); + } + } + + nsMsgKey msgKey; + if (mMsgSend) + { + mMsgSend->GetMessageKey(&msgKey); + nsAutoCString msgUri(m_folderName); + nsCString identityKey; + + m_identity->GetKey(identityKey); + + int32_t insertIndex = StringBeginsWith(msgUri, NS_LITERAL_CSTRING("mailbox")) ? 7 : 4; + msgUri.Insert("-message", insertIndex); // "mailbox/imap: -> "mailbox/imap-message:" + msgUri.Append('#'); + msgUri.AppendInt(msgKey); + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(msgUri.get(), 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.get()); + if (!dispositionSetting.IsEmpty()) + msgHdr->SetStringProperty(QUEUED_DISPOSITION_PROPERTY, dispositionSetting.get()); + } + msgHdr->SetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey.get()); + } + } + 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 || + 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(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; + + 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::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 *aFolderURI) +{ + m_folderName = aFolderURI; + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnGetDraftFolderURI(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; +} + +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); + } + 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 *aFolderURI) +{ + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnGetDraftFolderURI(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); + } + else + { + // Remove (possible) draft if we're in send later mode + if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater || + mDeliverMode == nsIMsgSend::nsMsgDeliverBackground) + { + msgCompose->SetDeleteDraft(true); + RemoveCurrentDraftMessage(msgCompose, true); + } + msgCompose->CloseWindow(); + } + } + msgCompose->ClearMessageSend(); + } + + return rv; +} + +nsresult +nsMsgComposeSendListener::GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> aMsgFolder; + nsCString folderUri; + + rv = compObj->GetSavedFolderURI(getter_Copies(folderUri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIRDFService> rdfService (do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIRDFResource> resource; + rv = rdfService->GetResource(folderUri, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv, rv); + + aMsgFolder = do_QueryInterface(resource, &rv); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*msgFolder = aMsgFolder); + return rv; +} + +nsresult +nsMsgComposeSendListener::RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy) +{ + 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; + nsMsgKey newUid = 0; + nsCString newDraftIdURL; + nsCOMPtr<nsIMsgFolder> msgFolder; + + rv = compFields->GetDraftId(getter_Copies(curDraftIdURL)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get draft id"); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) + { + nsCOMPtr <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage 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), "RemoveCurrentDraftMessage can't get msg folder interface pointer."); + if (NS_FAILED(rv) || !msgFolder) + break; + + // Only do this if it's a drafts folder. + bool isDraft; + msgFolder->GetFlag(nsMsgFolderFlags::Drafts, &isDraft); + if (!isDraft) + 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; + + // Build the msg array. + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't allocate array."); + if (NS_FAILED(rv) || !messageArray) + break; + rv = messageArray->AppendElement(msgDBHdr, false); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't append msg header to array."); + if (NS_FAILED(rv)) + break; + + // Ready to delete the msg. + rv = msgFolder->DeleteMessages(messageArray, nullptr, true, false, nullptr, false /*allowUndo*/); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage 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) + { + const char * str = PL_strchr(curDraftIdURL.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, 1, nullptr); + } + } + } + } + } + } + + // 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) + { + 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) + { + rv = savedToFolder->GenerateMessageURI(newUid, newDraftIdURL); + NS_ENSURE_SUCCESS(rv, rv); + compFields->SetDraftId(newDraftIdURL.get()); + } + } + } + return rv; +} + +nsresult +nsMsgComposeSendListener::SetMessageKey(nsMsgKey aMessageKey) +{ + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::GetMessageId(nsACString& messageId) +{ + return NS_OK; +} + +/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */ +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::services::GetStringBundleService(); + 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(u"msgCancelling", getter_Copies(msg)); + progress->OnStatusChange(nullptr, nullptr, NS_OK, msg.get()); + } + } + + nsCOMPtr<nsIMsgSend> msgSend; + msgCompose->GetMessageSend(getter_AddRefs(msgSend)); + if (msgSend) + msgSend->Abort(); + } + } + return NS_OK; +} + +/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */ +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; +} + +/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + /* Ignore this call */ + return NS_OK; +} + +/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + /* Ignore this call */ + return NS_OK; +} + +/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + /* 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, 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. + // + char16_t *escaped = MsgEscapeHTML2(origBuf.get(), origBuf.Length()); + if (escaped) + { + aSigData.Append(escaped); + NS_Free(escaped); + } + else + aSigData.Append(origBuf); + return NS_OK; +} + +nsresult +nsMsgCompose::LoadDataFromFile(nsIFile *file, nsString &sigData, + bool aAllowUTF8, bool aAllowUTF16) +{ + int32_t readSize; + uint32_t nGot; + char *readBuf; + char *ptr; + + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory) { + NS_ERROR("file is a directory"); + return NS_MSG_ERROR_READING_FILE; + } + + + nsCOMPtr <nsIInputStream> inputFile; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), file); + if (NS_FAILED(rv)) + return NS_MSG_ERROR_READING_FILE; + + int64_t fileSize; + file->GetFileSize(&fileSize); + readSize = (uint32_t) fileSize; + + + ptr = readBuf = (char *)PR_Malloc(readSize + 1); if (!readBuf) + return NS_ERROR_OUT_OF_MEMORY; + memset(readBuf, 0, readSize + 1); + + while (readSize) { + inputFile->Read(ptr, readSize, &nGot); + if (nGot) { + readSize -= nGot; + ptr += nGot; + } + else { + readSize = 0; + } + } + inputFile->Close(); + + readSize = (uint32_t) fileSize; + + nsAutoCString sigEncoding(nsMsgI18NParseMetaCharset(file)); + bool removeSigCharset = !sigEncoding.IsEmpty() && m_composeHTML; + + if (sigEncoding.IsEmpty()) { + if (aAllowUTF8 && MsgIsUTF8(nsDependentCString(readBuf))) { + sigEncoding.Assign("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.Assign("UTF-16"); + } + else { + //default to platform encoding for plain text files w/o meta charset + nsAutoCString textFileCharset; + nsMsgI18NTextFileCharset(textFileCharset); + sigEncoding.Assign(textFileCharset); + } + } + + nsAutoCString readStr(readBuf, (int32_t) fileSize); + PR_FREEIF(readBuf); + + // XXX: ^^^ could really use nsContentUtils::SlurpFileToString instead! + + if (NS_FAILED(ConvertToUnicode(sigEncoding.get(), readStr, sigData))) + CopyASCIItoUTF16(readStr, sigData); + + //remove sig meta charset to allow user charset override during composition + if (removeSigCharset) + { + nsAutoCString metaCharset("charset="); + metaCharset.Append(sigEncoding); + int32_t pos = sigData.Find(metaCharset.BeginReading(), true); + 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(nsAutoString &aData) +{ + int32_t fPos; + int32_t offset = -1; + while ((fPos = aData.RFind("file://", true, offset)) != kNotFound) { + if (fPos != kNotFound && fPos > 0) { + char16_t q = aData.CharAt(fPos - 1); + bool 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 this one failed, maybe because the file wasn't found, + // continue to process the next one. + if (NS_SUCCEEDED(rv)) { + aData.Replace(fPos, end - fPos, dataURL); + } + offset = fPos - 1; + } + } + 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 = nsContentUtils::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::BuildQuotedMessageAndSignature(void) +{ + // + // This should never happen...if it does, just bail out... + // + NS_ASSERTION(m_editor, "BuildQuotedMessageAndSignature but no editor!\n"); + 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). + + nsAutoCString sigNativePath; + 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) { + rv = sigFile->GetNativePath(sigNativePath); + if (NS_SUCCEEDED(rv) && !sigNativePath.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, NS_LITERAL_CSTRING("image/"), 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, false, true, true); + sigData.Append(prefSigText); + } + else + { + if (!htmlSig) + { + char16_t* escaped = MsgEscapeHTML2(prefSigText.get(), prefSigText.Length()); + if (escaped) + { + sigData.Append(escaped); + NS_Free(escaped); + } + else + sigData.Append(prefSigText); + } + 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("\r-- \r", true) < 0 && + sigData.Find("\n-- \n", true) < 0 && + sigData.Find("\n-- \r", true) < 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 wont. Let's figure that 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 : + 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)) + MsgReplaceSubstring(body, NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("<br>")); + + // 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, NS_LITERAL_STRING("-- \r")) || + StringBeginsWith(s, NS_LITERAL_STRING("-- \n"))) + { + i += 4; + continue; + } + if (StringBeginsWith(s, NS_LITERAL_STRING("- -- \r")) || + StringBeginsWith(s, NS_LITERAL_STRING("- -- \n"))) + { + 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) +{ + + if (aNotificationType == nsIMsgComposeNotificationType::SaveInFolderDone) + ResetUrisForEmbeddedObjects(); + + 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: + NS_NOTREACHED("Unknown notification"); + break; + } + } + + return NS_OK; +} + +nsresult nsMsgCompose::AttachmentPrettyName(const nsACString & scheme, const char* charset, nsACString& _retval) +{ + nsresult rv; + + if (MsgLowerCaseEqualsLiteral(StringHead(scheme, 5), "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; + } + + // To work around a mysterious bug in VC++ 6. + const char* cset = (!charset || !*charset) ? "UTF-8" : charset; + + nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString retUrl; + rv = textToSubURI->UnEscapeURIForUI(nsDependentCString(cset), scheme, retUrl); + + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(retUrl, _retval); + } else { + _retval.Assign(scheme); + } + if (MsgLowerCaseEqualsLiteral(StringHead(scheme, 5), "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; + if (aDirUri.EqualsLiteral(kMDBDirectoryRoot)) + collectedAddressbookFound = false; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(aDirUri, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> subDirectories; + if (NS_SUCCEEDED(directory->GetChildNodes(getter_AddRefs(subDirectories))) && subDirectories) + { + nsCOMPtr<nsISupports> item; + bool hasMore; + while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore) + { + if (NS_SUCCEEDED(subDirectories->GetNext(getter_AddRefs(item)))) + { + directory = do_QueryInterface(item, &rv); + if (NS_SUCCEEDED(rv)) + { + bool bIsMailList; + + if (NS_SUCCEEDED(directory->GetIsMailList(&bIsMailList)) && bIsMailList) + { + aMailListArray.AppendElement(directory); + continue; + } + + nsCString uri; + rv = directory->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(directory, pos); + rv = GetABDirAndMailLists(uri, aDirArray, aMailListArray); + } + } + } + } + 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; + + nsCOMPtr<nsIMutableArray> mailListAddresses; + rv = aMailList->GetAddressLists(getter_AddRefs(mailListAddresses)); + if (NS_FAILED(rv)) + return rv; + + uint32_t nbrAddresses = 0; + mailListAddresses->GetLength(&nbrAddresses); + for (uint32_t i = 0; i < nbrAddresses; i++) + { + nsCOMPtr<nsIAbCard> existingCard(do_QueryElementAt(mailListAddresses, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + 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(NS_LITERAL_CSTRING(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 rv; +} + +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; +} + +/** + * This function implements the decision logic for delivery format 'Auto-Detect', + * including optional 'Auto-Downgrade' behaviour for HTML messages considered + * convertible (silent, "lossless" conversion to plain text). + * @param aConvertible the result of analysing message body convertibility: + * nsIMsgCompConvertible::Plain | Yes | Altering | No + * @return nsIMsgCompSendFormat::AskUser | PlainText | HTML | Both + */ +NS_IMETHODIMP +nsMsgCompose::DetermineHTMLAction(int32_t aConvertible, int32_t *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsresult rv; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // *** Message-centric Auto-Downgrade *** + // If the message has practically no HTML formatting, + // AND if user accepts auto-downgrading (send options pref), + // bypass auto-detection of recipients' preferences and just + // send the message as plain text (silent, "lossless" conversion); + // which will also avoid asking for newsgroups for this typical scenario. + bool autoDowngrade = true; + rv = prefBranch->GetBoolPref("mailnews.sendformat.auto_downgrade", &autoDowngrade); + NS_ENSURE_SUCCESS(rv, rv); + if (autoDowngrade && (aConvertible == nsIMsgCompConvertible::Plain)) + { + *result = nsIMsgCompSendFormat::PlainText; + return NS_OK; + } + + // *** Newsgroups *** + // Right now, we don't have logic for newsgroups for intelligent send + // preferences. Therefore, bail out early and save us a lot of work if there + // are newsgroups. + + nsAutoString newsgroups; + m_compFields->GetNewsgroups(newsgroups); + + if (!newsgroups.IsEmpty()) + { + *result = nsIMsgCompSendFormat::AskUser; + return NS_OK; + } + + // *** Recipient-Centric Auto-Detect *** + + RecipientsArray recipientsList; + rv = LookupAddressBook(recipientsList); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally return the list of non-HTML recipients if requested and/or rebuilt + // the recipient field. Also, check for domain preference when preferFormat + // is unknown. + nsString plaintextDomains; + nsString htmlDomains; + + if (prefBranch) + { + NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.plaintext_domains", + EmptyString(), plaintextDomains); + NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.html_domains", + EmptyString(), htmlDomains); + } + + // allHTML and allPlain are summary recipient scopes of format preference + // according to address book and send options for recipient-centric Auto-Detect, + // used by Auto-Detect to determine the appropriate message delivery format. + + // allHtml: All recipients prefer HTML. + bool allHtml = true; + + // allPlain: All recipients prefer Plain Text. + bool allPlain = true; + + // Exit the loop early if allHtml and allPlain both decay to false to save us + // some work. + for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY && (allHtml || allPlain); ++i) + { + uint32_t nbrRecipients = recipientsList[i].Length(); + for (uint32_t j = 0; j < nbrRecipients && (allHtml || allPlain); ++j) + { + nsMsgRecipient &recipient = recipientsList[i][j]; + uint32_t preferFormat = nsIAbPreferMailFormat::unknown; + if (recipient.mCard) + { + recipient.mCard->GetPropertyAsUint32(kPreferMailFormatProperty, + &preferFormat); + } + + // if we don't have a prefer format for a recipient, check the domain in + // case we have a format defined for it + if (preferFormat == nsIAbPreferMailFormat::unknown && + (!plaintextDomains.IsEmpty() || !htmlDomains.IsEmpty())) + { + int32_t atPos = recipient.mEmail.FindChar('@'); + if (atPos < 0) + continue; + + nsDependentSubstring emailDomain = Substring(recipient.mEmail, + atPos + 1); + if (IsInDomainList(emailDomain, plaintextDomains)) + preferFormat = nsIAbPreferMailFormat::plaintext; + else if (IsInDomainList(emailDomain, htmlDomains)) + preferFormat = nsIAbPreferMailFormat::html; + } + + // Determine the delivery format preference of this recipient and adjust + // the summary recipient scopes of the message accordingly. + switch (preferFormat) + { + case nsIAbPreferMailFormat::html: + allPlain = false; + break; + + case nsIAbPreferMailFormat::plaintext: + allHtml = false; + break; + + default: // nsIAbPreferMailFormat::unknown + allHtml = false; + allPlain = false; + break; + } + } + } + + // Here's the final part of recipient-centric Auto-Detect logic where we set + // the actual send format (aka delivery format) after analysing recipients' + // format preferences above. + + // If all recipients prefer HTML, then return HTML. + if (allHtml) + { + *result = nsIMsgCompSendFormat::HTML; + return NS_OK; + } + + // If all recipients prefer plaintext, silently strip *all* HTML formatting, + // regardless of (non-)convertibility, and send the message as plaintext. + // **ToDo: UX-error-prevention, UX-wysiwyg: warn against dataloss potential.** + if (allPlain) + { + *result = nsIMsgCompSendFormat::PlainText; + return NS_OK; + } + + // Otherwise, check the preference to see what action we should default to. + // This pref covers all recipient scopes involving prefers-plain (except allplain) + // and prefers-unknown. So we are mixing format conflict resolution options for + // prefers-plain with default format setting for prefers-unknown; not ideal. + int32_t action = nsIMsgCompSendFormat::AskUser; + rv = prefBranch->GetIntPref("mail.default_html_action", &action); + NS_ENSURE_SUCCESS(rv, rv); + + // If the action is a known send format, return the value to send in that format. + // Otherwise, ask the user. + // Note that the preference may default to 0 (Ask), which is not a valid value + // for the following enum. + if (action == nsIMsgCompSendFormat::PlainText || + action == nsIMsgCompSendFormat::HTML || + action == nsIMsgCompSendFormat::Both) + { + *result = action; + return NS_OK; + } + + // At this point, ask the user. + *result = nsIMsgCompSendFormat::AskUser; + return NS_OK; +} + +/* Decides which tags trigger which convertible mode, i.e. here is the logic + for BodyConvertible */ +// Helper function. Parameters are not checked. +nsresult nsMsgCompose::TagConvertible(nsIDOMElement *node, int32_t *_retval) +{ + nsresult rv; + + *_retval = nsIMsgCompConvertible::No; + + uint16_t nodeType; + rv = node->GetNodeType(&nodeType); + if (NS_FAILED(rv)) + return rv; + + nsAutoString element; + rv = node->GetNodeName(element); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIDOMNode> pItem; + + // style attribute on any element can change layout in any way, so that is not convertible. + nsAutoString attribValue; + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("style"), attribValue)) && + !attribValue.IsEmpty()) + { + *_retval = nsIMsgCompConvertible::No; + return NS_OK; + } + + // moz-* classes are used internally by the editor and mail composition + // (like moz-cite 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). + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("class"), attribValue)) && + !attribValue.IsEmpty() && + !StringBeginsWith(attribValue, NS_LITERAL_STRING("moz-"), nsCaseInsensitiveStringComparator())) + { + *_retval = nsIMsgCompConvertible::No; + return NS_OK; + } + // ID attributes can contain attached style/context or be target of links + // so we should preserve them. + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("id"), attribValue)) && + !attribValue.IsEmpty()) + { + *_retval = nsIMsgCompConvertible::No; + return NS_OK; + } + if ( // some "simple" elements without "style" attribute + element.LowerCaseEqualsLiteral("br") || + element.LowerCaseEqualsLiteral("p") || + element.LowerCaseEqualsLiteral("pre") || + 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") || + ( + 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; + + bool hasAttribute; + nsAutoString color; + if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("background"), &hasAttribute)) + && hasAttribute) // There is a background image + *_retval = nsIMsgCompConvertible::No; + else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("text"), &hasAttribute)) && + hasAttribute && + NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("text"), color)) && + !color.EqualsLiteral("#000000")) { + *_retval = nsIMsgCompConvertible::Altering; + } + else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("bgcolor"), &hasAttribute)) && + hasAttribute && + NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("bgcolor"), color)) && + !color.LowerCaseEqualsLiteral("#ffffff")) { + *_retval = nsIMsgCompConvertible::Altering; + } + else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("dir"), &hasAttribute)) + && hasAttribute) // dir=rtl attributes should not downconvert + *_retval = nsIMsgCompConvertible::No; + + //ignore special color setting for link, vlink and alink at this point. + } + else if (element.LowerCaseEqualsLiteral("blockquote")) + { + // Skip <blockquote type="cite"> + *_retval = nsIMsgCompConvertible::Yes; + + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("type"), attribValue)) && + 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; + bool hasChild; + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("href"), hrefValue)) && + NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild) + { + nsCOMPtr<nsIDOMNodeList> children; + if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children))) && + children && + NS_SUCCEEDED(children->Item(0, getter_AddRefs(pItem))) && + pItem) + { + nsAutoString textValue; + if (NS_SUCCEEDED(pItem->GetNodeValue(textValue)) && + 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; + } + } + + return rv; +} + +nsresult nsMsgCompose::_NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval) +{ + NS_ENSURE_TRUE(node && _retval, NS_ERROR_NULL_POINTER); + + nsresult rv; + int32_t result; + + // Check this node + rv = TagConvertible(node, &result); + if (NS_FAILED(rv)) + return rv; + + // Walk tree recursively to check the children + bool hasChild; + if (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild) + { + nsCOMPtr<nsIDOMNodeList> children; + if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children))) + && children) + { + uint32_t nbrOfElements; + rv = children->GetLength(&nbrOfElements); + for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < nbrOfElements; i++) + { + nsCOMPtr<nsIDOMNode> pItem; + if (NS_SUCCEEDED(children->Item(i, getter_AddRefs(pItem))) + && pItem) + { + // We assume all nodes that are not elements are convertible, + // so only test elements. + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(pItem); + if (domElement) { + int32_t curresult; + rv = _NodeTreeConvertible(domElement, &curresult); + + if (NS_SUCCEEDED(rv) && curresult > result) + result = curresult; + } + } + } + } + } + + *_retval = result; + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::BodyConvertible(int32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(m_editor); + + nsCOMPtr<nsIDOMDocument> rootDocument; + nsresult rv = m_editor->GetDocument(getter_AddRefs(rootDocument)); + if (NS_FAILED(rv) || !rootDocument) + return rv; + + // get the top level element, which contains <html> + nsCOMPtr<nsIDOMElement> rootElement; + rv = rootDocument->GetDocumentElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) + return rv; + + return _NodeTreeConvertible(rootElement, _retval); +} + +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) +{ + nsCOMPtr<nsIDOMElement> rootElement; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + nsCOMPtr<nsIDOMNode> node; + nsAutoString attributeName; + nsAutoString attributeValue; + nsAutoString tagLocalName; + attributeName.AssignLiteral("class"); + + rv = rootElement->GetFirstChild(getter_AddRefs(node)); + while (NS_SUCCEEDED(rv) && node) { + nsCOMPtr<nsIDOMElement> 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. + node->GetLocalName(tagLocalName); + 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.Find("moz-cite-prefix", true) != kNotFound) { + break; + } + + // Next check for forwarded content. + // The forwarded part is inside an element with + // class="moz-forward-container". + if (attributeValue.Find("moz-forward-container", true) != kNotFound) { + break; + } + } + + rv = node->GetNextSibling(getter_AddRefs(node)); + if (NS_FAILED(rv) || !node) { + // No further siblings found, so we didn't find what we were looking for. + rv = NS_OK; + node = nullptr; + break; + } + } + + // Now position. If no quote was found, we position to the very front. + int32_t offset = 0; + if (node) { + rv = GetChildOffset(node, rootElement, offset); + if (NS_FAILED(rv)) { + return rv; + } + } + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) + rv = selection->Collapse(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) +{ + nsCOMPtr<nsIDOMElement> rootElement; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) + rv = selection->Collapse(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; + nsCOMPtr<nsIDOMElement> rootElement; + nsCOMPtr<nsIDOMNode> lastNode; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + rv = rootElement->GetLastChild(getter_AddRefs(lastNode)); + if (NS_FAILED(rv) || !lastNode) { + return rv; + } + + rv = GetChildOffset(lastNode, rootElement, offset); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) + rv = selection->Collapse(rootElement, offset + 1); + + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::SetIdentity(nsIMsgIdentity *aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + + m_identity = aIdentity; + + nsresult rv; + + if (! m_editor) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDOMElement> 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<nsIDOMNode> lastNode; + nsCOMPtr<nsIDOMNode> node; + nsCOMPtr<nsIDOMNode> tempNode; + nsAutoString tagLocalName; + + rv = rootElement->GetLastChild(getter_AddRefs(lastNode)); + if (NS_SUCCEEDED(rv) && lastNode) + { + node = lastNode; + // In html, the signature is inside an element with + // class="moz-signature" + bool signatureFound = false; + nsAutoString attributeName; + attributeName.AssignLiteral("class"); + + do + { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node); + if (element) + { + nsAutoString attributeValue; + + rv = element->GetAttribute(attributeName, attributeValue); + + if (attributeValue.Find("moz-signature", true) != kNotFound) { + signatureFound = true; + break; + } + } + } while (!signatureFound && + node && + NS_SUCCEEDED(node->GetPreviousSibling(getter_AddRefs(node)))); + + if (signatureFound) + { + m_editor->BeginTransaction(); + node->GetPreviousSibling(getter_AddRefs(tempNode)); + rv = m_editor->DeleteNode(node); + if (NS_FAILED(rv)) + { + m_editor->EndTransaction(); + return rv; + } + + // Also, remove the <br> right before the signature. + if (tempNode) + { + tempNode->GetLocalName(tagLocalName); + if (tagLocalName.EqualsLiteral("br")) + m_editor->DeleteNode(tempNode); + } + m_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); + + m_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) { + nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor)); + rv = htmlEditor->InsertHTML(aSignature); + } else { + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor)); + rv = textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, NS_LITERAL_STRING("moz-signature")); + } + } + m_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; +} + +nsMsgMailList::nsMsgMailList(nsIAbDirectory* directory) : + mDirectory(directory) +{ + mDirectory->GetDirName(mName); + mDirectory->GetDescription(mDescription); + + if (mDescription.IsEmpty()) + mDescription = mName; + + mDirectory = directory; +} |