Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsMsgCompose.h"
#include "mozilla/dom/Document.h"
#include "nsPIDOMWindow.h"
#include "mozIDOMWindow.h"
#include "nsISelectionController.h"
#include "nsMsgI18N.h"
#include "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 "nsIHTMLEditor.h"
#include "nsIEditor.h"
#include "plstr.h"
#include "prmem.h"
#include "nsIDocShell.h"
#include "nsAbBaseCID.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 "DateTimeFormat.h"
#include "nsIMsgComposeService.h"
#include "nsIMsgComposeProgressParams.h"
#include "nsMsgUtils.h"
#include "nsIMsgImapMailFolder.h"
#include "nsImapCore.h"
#include "nsUnicharUtils.h"
#include "nsNetUtil.h"
#include "nsIContentViewer.h"
#include "nsIMsgMdnGenerator.h"
#include "plbase64.h"
#include "nsIMsgAccountManager.h"
#include "nsIMsgAttachment.h"
#include "nsIMsgProgress.h"
#include "nsMsgFolderFlags.h"
#include "nsMsgMessageFlags.h"
#include "nsIMsgDatabase.h"
#include "nsStringStream.h"
#include "nsIMutableArray.h"
#include "nsArrayUtils.h"
#include "nsIMsgWindow.h"
#include "nsITextToSubURI.h"
#include "nsIAbManager.h"
#include "nsCRT.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/Services.h"
#include "mozilla/mailnews/MimeHeaderParser.h"
#include "mozilla/Preferences.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/HTMLAnchorElement.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/Utf8.h"
#include "nsStreamConverter.h"
#include "nsIObserverService.h"
#include "nsIProtocolHandler.h"
#include "nsContentUtils.h"
#include "nsIFileURL.h"
#include "nsTextNode.h" // from dom/base
#include "nsIParserUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::mailnews;
static nsresult GetReplyHeaderInfo(int32_t* reply_header_type,
nsString& reply_header_authorwrote,
nsString& reply_header_ondateauthorwrote,
nsString& reply_header_authorwroteondate,
nsString& reply_header_originalmessage) {
nsresult rv;
*reply_header_type = 0;
nsCOMPtr<nsIPrefBranch> prefBranch(
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
// If fetching any of the preferences fails,
// we return early with header_type = 0 meaning "no header".
rv = NS_GetLocalizedUnicharPreference(
prefBranch, "mailnews.reply_header_authorwrotesingle",
reply_header_authorwrote);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_GetLocalizedUnicharPreference(
prefBranch, "mailnews.reply_header_ondateauthorwrote",
reply_header_ondateauthorwrote);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_GetLocalizedUnicharPreference(
prefBranch, "mailnews.reply_header_authorwroteondate",
reply_header_authorwroteondate);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_GetLocalizedUnicharPreference(prefBranch,
"mailnews.reply_header_originalmessage",
reply_header_originalmessage);
NS_ENSURE_SUCCESS(rv, rv);
return prefBranch->GetIntPref("mailnews.reply_header_type",
reply_header_type);
}
static void TranslateLineEnding(nsString& data) {
char16_t* rPtr; // Read pointer
char16_t* wPtr; // Write pointer
char16_t* sPtr; // Start data pointer
char16_t* ePtr; // End data pointer
rPtr = wPtr = sPtr = data.BeginWriting();
ePtr = rPtr + data.Length();
while (rPtr < ePtr) {
if (*rPtr == nsCRT::CR) {
*wPtr = nsCRT::LF;
if (rPtr + 1 < ePtr && *(rPtr + 1) == nsCRT::LF) rPtr++;
} else
*wPtr = *rPtr;
rPtr++;
wPtr++;
}
data.SetLength(wPtr - sPtr);
}
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() {}
/* the following macro actually implement addref, release and query interface
* for our component. */
NS_IMPL_ISUPPORTS(nsMsgCompose, nsIMsgCompose, nsIMsgSendListener,
nsISupportsWeakReference)
//
// Once we are here, convert the data which we know to be UTF-8 to UTF-16
// for insertion into the editor
//
nsresult GetChildOffset(nsINode* aChild, nsINode* aParent, int32_t& aOffset) {
NS_ASSERTION((aChild && aParent), "bad args");
if (!aChild || !aParent) return NS_ERROR_NULL_POINTER;
nsINodeList* childNodes = aParent->ChildNodes();
for (uint32_t i = 0; i < childNodes->Length(); i++) {
nsINode* childNode = childNodes->Item(i);
if (childNode == aChild) {
aOffset = i;
return NS_OK;
}
}
return NS_ERROR_NULL_POINTER;
}
nsresult GetNodeLocation(nsINode* inChild, nsCOMPtr<nsINode>* outParent,
int32_t* outOffset) {
NS_ASSERTION((outParent && outOffset), "bad args");
nsresult result = NS_ERROR_NULL_POINTER;
if (inChild && outParent && outOffset) {
nsCOMPtr<nsINode> inChild2 = inChild;
*outParent = inChild2->GetParentNode();
if (*outParent) {
result = GetChildOffset(inChild2, *outParent, *outOffset);
}
}
return result;
}
bool nsMsgCompose::IsEmbeddedObjectSafe(const char* originalScheme,
const char* originalHost,
const char* originalPath,
Element* element) {
nsresult rv;
nsAutoString objURL;
if (!originalScheme || !originalPath) // Having a null host is OK.
return false;
RefPtr<HTMLImageElement> image = HTMLImageElement::FromNode(element);
RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(element);
if (image)
image->GetSrc(objURL);
else if (anchor)
anchor->GetHref(objURL);
else
return false;
if (!objURL.IsEmpty()) {
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), objURL);
if (NS_SUCCEEDED(rv) && uri) {
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
if (NS_SUCCEEDED(rv) &&
scheme.Equals(originalScheme, nsCaseInsensitiveCStringComparator)) {
nsAutoCString host;
rv = uri->GetAsciiHost(host);
// mailbox url don't have a host therefore don't be too strict.
if (NS_SUCCEEDED(rv) &&
(host.IsEmpty() || originalHost ||
host.Equals(originalHost, nsCaseInsensitiveCStringComparator))) {
nsAutoCString path;
rv = uri->GetPathQueryRef(path);
if (NS_SUCCEEDED(rv)) {
nsAutoCString orgPath(originalPath);
MsgRemoveQueryPart(orgPath);
MsgRemoveQueryPart(path);
// mailbox: and JS Account URLs have a message number in
// the query part of "path query ref". We removed this so
// we're not comparing down to the message but down to the folder.
// Code in the frontend (in the "error" event listener in
// MsgComposeCommands.js that deals with unblocking images) will
// prompt if a part of another message is referenced.
// A saved message opened for reply or forwarding has a
// mailbox: URL.
// imap: URLs don't have the message number in the query, so we do
// compare it here.
// news: URLs use group and key in the query, but it's OK to compare
// without them.
return path.Equals(orgPath, nsCaseInsensitiveCStringComparator);
}
}
}
}
}
return false;
}
/* 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() {
uint32_t numNodes;
uint32_t i;
if (!m_editor) return NS_ERROR_FAILURE;
nsCOMPtr<Document> document;
m_editor->GetDocument(getter_AddRefs(document));
if (!document) return NS_ERROR_FAILURE;
nsCOMPtr<nsIArray> aNodeList = GetEmbeddedObjects(document);
if (!aNodeList) return NS_ERROR_FAILURE;
if (NS_FAILED(aNodeList->GetLength(&numNodes))) return NS_ERROR_FAILURE;
nsCString curDraftIdURL;
nsresult 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);
RefPtr<Element> domElement;
for (i = 0; i < numNodes; i++) {
domElement = do_QueryElementAt(aNodeList, i);
if (!domElement) continue;
RefPtr<HTMLImageElement> image = HTMLImageElement::FromNode(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:
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, nullptr, getter_AddRefs(newUrl));
if (NS_FAILED(rv) || !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);
IgnoredErrorResult rv2;
image->SetSrc(newSrc, rv2);
}
}
}
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(nsIEditor* aEditor) {
nsresult rv = NS_OK;
uint32_t count;
uint32_t i;
if (!aEditor) return NS_ERROR_FAILURE;
nsCOMPtr<Document> document;
aEditor->GetDocument(getter_AddRefs(document));
if (!document) return NS_ERROR_FAILURE;
nsCOMPtr<nsIArray> aNodeList = GetEmbeddedObjects(document);
if (!aNodeList) return NS_ERROR_FAILURE;
if (NS_FAILED(aNodeList->GetLength(&count))) return NS_ERROR_FAILURE;
nsCOMPtr<nsIURI> originalUrl;
nsCString originalScheme;
nsCString originalHost;
nsCString originalPath;
// first, convert the rdf original msg uri into a url that represents the
// message...
nsCOMPtr<nsIMsgMessageService> msgService;
rv = GetMessageServiceFromURI(mOriginalMsgURI, getter_AddRefs(msgService));
if (NS_SUCCEEDED(rv)) {
rv = msgService->GetUrlForUri(mOriginalMsgURI, nullptr,
getter_AddRefs(originalUrl));
if (NS_SUCCEEDED(rv) && originalUrl) {
originalUrl->GetScheme(originalScheme);
originalUrl->GetAsciiHost(originalHost);
originalUrl->GetPathQueryRef(originalPath);
}
}
// Then compare the url of each embedded objects with the original message.
// If they a not coming from the original message, they should not be sent
// with the message.
for (i = 0; i < count; i++) {
nsCOMPtr<Element> domElement = do_QueryElementAt(aNodeList, i);
if (!domElement) continue;
if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(),
originalPath.get(), domElement))
continue; // Don't need to tag this object, it's safe to send it.
// The source of this object should not be sent with the message.
IgnoredErrorResult rv2;
domElement->SetAttribute(u"moz-do-not-send"_ns, u"true"_ns, rv2);
}
return NS_OK;
}
NS_IMETHODIMP
nsMsgCompose::GetInsertingQuotedContent(bool* aInsertingQuotedText) {
NS_ENSURE_ARG_POINTER(aInsertingQuotedText);
*aInsertingQuotedText = mInsertingQuotedContent;
return NS_OK;
}
NS_IMETHODIMP
nsMsgCompose::SetInsertingQuotedContent(bool aInsertingQuotedText) {
mInsertingQuotedContent = aInsertingQuotedText;
return NS_OK;
}
MOZ_CAN_RUN_SCRIPT void nsMsgCompose::InsertDivWrappedTextAtSelection(
const nsAString& aText, const nsAString& classStr) {
NS_ASSERTION(m_editor,
"InsertDivWrappedTextAtSelection called, but no editor exists");
if (!m_editor) return;
RefPtr<Element> divElem;
nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor));
nsresult rv =
htmlEditor->CreateElementWithDefaults(u"div"_ns, getter_AddRefs(divElem));
NS_ENSURE_SUCCESS_VOID(rv);
// We need the document
nsCOMPtr<Document> doc;
rv = m_editor->GetDocument(getter_AddRefs(doc));
NS_ENSURE_SUCCESS_VOID(rv);
// Break up the text by newlines, and then insert text nodes followed
// by <br> nodes.
int32_t start = 0;
int32_t end = aText.Length();
for (;;) {
int32_t delimiter = aText.FindChar('\n', start);
if (delimiter == kNotFound) delimiter = end;
RefPtr<nsTextNode> textNode =
doc->CreateTextNode(Substring(aText, start, delimiter - start));
IgnoredErrorResult rv2;
divElem->AppendChild(*textNode, rv2);
if (rv2.Failed()) {
return;
}
// Now create and insert a BR
RefPtr<Element> brElem;
rv =
htmlEditor->CreateElementWithDefaults(u"br"_ns, getter_AddRefs(brElem));
NS_ENSURE_SUCCESS_VOID(rv);
divElem->AppendChild(*brElem, rv2);
if (rv2.Failed()) {
return;
}
if (delimiter == end) break;
start = ++delimiter;
if (start == end) break;
}
htmlEditor->InsertElementAtSelection(divElem, true);
nsCOMPtr<nsINode> parent;
int32_t offset;
rv = GetNodeLocation(divElem, address_of(parent), &offset);
if (NS_SUCCEEDED(rv)) {
RefPtr<Selection> selection;
m_editor->GetSelection(getter_AddRefs(selection));
if (selection) selection->CollapseInLimiter(parent, offset + 1);
}
if (divElem) {
RefPtr<Element> divElem2 = divElem;
IgnoredErrorResult rv2;
divElem2->SetAttribute(u"class"_ns, classStr, rv2);
}
}
/*
* The following function replaces <plaintext> tags with <x-plaintext>.
* <plaintext> is a funny beast: It leads to everything following it
* being displayed verbatim, even a </plaintext> tag is ignored.
*/
static void remove_plaintext_tag(nsString& body) {
// Replace all <plaintext> and </plaintext> tags.
int32_t index = 0;
bool replaced = false;
while ((index = body.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;
}
}
}
static void remove_conditional_CSS(const nsAString& in, nsAString& out) {
nsCOMPtr<nsIParserUtils> parserUtils =
do_GetService(NS_PARSERUTILS_CONTRACTID);
parserUtils->Sanitize(in, nsIParserUtils::SanitizerRemoveOnlyConditionalCSS,
out);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix, nsString& aBuf,
nsString& aSignature, bool aQuoted,
bool aHTMLEditor) {
NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor");
NS_ENSURE_TRUE(m_editor && m_identity, NS_ERROR_NOT_INITIALIZED);
// First, get the nsIEditor interface for future use
nsCOMPtr<nsINode> nodeInserted;
TranslateLineEnding(aPrefix);
TranslateLineEnding(aBuf);
TranslateLineEnding(aSignature);
m_editor->EnableUndo(false);
// Ok - now we need to figure out the charset of the aBuf we are going to send
// into the editor shell. There are I18N calls to sniff the data and then we
// need to call the new routine in the editor that will allow us to send in
// the charset
//
// Now, insert it into the editor...
RefPtr<HTMLEditor> htmlEditor = m_editor->AsHTMLEditor();
int32_t reply_on_top = 0;
bool sig_bottom = true;
m_identity->GetReplyOnTop(&reply_on_top);
m_identity->GetSigBottom(&sig_bottom);
bool sigOnTop = (reply_on_top == 1 && !sig_bottom);
bool isForwarded = (mType == nsIMsgCompType::ForwardInline);
// When in paragraph mode, don't call InsertLineBreak() since that inserts
// a full paragraph instead of just a line break since we switched
// the default paragraph separator to "p".
bool paragraphMode =
mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false);
if (aQuoted) {
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 but not a whole paragraph.
if (aHTMLEditor && !paragraphMode) htmlEditor->InsertLineBreak();
// add one newline if a signature comes before the quote, two otherwise
bool includeSignature = true;
bool sig_bottom = true;
bool attachFile = false;
nsString prefSigText;
m_identity->GetSigOnReply(&includeSignature);
m_identity->GetSigBottom(&sig_bottom);
m_identity->GetHtmlSigText(prefSigText);
nsresult rv = m_identity->GetAttachSignature(&attachFile);
if (!paragraphMode || !aHTMLEditor) {
if (includeSignature && !sig_bottom &&
((NS_SUCCEEDED(rv) && attachFile) || !prefSigText.IsEmpty()))
htmlEditor->InsertLineBreak();
else {
htmlEditor->InsertLineBreak();
htmlEditor->InsertLineBreak();
}
}
}
InsertDivWrappedTextAtSelection(aPrefix, u"moz-cite-prefix"_ns);
}
if (!aBuf.IsEmpty()) {
// This leaves the caret at the right place to insert a bottom signature.
if (aHTMLEditor) {
nsAutoString body(aBuf);
remove_plaintext_tag(body);
htmlEditor->InsertAsCitedQuotation(body, mCiteReference, true,
getter_AddRefs(nodeInserted));
} else {
htmlEditor->InsertAsQuotation(aBuf, getter_AddRefs(nodeInserted));
}
}
mInsertingQuotedContent = false;
(void)TagEmbeddedObjects(htmlEditor);
if (!aSignature.IsEmpty()) {
// we cannot add it on top earlier, because TagEmbeddedObjects will mark
// all images in the signature as "moz-do-not-send"
if (sigOnTop) MoveToBeginningOfDocument();
if (aHTMLEditor)
htmlEditor->InsertHTML(aSignature);
else {
htmlEditor->InsertLineBreak();
InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
}
if (sigOnTop) htmlEditor->EndOfDocument();
}
} else {
if (aHTMLEditor) {
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 divTag;
divTag.AssignLiteral("<div class=\"moz-forward-container\">");
aBuf.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX) - 1 - 8);
}
remove_plaintext_tag(aBuf);
bool stripConditionalCSS = mozilla::Preferences::GetBool(
"mail.html_sanitize.drop_conditional_css", true);
if (stripConditionalCSS) {
nsString newBody;
remove_conditional_CSS(aBuf, newBody);
htmlEditor->RebuildDocumentFromSource(newBody);
} else {
htmlEditor->RebuildDocumentFromSource(aBuf);
}
mInsertingQuotedContent = false;
// When forwarding a message as inline, or editing as new (which could
// contain unsanitized remote content), tag any embedded objects
// with moz-do-not-send=true so they don't get attached upon send.
if (isForwarded || mType == nsIMsgCompType::EditAsNew)
(void)TagEmbeddedObjects(htmlEditor);
if (!aSignature.IsEmpty()) {
if (isForwarded && sigOnTop) {
// Use our own function, nsEditor::BeginningOfDocument() would
// position into the <div class="moz-forward-container"> we've just
// created.
MoveToBeginningOfDocument();
} else {
// Use our own function, nsEditor::EndOfDocument() would position
// into the <div class="moz-forward-container"> we've just created.
MoveToEndOfDocument();
}
htmlEditor->InsertHTML(aSignature);
if (isForwarded && sigOnTop) htmlEditor->EndOfDocument();
} else
htmlEditor->EndOfDocument();
} else {
bool sigOnTopInserted = false;
if (isForwarded && sigOnTop && !aSignature.IsEmpty()) {
htmlEditor->InsertLineBreak();
InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
htmlEditor->EndOfDocument();
sigOnTopInserted = true;
}
if (!aBuf.IsEmpty()) {
nsresult rv;
RefPtr<Element> divElem;
RefPtr<Element> extraBr;
if (isForwarded) {
// Special treatment for forwarded messages: Part 1.
// Create a <div> of the required class.
rv = htmlEditor->CreateElementWithDefaults(u"div"_ns,
getter_AddRefs(divElem));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoString attributeName;
nsAutoString attributeValue;
attributeName.AssignLiteral("class");
attributeValue.AssignLiteral("moz-forward-container");
IgnoredErrorResult rv1;
divElem->SetAttribute(attributeName, attributeValue, rv1);
// We can't insert an empty <div>, so fill it with something.
rv = htmlEditor->CreateElementWithDefaults(u"br"_ns,
getter_AddRefs(extraBr));
NS_ENSURE_SUCCESS(rv, rv);
ErrorResult rv2;
divElem->AppendChild(*extraBr, rv2);
if (rv2.Failed()) {
return rv2.StealNSResult();
}
// Insert the non-empty <div> into the DOM.
rv = htmlEditor->InsertElementAtSelection(divElem, false);
NS_ENSURE_SUCCESS(rv, rv);
// Position into the div, so out content goes there.
RefPtr<Selection> selection;
htmlEditor->GetSelection(getter_AddRefs(selection));
rv = selection->CollapseInLimiter(divElem, 0);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = htmlEditor->InsertTextWithQuotations(aBuf);
NS_ENSURE_SUCCESS(rv, rv);
if (isForwarded) {
// Special treatment for forwarded messages: Part 2.
if (sigOnTopInserted) {
// Sadly the M-C editor inserts a <br> between the <div> for the
// signature and this <div>, so remove the <br> we don't want.
nsCOMPtr<nsINode> brBeforeDiv;
nsAutoString tagLocalName;
brBeforeDiv = divElem->GetPreviousSibling();
if (brBeforeDiv) {
tagLocalName = brBeforeDiv->LocalName();
if (tagLocalName.EqualsLiteral("br")) {
rv = htmlEditor->DeleteNode(brBeforeDiv);
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
// Clean up the <br> we inserted.
rv = htmlEditor->DeleteNode(extraBr);
NS_ENSURE_SUCCESS(rv, rv);
}
// Use our own function instead of nsEditor::EndOfDocument() because
// we don't want to position at the end of the div we've just created.
// It's OK to use, even if we're not forwarding and didn't create a
// <div>.
rv = MoveToEndOfDocument();
NS_ENSURE_SUCCESS(rv, rv);
}
if ((!isForwarded || !sigOnTop) && !aSignature.IsEmpty()) {
htmlEditor->InsertLineBreak();
InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
}
}
}
if (aBuf.IsEmpty())
htmlEditor->BeginningOfDocument();
else {
switch (reply_on_top) {
// This should set the cursor after the body but before the sig
case 0: {
if (!htmlEditor) {
htmlEditor->BeginningOfDocument();
break;
}
RefPtr<Selection> selection;
nsCOMPtr<nsINode> parent;
int32_t offset;
nsresult rv;
// get parent and offset of mailcite
rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
if (NS_FAILED(rv) || (!parent)) {
htmlEditor->BeginningOfDocument();
break;
}
// get selection
htmlEditor->GetSelection(getter_AddRefs(selection));
if (!selection) {
htmlEditor->BeginningOfDocument();
break;
}
// place selection after mailcite
selection->CollapseInLimiter(parent, offset + 1);
// insert a break at current selection
if (!paragraphMode || !aHTMLEditor) htmlEditor->InsertLineBreak();
// i'm not sure if you need to move the selection back to before the
// break. expirement.
selection->CollapseInLimiter(parent, offset + 1);
break;
}
case 2: {
nsCOMPtr<nsIEditor> editor(htmlEditor); // Strong reference.
editor->SelectAll();
break;
}
// This should set the cursor to the top!
default: {
MoveToBeginningOfDocument();
break;
}
}
}
nsCOMPtr<nsISelectionController> selCon;
htmlEditor->GetSelectionController(getter_AddRefs(selCon));
if (selCon)
selCon->ScrollSelectionIntoView(
nsISelectionController::SELECTION_NORMAL,
nsISelectionController::SELECTION_ANCHOR_REGION, true);
htmlEditor->EnableUndo(true);
SetBodyModified(false);
#ifdef MSGCOMP_TRACE_PERFORMANCE
nsCOMPtr<nsIMsgComposeService> composeService(
do_GetService(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 = 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);
#ifndef MOZ_SUITE
if (m_composeHTML) {
Telemetry::ScalarAdd(Telemetry::ScalarID::TB_COMPOSE_FORMAT_HTML, 1);
} else {
Telemetry::ScalarAdd(Telemetry::ScalarID::TB_COMPOSE_FORMAT_PLAIN_TEXT, 1);
}
Telemetry::Accumulate(Telemetry::TB_COMPOSE_TYPE, type);
#endif
if (composeFields) {
nsAutoCString draftId; // will get set for drafts and templates
rv = composeFields->GetDraftId(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 &&
type != nsIMsgCompType::EditAsNew) {
bool requestReturnReceipt = false;
rv = m_identity->GetRequestReturnReceipt(&requestReturnReceipt);
NS_ENSURE_SUCCESS(rv, rv);
rv = composeFields->SetReturnReceipt(requestReturnReceipt);
NS_ENSURE_SUCCESS(rv, rv);
int32_t receiptType = nsIMsgMdnGenerator::eDntType;
rv = m_identity->GetReceiptHeaderType(&receiptType);
NS_ENSURE_SUCCESS(rv, rv);
rv = composeFields->SetReceiptHeaderType(receiptType);
NS_ENSURE_SUCCESS(rv, rv);
bool requestDSN = false;
rv = m_identity->GetRequestDSN(&requestDSN);
NS_ENSURE_SUCCESS(rv, rv);
rv = composeFields->SetDSN(requestDSN);
NS_ENSURE_SUCCESS(rv, rv);
bool attachVCard;
rv = m_identity->GetAttachVCard(&attachVCard);
NS_ENSURE_SUCCESS(rv, rv);
rv = composeFields->SetAttachVCard(attachVCard);
NS_ENSURE_SUCCESS(rv, rv);
}
}
nsCOMPtr<nsIMsgSendListener> externalSendListener;
aParams->GetSendListener(getter_AddRefs(externalSendListener));
if (externalSendListener) AddMsgSendListener(externalSendListener);
nsString smtpPassword;
aParams->GetSmtpPassword(smtpPassword);
mSmtpPassword = smtpPassword;
aParams->GetHtmlToQuote(mHtmlToQuote);
if (aDocShell) {
mDocShell = aDocShell;
// register the compose object with the compose service
rv = composeService->RegisterComposeDocShell(aDocShell, this);
NS_ENSURE_SUCCESS(rv, rv);
}
return CreateMessage(originalMsgURI.get(), type, composeFields);
}
NS_IMETHODIMP
nsMsgCompose::RegisterStateListener(
nsIMsgComposeStateListener* aStateListener) {
NS_ENSURE_ARG_POINTER(aStateListener);
mStateListeners.AppendElement(aStateListener);
return NS_OK;
}
NS_IMETHODIMP
nsMsgCompose::UnregisterStateListener(
nsIMsgComposeStateListener* aStateListener) {
NS_ENSURE_ARG_POINTER(aStateListener);
return mStateListeners.RemoveElement(aStateListener) ? NS_OK
: NS_ERROR_FAILURE;
}
// Added to allow easier use of the nsIMsgSendListener
NS_IMETHODIMP nsMsgCompose::AddMsgSendListener(
nsIMsgSendListener* aMsgSendListener) {
NS_ENSURE_ARG_POINTER(aMsgSendListener);
mExternalSendListeners.AppendElement(aMsgSendListener);
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::RemoveMsgSendListener(
nsIMsgSendListener* aMsgSendListener) {
NS_ENSURE_ARG_POINTER(aMsgSendListener);
return mExternalSendListeners.RemoveElement(aMsgSendListener)
? NS_OK
: NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsMsgCompose::SendMsgToServer(MSG_DeliverMode deliverMode,
nsIMsgIdentity* identity, const char* accountKey,
Promise** aPromise) {
nsresult rv = NS_OK;
// clear saved message id if sending, so we don't send out the same
// message-id.
if (deliverMode == nsIMsgCompDeliverMode::Now ||
deliverMode == nsIMsgCompDeliverMode::Later ||
deliverMode == nsIMsgCompDeliverMode::Background)
m_compFields->SetMessageId("");
if (m_compFields && identity) {
// Pref values are supposed to be stored as UTF-8, so no conversion
nsCString email;
nsString fullName;
nsString organization;
identity->GetEmail(email);
identity->GetFullName(fullName);
identity->GetOrganization(organization);
const char* pFrom = m_compFields->GetFrom();
if (!pFrom || !*pFrom) {
nsCString sender;
MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), email, sender);
m_compFields->SetFrom(sender.IsEmpty() ? email.get() : sender.get());
}
m_compFields->SetOrganization(organization);
// We need an nsIMsgSend instance to send the message. Allow extensions
// to override the default SMTP sender by observing mail-set-sender.
mMsgSend = nullptr;
mDeliverMode = deliverMode; // save for possible access by observer.
// Allow extensions to specify an outgoing server.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
NS_ENSURE_STATE(observerService);
// Assemble a string with sending parameters.
nsAutoString sendParms;
// First parameter: account key. This may be null.
sendParms.AppendASCII(accountKey && *accountKey ? accountKey : "");
sendParms.Append(',');
// Second parameter: deliverMode.
sendParms.AppendInt(deliverMode);
sendParms.Append(',');
// Third parameter: identity (as identity key).
nsAutoCString identityKey;
identity->GetKey(identityKey);
sendParms.AppendASCII(identityKey.get());
observerService->NotifyObservers(NS_ISUPPORTS_CAST(nsIMsgCompose*, this),
"mail-set-sender", sendParms.get());
if (!mMsgSend) mMsgSend = do_CreateInstance(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);
RefPtr<mozilla::dom::Promise> promise;
rv = mMsgSend->CreateAndSendMessage(
m_composeHTML ? m_editor.get() : nullptr, identity, accountKey,
m_compFields, false, false, (nsMsgDeliverMode)deliverMode, nullptr,
m_composeHTML ? TEXT_HTML : TEXT_PLAIN, bodyString, m_window,
mProgress, sendListener, mSmtpPassword, mOriginalMsgURI, mType,
getter_AddRefs(promise));
promise.forget(aPromise);
} else
rv = NS_ERROR_FAILURE;
} else
rv = NS_ERROR_NOT_INITIALIZED;
return rv;
}
NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode,
nsIMsgIdentity* identity,
const char* accountKey,
nsIMsgWindow* aMsgWindow,
nsIMsgProgress* progress,
Promise** aPromise) {
NS_ENSURE_TRUE(m_compFields, NS_ERROR_NOT_INITIALIZED);
nsresult rv = NS_OK;
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) ? u"text/html"_ns : u"text/plain"_ns;
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);
uint32_t flags = nsIDocumentEncoder::OutputCRLineBreak |
nsIDocumentEncoder::OutputLFLineBreak;
if (m_composeHTML) {
flags |= nsIDocumentEncoder::OutputFormatted |
nsIDocumentEncoder::OutputDisallowLineBreaking;
} else {
bool flowed, formatted;
GetSerialiserFlags(&flowed, &formatted);
if (flowed) flags |= nsIDocumentEncoder::OutputFormatFlowed;
if (formatted) flags |= nsIDocumentEncoder::OutputFormatted;
flags |= nsIDocumentEncoder::OutputDisallowLineBreaking;
// Don't lose NBSP in the plain text encoder.
flags |= nsIDocumentEncoder::OutputPersistNBSP;
}
rv = m_editor->OutputToString(contentType, flags, msgBody);
NS_ENSURE_SUCCESS(rv, rv);
} else {
m_compFields->GetBody(msgBody);
}
if (!msgBody.IsEmpty()) {
// Ensure body ends in CRLF to avoid SMTP server timeout when sent.
if (!StringEndsWith(msgBody, u"\r\n"_ns)) msgBody.AppendLiteral("\r\n");
bool isAsciiOnly = mozilla::IsAsciiNullTerminated(
static_cast<const char16_t*>(msgBody.get()));
// Convert body to UTF-8
nsCString outCString;
outCString.Assign(NS_ConvertUTF16toUTF8(msgBody));
if (m_compFields->GetForceMsgEncoding()) isAsciiOnly = false;
if (NS_SUCCEEDED(rv) && !outCString.IsEmpty()) {
m_compFields->SetBodyIsAsciiOnly(isAsciiOnly);
m_compFields->SetBody(outCString.get());
} else {
m_compFields->SetBody(NS_ConvertUTF16toUTF8(msgBody).get());
}
}
// 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,
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(u"vcard.vcf"_ns);
else {
// Replace any dot with underscore to stop vCards
// generating false positives with some heuristic scanners
userid.ReplaceChar('.', '_');
userid.AppendLiteral(".vcf");
attachment->SetName(NS_ConvertASCIItoUTF16(userid));
}
attachment->SetUrl(vCardUrl);
m_compFields->AddAttachment(attachment);
}
}
}
// Save the identity being sent for later use.
m_identity = identity;
RefPtr<mozilla::dom::Promise> promise;
rv = SendMsgToServer(deliverMode, identity, accountKey,
getter_AddRefs(promise));
auto handleFailure = [&](nsresult rv) {
NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, 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 initialize
// a send report! Let's try our best...
switch (deliverMode) {
case nsIMsgCompDeliverMode::Later:
nsMsgDisplayMessageByName(prompt, "unableToSendLater");
break;
case nsIMsgCompDeliverMode::AutoSaveAsDraft:
case nsIMsgCompDeliverMode::SaveAsDraft:
nsMsgDisplayMessageByName(prompt, "unableToSaveDraft");
break;
case nsIMsgCompDeliverMode::SaveAsTemplate:
nsMsgDisplayMessageByName(prompt, "unableToSaveTemplate");
break;
default:
nsMsgDisplayMessageByName(prompt, "sendFailed");
break;
}
}
if (mProgress) mProgress->CloseProgressDialog(true);
};
if (promise) {
new DomPromiseListener(
promise, [](JSContext*, JS::Handle<JS::Value>) {}, handleFailure);
promise.forget(aPromise);
} else if (NS_FAILED(rv)) {
handleFailure(rv);
}
return rv;
}
NS_IMETHODIMP nsMsgCompose::GetDeleteDraft(bool* aDeleteDraft) {
NS_ENSURE_ARG_POINTER(aDeleteDraft);
*aDeleteDraft = mDeleteDraft;
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::SetDeleteDraft(bool aDeleteDraft) {
mDeleteDraft = aDeleteDraft;
return NS_OK;
}
bool nsMsgCompose::IsLastWindow() {
nsresult rv;
bool more;
nsCOMPtr<nsIWindowMediator> windowMediator =
do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
rv = windowMediator->GetEnumerator(nullptr,
getter_AddRefs(windowEnumerator));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsISupports> isupports;
if (NS_SUCCEEDED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
if (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)))
return !more;
}
}
return true;
}
NS_IMETHODIMP nsMsgCompose::CloseWindow(void) {
nsresult rv;
nsCOMPtr<nsIMsgComposeService> composeService =
do_GetService(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;
}
// This used to be called BEFORE editor was created
// (it did the loadURI that triggered editor creation)
// It is called from JS after editor creation
// (loadURI is done in JS)
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsMsgCompose::InitEditor(
nsIEditor* aEditor, mozIDOMWindowProxy* aContentWindow) {
NS_ENSURE_ARG_POINTER(aEditor);
NS_ENSURE_ARG_POINTER(aContentWindow);
nsresult rv;
m_editor = aEditor;
aEditor->SetDocumentCharacterSet("UTF-8"_ns);
nsCOMPtr<nsPIDOMWindowOuter> window =
nsPIDOMWindowOuter::From(aContentWindow);
nsIDocShell* docShell = window->GetDocShell();
NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
rv = docShell->SetCharset("UTF-8"_ns);
NS_ENSURE_SUCCESS(rv, rv);
bool quotingToFollow = false;
GetQuotingToFollow(&quotingToFollow);
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;
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
nsMsgCompose::SetBodyModified(bool modified) {
nsresult rv = NS_OK;
if (m_editor) {
nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference.
if (modified) {
int32_t modCount = 0;
editor->GetModificationCount(&modCount);
if (modCount == 0) editor->IncrementModificationCount(1);
} else
editor->ResetModificationCount();
}
return rv;
}
NS_IMETHODIMP
nsMsgCompose::GetDomWindow(mozIDOMWindowProxy** aDomWindow) {
NS_IF_ADDREF(*aDomWindow = m_window);
return NS_OK;
}
nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields** aCompFields) {
NS_IF_ADDREF(*aCompFields = (nsIMsgCompFields*)m_compFields);
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::GetComposeHTML(bool* aComposeHTML) {
*aComposeHTML = m_composeHTML;
return NS_OK;
}
nsresult nsMsgCompose::GetWrapLength(int32_t* aWrapLength) {
nsresult rv;
nsCOMPtr<nsIPrefBranch> prefBranch(
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
if (NS_FAILED(rv)) return rv;
return prefBranch->GetIntPref("mailnews.wraplength", aWrapLength);
}
nsresult nsMsgCompose::CreateMessage(const 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, "file:"_ns);
int32_t typeIndex = msgUri.Find("type=application/x-message-display");
if (typeIndex != kNotFound && typeIndex > 0) {
// Strip out type=application/x-message-display because it confuses libmime.
msgUri.Cut(typeIndex, sizeof("type=application/x-message-display"));
if (fileUrl) // we're dealing with an .eml file msg
{
// We have now removed the type from the uri. Make sure we don't have
// an uri with "&&" now. If we do, remove the second '&'.
if (msgUri.CharAt(typeIndex) == '&') msgUri.Cut(typeIndex, 1);
// Remove possible trailing '?'.
if (msgUri.CharAt(msgUri.Length() - 1) == '?')
msgUri.Cut(msgUri.Length() - 1, 1);
} else // we're dealing with a message/rfc822 attachment
{
// nsURLFetcher will check for "realtype=message/rfc822" and will set the
// content type to message/rfc822 in the forwarded message.
msgUri.AppendLiteral("&realtype=message/rfc822");
}
originalMsgURI = msgUri.get();
}
if (compFields) {
m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields);
} else {
m_compFields = new nsMsgCompFields();
}
if (m_identity && mType != nsIMsgCompType::Draft) {
// Setup reply-to field.
nsCString replyTo;
m_identity->GetReplyTo(replyTo);
if (!replyTo.IsEmpty()) {
nsCString resultStr;
RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetReplyTo()),
replyTo, resultStr);
if (!resultStr.IsEmpty()) {
replyTo.Append(',');
replyTo.Append(resultStr);
}
m_compFields->SetReplyTo(replyTo.get());
}
// Setup auto-Cc field.
bool doCc;
m_identity->GetDoCc(&doCc);
if (doCc) {
nsCString ccList;
m_identity->GetDoCcList(ccList);
nsCString resultStr;
RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetCc()),
ccList, resultStr);
if (!resultStr.IsEmpty()) {
ccList.Append(',');
ccList.Append(resultStr);
}
m_compFields->SetCc(ccList.get());
}
// Setup auto-Bcc field.
bool doBcc;
m_identity->GetDoBcc(&doBcc);
if (doBcc) {
nsCString bccList;
m_identity->GetDoBccList(bccList);
nsCString resultStr;
RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetBcc()),
bccList, resultStr);
if (!resultStr.IsEmpty()) {
bccList.Append(',');
bccList.Append(resultStr);
}
m_compFields->SetBcc(bccList.get());
}
}
if (mType == nsIMsgCompType::Draft) {
nsCString curDraftIdURL;
rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL));
// 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.EqualsLiteral("replied"))
mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied;
else if (queuedDisposition.EqualsLiteral("forward"))
mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded;
}
}
} else {
NS_WARNING("CreateMessage can't get draft id");
}
}
// 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) {
// 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.Append('<');
reference.Append(ref);
reference.AppendLiteral("> ");
}
}
reference.Trim(" ", false, true);
}
msgHdr->GetMessageId(getter_Copies(messageId));
reference.Append('<');
reference.Append(messageId);
reference.Append('>');
m_compFields->SetReferences(reference.get());
}
// 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;
nsString subject;
rv = msgHdr->GetMime2DecodedSubject(subject);
if (NS_FAILED(rv)) return rv;
// Check if (was: is present in the subject
int32_t wasOffset = subject.RFind(u" (was:"_ns);
bool strip = true;
if (wasOffset >= 0) {
// Check the number of references, to check if was: should be stripped
// First, assume that it should be stripped; the variable will be set to
// false later if stripping should not happen.
uint16_t numRef;
msgHdr->GetNumReferences(&numRef);
if (numRef) {
// If there are references, look for the first message in the thread
// firstly, get the database via the folder
nsCOMPtr<nsIMsgFolder> folder;
msgHdr->GetFolder(getter_AddRefs(folder));
if (folder) {
nsCOMPtr<nsIMsgDatabase> db;
folder->GetMsgDatabase(getter_AddRefs(db));
if (db) {
nsAutoCString reference;
msgHdr->GetStringReference(0, reference);
nsCOMPtr<nsIMsgDBHdr> refHdr;
db->GetMsgHdrForMessageID(reference.get(),
getter_AddRefs(refHdr));
if (refHdr) {
nsCString refSubject;
rv = refHdr->GetSubject(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.InsertLiteral(u"Re: ", 0);
m_compFields->SetSubject(subject);
// Setup quoting callbacks for later...
mWhatHolder = 1;
break;
}
case nsIMsgCompType::ForwardAsAttachment: {
// Add the forwarded message in the references, first
nsAutoCString messageId;
msgHdr->GetMessageId(getter_Copies(messageId));
if (isFirstPass) {
nsAutoCString reference;
reference.Append('<');
reference.Append(messageId);
reference.Append('>');
m_compFields->SetReferences(reference.get());
} else {
nsAutoCString references;
m_compFields->GetReferences(getter_Copies(references));
references.AppendLiteral(" <");
references.Append(messageId);
references.Append('>');
m_compFields->SetReferences(references.get());
}
uint32_t flags;
msgHdr->GetFlags(&flags);
if (flags & nsMsgMessageFlags::HasRe)
subject.InsertLiteral(u"Re: ", 0);
// Setup quoting callbacks for later...
mQuotingToFollow =
false; // We don't need to quote the original message.
nsCOMPtr<nsIMsgAttachment> attachment =
do_CreateInstance(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(
"composeMsgs.properties",
getter_AddRefs(composeBundle));
NS_ENSURE_SUCCESS(rv, rv);
composeBundle->GetStringFromName("messageAttachmentSafeName",
sanitizedSubj);
} else
sanitizedSubj.Assign(subject);
// set the file size
uint32_t messageSize;
msgHdr->GetMessageSize(&messageSize);
attachment->SetSize(messageSize);
// change all '.' to '_' see bug #271211
sanitizedSubj.ReplaceChar(".", '_');
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", fwdPrefix);
if (!fwdPrefix.IsEmpty()) {
nsString unicodeFwdPrefix;
CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix);
unicodeFwdPrefix.AppendLiteral(": ");
subject.Insert(unicodeFwdPrefix, 0);
} else {
subject.InsertLiteral(u"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);
NS_IF_ADDREF(*_retval = mProgress);
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend** _retval) {
NS_ENSURE_ARG_POINTER(_retval);
NS_IF_ADDREF(*_retval = mMsgSend);
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::SetMessageSend(nsIMsgSend* aMsgSend) {
mMsgSend = aMsgSend;
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::ClearMessageSend() {
mMsgSend = nullptr;
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::SetCiteReference(nsString citeReference) {
mCiteReference = citeReference;
return NS_OK;
}
NS_IMETHODIMP nsMsgCompose::SetSavedFolderURI(const 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 OUTPUT
// FROM LIBMIME. THIS IS FOR QUOTING
////////////////////////////////////////////////////////////////////////////////////
QuotingOutputStreamListener::~QuotingOutputStreamListener() {}
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;
mQuoteOriginal = quoteOriginal;
mHtmlToQuote = htmlToQuote;
mQuote = msgQuote;
mCharsetFixed = charsetFixed;
if (!mHeadersOnly || !mHtmlToQuote.IsEmpty()) {
// Get header type, locale and strings from pref.
int32_t replyHeaderType;
nsString replyHeaderAuthorWrote;
nsString replyHeaderOnDateAuthorWrote;
nsString replyHeaderAuthorWroteOnDate;
nsString replyHeaderOriginalmessage;
GetReplyHeaderInfo(
&replyHeaderType, replyHeaderAuthorWrote, replyHeaderOnDateAuthorWrote,
replyHeaderAuthorWroteOnDate, replyHeaderOriginalmessage);
// For the built message body...
if (originalMsgHdr && !quoteHeaders) {
// Setup the cite information....
nsCString myGetter;
if (NS_SUCCEEDED(originalMsgHdr->GetMessageId(getter_Copies(myGetter)))) {
if (!myGetter.IsEmpty()) {
nsAutoCString buf;
mCiteReference.AssignLiteral("mid:");
MsgEscapeURL(myGetter,
nsINetUtil::ESCAPE_URL_FILE_BASENAME |
nsINetUtil::ESCAPE_URL_FORCED,
buf);
mCiteReference.Append(NS_ConvertASCIItoUTF16(buf));
}
}
bool citingHeader; // Do we have a header needing to cite any info from
// original message?
bool headerDate; // Do we have a header needing to cite date/time from
// original message?
switch (replyHeaderType) {
case 0: // No reply header at all (actually the "---- original message
// ----" string, which is kinda misleading. TODO: Should there
// be a "really no header" option?
mCitePrefix.Assign(replyHeaderOriginalmessage);
citingHeader = false;
headerDate = false;
break;
case 2: // Insert both the original author and date in the reply header
// (date followed by author)
mCitePrefix.Assign(replyHeaderOnDateAuthorWrote);
citingHeader = true;
headerDate = true;
break;
case 3: // Insert both the original author and date in the reply header
// (author followed by date)
mCitePrefix.Assign(replyHeaderAuthorWroteOnDate);
citingHeader = true;
headerDate = true;
break;
case 4: // TODO bug 107884: implement a more featureful user specified
// header
case 1:
default: // Default is to only show the author.
mCitePrefix.Assign(replyHeaderAuthorWrote);
citingHeader = true;
headerDate = false;
break;
}
if (citingHeader) {
int32_t placeholderIndex = kNotFound;
if (headerDate) {
PRTime originalMsgDate;
rv = originalMsgHdr->GetDate(&originalMsgDate);
if (NS_SUCCEEDED(rv)) {
nsAutoString citeDatePart;
if ((placeholderIndex = mCitePrefix.Find("#2")) != kNotFound) {
rv = mozilla::DateTimeFormat::FormatPRTime(
kDateFormatShort, kTimeFormatNone, originalMsgDate,
citeDatePart);
if (NS_SUCCEEDED(rv))
mCitePrefix.Replace(placeholderIndex, 2, citeDatePart);
}
if ((placeholderIndex = mCitePrefix.Find("#3")) != kNotFound) {
rv = mozilla::DateTimeFormat::FormatPRTime(
kDateFormatNone, kTimeFormatShort, originalMsgDate,
citeDatePart);
if (NS_SUCCEEDED(rv))
mCitePrefix.Replace(placeholderIndex, 2, citeDatePart);