diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mailnews/compose/src/nsMsgComposeService.cpp | 1411 |
1 files changed, 1411 insertions, 0 deletions
diff --git a/comm/mailnews/compose/src/nsMsgComposeService.cpp b/comm/mailnews/compose/src/nsMsgComposeService.cpp new file mode 100644 index 0000000000..be12fbbef4 --- /dev/null +++ b/comm/mailnews/compose/src/nsMsgComposeService.cpp @@ -0,0 +1,1411 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsMsgComposeService.h" +#include "nsIMsgSend.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsIMsgIdentity.h" +#include "nsISmtpUrl.h" +#include "nsIURI.h" +#include "nsMsgI18N.h" +#include "nsIMsgComposeParams.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIWindowWatcher.h" +#include "mozIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIMsgWindow.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Document.h" +#include "nsIAppWindow.h" +#include "nsIWindowMediator.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIBaseWindow.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMsgAccountManager.h" +#include "nsIStreamConverter.h" +#include "nsToolkitCompsCID.h" +#include "nsNetUtil.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgDatabase.h" +#include "nsIDocumentEncoder.h" +#include "nsContentCID.h" +#include "mozilla/dom/Selection.h" +#include "nsUTF8Utils.h" +#include "mozilla/intl/LineBreaker.h" +#include "mimemoz2.h" +#include "nsIURIMutator.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/XULFrameElement.h" +#include "nsFrameLoader.h" +#include "nsSmtpUrl.h" +#include "nsUnicharUtils.h" +#include "mozilla/NullPrincipal.h" + +#ifdef MSGCOMP_TRACE_PERFORMANCE +# include "mozilla/Logging.h" +# include "nsIMsgHdr.h" +# include "nsIMsgMessageService.h" +# include "nsMsgUtils.h" +#endif + +#include "nsICommandLine.h" +#include "nsIAppStartup.h" +#include "nsMsgUtils.h" +#include "nsIPrincipal.h" +#include "nsIMutableArray.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#ifdef XP_WIN +# include <windows.h> +# include <shellapi.h> +# include "nsIWidget.h" +#endif + +#define DEFAULT_CHROME \ + "chrome://messenger/content/messengercompose/messengercompose.xhtml"_ns + +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION "mailnews.reply_quoting_selection" +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD \ + "mailnews.reply_quoting_selection.multi_word" +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF \ + "mailnews.reply_quoting_selection.only_if_chars" + +#define MAIL_ROOT_PREF "mail." +#define MAILNEWS_ROOT_PREF "mailnews." +#define HTMLDOMAINUPDATE_VERSION_PREF_NAME "global_html_domains.version" +#define HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME "global_html_domains" +#define USER_CURRENT_HTMLDOMAINLIST_PREF_NAME "html_domains" +#define USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME "plaintext_domains" +#define DOMAIN_DELIMITER ',' + +#ifdef MSGCOMP_TRACE_PERFORMANCE +static mozilla::LazyLogModule MsgComposeLogModule("MsgCompose"); + +static uint32_t GetMessageSizeFromURI(const nsACString& originalMsgURI) { + uint32_t msgSize = 0; + + if (!originalMsgURI.IsEmpty()) { + nsCOMPtr<nsIMsgDBHdr> originalMsgHdr; + GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(originalMsgHdr)); + if (originalMsgHdr) originalMsgHdr->GetMessageSize(&msgSize); + } + + return msgSize; +} +#endif + +nsMsgComposeService::nsMsgComposeService() { + // Defaulting the value of mLogComposePerformance to FALSE to prevent logging. + mLogComposePerformance = false; +#ifdef MSGCOMP_TRACE_PERFORMANCE + mStartTime = PR_IntervalNow(); + mPreviousTime = mStartTime; +#endif +} + +NS_IMPL_ISUPPORTS(nsMsgComposeService, nsIMsgComposeService, + ICOMMANDLINEHANDLER, nsISupportsWeakReference) + +nsMsgComposeService::~nsMsgComposeService() { mOpenComposeWindows.Clear(); } + +nsresult nsMsgComposeService::Init() { + nsresult rv = NS_OK; + + Reset(); + + AddGlobalHtmlDomains(); + // Since the compose service should only be initialized once, we can + // be pretty sure there aren't any existing compose windows open. + MsgCleanupTempFiles("nsmail", "tmp"); + MsgCleanupTempFiles("nscopy", "tmp"); + MsgCleanupTempFiles("nsemail", "eml"); + MsgCleanupTempFiles("nsemail", "tmp"); + MsgCleanupTempFiles("nsqmail", "tmp"); + return rv; +} + +void nsMsgComposeService::Reset() { + mOpenComposeWindows.Clear(); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetBoolPref("mailnews.logComposePerformance", + &mLogComposePerformance); +} + +// Function to open a message compose window and pass an nsIMsgComposeParams +// parameter to it. +NS_IMETHODIMP +nsMsgComposeService::OpenComposeWindowWithParams(const char* chrome, + nsIMsgComposeParams* params) { + NS_ENSURE_ARG_POINTER(params); +#ifdef MSGCOMP_TRACE_PERFORMANCE + if (mLogComposePerformance) { + TimeStamp("Start opening the window", true); + } +#endif + + nsresult rv; + + NS_ENSURE_ARG_POINTER(params); + + // Use default identity if no identity has been specified + nsCOMPtr<nsIMsgIdentity> identity; + params->GetIdentity(getter_AddRefs(identity)); + if (!identity) { + GetDefaultIdentity(getter_AddRefs(identity)); + params->SetIdentity(identity); + } + + // Create a new window. + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (!wwatch) return NS_ERROR_FAILURE; + + nsCOMPtr<nsISupportsInterfacePointer> msgParamsWrapper = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + msgParamsWrapper->SetData(params); + msgParamsWrapper->SetDataIID(&NS_GET_IID(nsIMsgComposeParams)); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + nsAutoCString chromeURL; + if (chrome && *chrome) { + chromeURL = nsDependentCString(chrome); + } else { + chromeURL = DEFAULT_CHROME; + } + rv = wwatch->OpenWindow(0, chromeURL, "_blank"_ns, + "all,chrome,dialog=no,status,toolbar"_ns, + msgParamsWrapper, getter_AddRefs(newWindow)); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::DetermineComposeHTML(nsIMsgIdentity* aIdentity, + MSG_ComposeFormat aFormat, + bool* aComposeHTML) { + NS_ENSURE_ARG_POINTER(aComposeHTML); + + *aComposeHTML = true; + switch (aFormat) { + case nsIMsgCompFormat::HTML: + *aComposeHTML = true; + break; + case nsIMsgCompFormat::PlainText: + *aComposeHTML = false; + break; + + default: + nsCOMPtr<nsIMsgIdentity> identity = aIdentity; + if (!identity) GetDefaultIdentity(getter_AddRefs(identity)); + + if (identity) { + identity->GetComposeHtml(aComposeHTML); + if (aFormat == nsIMsgCompFormat::OppositeOfDefault) + *aComposeHTML = !*aComposeHTML; + } else { + // default identity not found. Use the mail.html_compose pref to + // determine message compose type (HTML or PlainText). + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) { + nsresult rv; + bool useHTMLCompose; + rv = prefs->GetBoolPref(MAIL_ROOT_PREF "html_compose", + &useHTMLCompose); + if (NS_SUCCEEDED(rv)) *aComposeHTML = useHTMLCompose; + } + } + break; + } + + return NS_OK; +} + +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION nsresult +nsMsgComposeService::GetOrigWindowSelection(MSG_ComposeType type, + mozilla::dom::Selection* selection, + nsACString& aSelHTML) { + nsresult rv; + + // Good hygiene + aSelHTML.Truncate(); + + // Get the pref to see if we even should do reply quoting selection + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool replyQuotingSelection; + rv = prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION, + &replyQuotingSelection); + NS_ENSURE_SUCCESS(rv, rv); + if (!replyQuotingSelection) return NS_ERROR_ABORT; + + bool requireMultipleWords = true; + nsAutoCString charsOnlyIf; + prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD, + &requireMultipleWords); + prefs->GetCharPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF, + charsOnlyIf); + if (requireMultipleWords || !charsOnlyIf.IsEmpty()) { + nsAutoString selPlain; + selection->Stringify(selPlain); + + // If "mailnews.reply_quoting_selection.multi_word" is on, then there must + // be at least two words selected in order to quote just the selected text + if (requireMultipleWords) { + if (selPlain.IsEmpty()) return NS_ERROR_ABORT; + + if (NS_SUCCEEDED(rv)) { + const uint32_t length = selPlain.Length(); + const char16_t* unicodeStr = selPlain.get(); + int32_t endWordPos = + mozilla::intl::LineBreaker::Next(unicodeStr, length, 0); + + // If there's not even one word, then there's not multiple words + if (endWordPos == NS_LINEBREAKER_NEED_MORE_TEXT) return NS_ERROR_ABORT; + + // If after the first word is only space, then there's not multiple + // words + const char16_t* end; + for (end = unicodeStr + endWordPos; mozilla::intl::NS_IsSpace(*end); + end++) + ; + if (!*end) return NS_ERROR_ABORT; + } + } + + if (!charsOnlyIf.IsEmpty()) { + if (selPlain.FindCharInSet(NS_ConvertUTF8toUTF16(charsOnlyIf)) == + kNotFound) { + return NS_ERROR_ABORT; + } + } + } + + nsAutoString selHTML; + IgnoredErrorResult rv2; + selection->ToStringWithFormat(u"text/html"_ns, + nsIDocumentEncoder::SkipInvisibleContent, 0, + selHTML, rv2); + if (rv2.Failed()) { + return NS_ERROR_FAILURE; + } + + // Now remove <span class="moz-txt-citetags">> </span>. + nsAutoCString html(NS_ConvertUTF16toUTF8(selHTML).get()); + int32_t spanInd = html.Find("<span class=\"moz-txt-citetags\">"); + while (spanInd != kNotFound) { + nsAutoCString right0(Substring(html, spanInd)); + int32_t endInd = right0.Find("</span>"); + if (endInd == kNotFound) break; // oops, where is the closing tag gone? + nsAutoCString right1(Substring(html, spanInd + endInd + 7)); + html.SetLength(spanInd); + html.Append(right1); + spanInd = html.Find("<span class=\"moz-txt-citetags\">"); + } + + aSelHTML.Assign(html); + + return rv; +} + +MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION NS_IMETHODIMP +nsMsgComposeService::OpenComposeWindow( + const nsACString& msgComposeWindowURL, nsIMsgDBHdr* origMsgHdr, + const nsACString& originalMsgURI, MSG_ComposeType type, + MSG_ComposeFormat format, nsIMsgIdentity* aIdentity, const nsACString& from, + nsIMsgWindow* aMsgWindow, mozilla::dom::Selection* selection, + bool autodetectCharset) { + nsresult rv; + + nsCOMPtr<nsIMsgIdentity> identity = aIdentity; + if (!identity) GetDefaultIdentity(getter_AddRefs(identity)); + + /* Actually, the only way to implement forward inline is to simulate a + template message. Maybe one day when we will have more time we can change + that + */ + if (type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft || + type == nsIMsgCompType::EditTemplate || + type == nsIMsgCompType::Template || + type == nsIMsgCompType::ReplyWithTemplate || + type == nsIMsgCompType::Redirect || type == nsIMsgCompType::EditAsNew) { + nsAutoCString uriToOpen(originalMsgURI); + char sep = (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&'; + + // The compose type that gets transmitted to a compose window open in mime + // is communicated using url query parameters here. + if (type == nsIMsgCompType::Redirect) { + uriToOpen += sep; + uriToOpen.AppendLiteral("redirect=true"); + } else if (type == nsIMsgCompType::EditAsNew) { + uriToOpen += sep; + uriToOpen.AppendLiteral("editasnew=true"); + } else if (type == nsIMsgCompType::EditTemplate) { + uriToOpen += sep; + uriToOpen.AppendLiteral("edittempl=true"); + } + + return LoadDraftOrTemplate( + uriToOpen, + type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft + ? nsMimeOutput::nsMimeMessageDraftOrTemplate + : nsMimeOutput::nsMimeMessageEditorTemplate, + identity, originalMsgURI, origMsgHdr, + type == nsIMsgCompType::ForwardInline, + format == nsIMsgCompFormat::OppositeOfDefault, aMsgWindow, + autodetectCharset); + } + + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv)); + if (NS_SUCCEEDED(rv) && pMsgComposeParams) { + nsCOMPtr<nsIMsgCompFields> pMsgCompFields(do_CreateInstance( + "@mozilla.org/messengercompose/composefields;1", &rv)); + if (NS_SUCCEEDED(rv) && pMsgCompFields) { + pMsgComposeParams->SetType(type); + pMsgComposeParams->SetFormat(format); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetAutodetectCharset(autodetectCharset); + + // When doing a reply (except with a template) see if there's a selection + // that we should quote + if (selection && + (type == nsIMsgCompType::Reply || type == nsIMsgCompType::ReplyAll || + type == nsIMsgCompType::ReplyToSender || + type == nsIMsgCompType::ReplyToGroup || + type == nsIMsgCompType::ReplyToSenderAndGroup || + type == nsIMsgCompType::ReplyToList)) { + nsAutoCString selHTML; + if (NS_SUCCEEDED(GetOrigWindowSelection(type, selection, selHTML))) { + nsCOMPtr<nsINode> node = selection->GetFocusNode(); + NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); + IgnoredErrorResult er; + + if ((node->LocalName().IsEmpty() || + node->LocalName().EqualsLiteral("pre")) && + node->OwnerDoc()->QuerySelector( + "body > div:first-of-type.moz-text-plain"_ns, er)) { + // Treat the quote as <pre> for selections in moz-text-plain bodies. + // If focusNode.localName isn't empty, we had e.g. body selected + // and should not add <pre>. + pMsgComposeParams->SetHtmlToQuote("<pre>"_ns + selHTML + + "</pre>"_ns); + } else { + pMsgComposeParams->SetHtmlToQuote(selHTML); + } + } + } + + if (!originalMsgURI.IsEmpty()) { + if (type == nsIMsgCompType::NewsPost) { + nsAutoCString newsURI(originalMsgURI); + nsAutoCString group; + nsAutoCString host; + + int32_t slashpos = newsURI.RFindChar('/'); + if (slashpos > 0) { + // uri is "[s]news://host[:port]/group" + host = StringHead(newsURI, slashpos); + group = Substring(newsURI, slashpos + 1); + + } else + group = originalMsgURI; + + nsAutoCString unescapedName; + MsgUnescapeString(group, + nsINetUtil::ESCAPE_URL_FILE_BASENAME | + nsINetUtil::ESCAPE_URL_FORCED, + unescapedName); + pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(unescapedName)); + pMsgCompFields->SetNewspostUrl(host.get()); + } else { + pMsgComposeParams->SetOriginalMsgURI(originalMsgURI); + pMsgComposeParams->SetOrigMsgHdr(origMsgHdr); + pMsgCompFields->SetFrom(NS_ConvertUTF8toUTF16(from)); + } + } + + pMsgComposeParams->SetComposeFields(pMsgCompFields); + + if (mLogComposePerformance) { +#ifdef MSGCOMP_TRACE_PERFORMANCE + // ducarroz, properly fix this in the case of new message (not a reply) + if (type != nsIMsgCompType::NewsPost) { + char buff[256]; + sprintf(buff, "Start opening the window, message size = %d", + GetMessageSizeFromURI(originalMsgURI)); + TimeStamp(buff, true); + } +#endif + } // end if(mLogComposePerformance) + + rv = OpenComposeWindowWithParams( + PromiseFlatCString(msgComposeWindowURL).get(), pMsgComposeParams); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgComposeService::GetParamsForMailto( + nsIURI* aURI, nsIMsgComposeParams** aParams) { + nsresult rv = NS_OK; + if (aURI) { + nsCString spec; + aURI->GetSpec(spec); + + nsCOMPtr<nsIURI> url; + rv = nsMailtoUrl::NewMailtoURI(spec, nullptr, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMailtoUrl> aMailtoUrl = do_QueryInterface(url, &rv); + + if (NS_SUCCEEDED(rv)) { + MSG_ComposeFormat requestedComposeFormat = nsIMsgCompFormat::Default; + nsCString toPart; + nsCString ccPart; + nsCString bccPart; + nsCString subjectPart; + nsCString bodyPart; + nsCString newsgroup; + nsCString refPart; + nsCString HTMLBodyPart; + + aMailtoUrl->GetMessageContents(toPart, ccPart, bccPart, subjectPart, + bodyPart, HTMLBodyPart, refPart, newsgroup, + &requestedComposeFormat); + + nsAutoString sanitizedBody; + + bool composeHTMLFormat; + DetermineComposeHTML(NULL, requestedComposeFormat, &composeHTMLFormat); + + // If there was an 'html-body' param, finding it will have requested + // HTML format in GetMessageContents, so we try to use it first. If it's + // empty, but we are composing in HTML because of the user's prefs, the + // 'body' param needs to be escaped, since it's supposed to be plain + // text, but it then doesn't need to sanitized. + nsString rawBody; + if (HTMLBodyPart.IsEmpty()) { + if (composeHTMLFormat) { + nsCString escaped; + nsAppendEscapedHTML(bodyPart, escaped); + CopyUTF8toUTF16(escaped, sanitizedBody); + } else + CopyUTF8toUTF16(bodyPart, rawBody); + } else + CopyUTF8toUTF16(HTMLBodyPart, rawBody); + + if (!rawBody.IsEmpty() && composeHTMLFormat) { + // For security reason, we must sanitize the message body before + // accepting any html... + + rv = HTMLSanitize(rawBody, sanitizedBody); // from mimemoz2.h + + if (NS_FAILED(rv)) { + // Something went horribly wrong with parsing for html format + // in the body. Set composeHTMLFormat to false so we show the + // plain text mail compose. + composeHTMLFormat = false; + } + } + + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams(do_CreateInstance( + "@mozilla.org/messengercompose/composeparams;1", &rv)); + if (NS_SUCCEEDED(rv) && pMsgComposeParams) { + pMsgComposeParams->SetType(nsIMsgCompType::MailToUrl); + pMsgComposeParams->SetFormat(composeHTMLFormat + ? nsIMsgCompFormat::HTML + : nsIMsgCompFormat::PlainText); + + nsCOMPtr<nsIMsgCompFields> pMsgCompFields(do_CreateInstance( + "@mozilla.org/messengercompose/composefields;1", &rv)); + if (pMsgCompFields) { + // ugghh more conversion work!!!! + pMsgCompFields->SetTo(NS_ConvertUTF8toUTF16(toPart)); + pMsgCompFields->SetCc(NS_ConvertUTF8toUTF16(ccPart)); + pMsgCompFields->SetBcc(NS_ConvertUTF8toUTF16(bccPart)); + pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(newsgroup)); + pMsgCompFields->SetReferences(refPart.get()); + pMsgCompFields->SetSubject(NS_ConvertUTF8toUTF16(subjectPart)); + pMsgCompFields->SetBody(composeHTMLFormat ? sanitizedBody : rawBody); + pMsgComposeParams->SetComposeFields(pMsgCompFields); + + NS_ADDREF(*aParams = pMsgComposeParams); + return NS_OK; + } + } // if we created msg compose params.... + } // if we had a mailto url + } // if we had a url... + + // if we got here we must have encountered an error + *aParams = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgComposeService::OpenComposeWindowWithURI( + const char* aMsgComposeWindowURL, nsIURI* aURI, nsIMsgIdentity* identity) { + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = GetParamsForMailto(aURI, getter_AddRefs(pMsgComposeParams)); + if (NS_SUCCEEDED(rv)) { + pMsgComposeParams->SetIdentity(identity); + rv = OpenComposeWindowWithParams(aMsgComposeWindowURL, pMsgComposeParams); + } + return rv; +} + +NS_IMETHODIMP nsMsgComposeService::InitCompose(nsIMsgComposeParams* aParams, + mozIDOMWindowProxy* aWindow, + nsIDocShell* aDocShell, + nsIMsgCompose** _retval) { + nsresult rv; + nsCOMPtr<nsIMsgCompose> msgCompose = + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = msgCompose->Initialize(aParams, aWindow, aDocShell); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*_retval = msgCompose); + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::GetDefaultIdentity(nsIMsgIdentity** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, rv); + + return defaultAccount ? defaultAccount->GetDefaultIdentity(_retval) : NS_OK; +} + +/* readonly attribute boolean logComposePerformance; */ +NS_IMETHODIMP nsMsgComposeService::GetLogComposePerformance( + bool* aLogComposePerformance) { + *aLogComposePerformance = mLogComposePerformance; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeService::TimeStamp(const char* label, + bool resetTime) { + if (!mLogComposePerformance) return NS_OK; + +#ifdef MSGCOMP_TRACE_PERFORMANCE + + PRIntervalTime now; + + if (resetTime) { + MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, + ("\n[process]: [totalTime][deltaTime]\n--------------------\n")); + + mStartTime = PR_IntervalNow(); + mPreviousTime = mStartTime; + now = mStartTime; + } else + now = PR_IntervalNow(); + + PRIntervalTime totalTime = PR_IntervalToMilliseconds(now - mStartTime); + PRIntervalTime deltaTime = PR_IntervalToMilliseconds(now - mPreviousTime); + + MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, + ("[%3.2f][%3.2f] - %s\n", ((double)totalTime / 1000.0) + 0.005, + ((double)deltaTime / 1000.0) + 0.005, label)); + + mPreviousTime = now; +#endif + return NS_OK; +} + +class nsMsgTemplateReplyHelper final : public nsIStreamListener, + public nsIUrlListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + nsMsgTemplateReplyHelper(); + + nsCOMPtr<nsIMsgDBHdr> mHdrToReplyTo; + nsCOMPtr<nsIMsgDBHdr> mTemplateHdr; + nsCOMPtr<nsIMsgWindow> mMsgWindow; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCString mTemplateBody; + bool mInMsgBody; + char mLastBlockChars[3]; + + private: + ~nsMsgTemplateReplyHelper(); +}; + +NS_IMPL_ISUPPORTS(nsMsgTemplateReplyHelper, nsIStreamListener, + nsIRequestObserver, nsIUrlListener) + +nsMsgTemplateReplyHelper::nsMsgTemplateReplyHelper() { + mInMsgBody = false; + memset(mLastBlockChars, 0, sizeof(mLastBlockChars)); +} + +nsMsgTemplateReplyHelper::~nsMsgTemplateReplyHelper() {} + +NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStartRunningUrl(nsIURI* aUrl) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStopRunningUrl(nsIURI* aUrl, + nsresult aExitCode) { + NS_ENSURE_SUCCESS(aExitCode, aExitCode); + nsresult rv; + nsCOMPtr<nsPIDOMWindowOuter> parentWindow; + if (mMsgWindow) { + nsCOMPtr<nsIDocShell> docShell; + rv = mMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + parentWindow = do_GetInterface(docShell); + NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE); + } + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv)); + if (NS_FAILED(rv) || (!pMsgComposeParams)) return rv; + nsCOMPtr<nsIMsgCompFields> compFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + + nsCString replyTo; + mHdrToReplyTo->GetStringProperty("replyTo", replyTo); + if (replyTo.IsEmpty()) mHdrToReplyTo->GetAuthor(getter_Copies(replyTo)); + compFields->SetTo(NS_ConvertUTF8toUTF16(replyTo)); + + nsString body; + nsString templateSubject, replySubject; + + mHdrToReplyTo->GetMime2DecodedSubject(replySubject); + mTemplateHdr->GetMime2DecodedSubject(templateSubject); + nsString subject(u"Auto: "_ns); // RFC 3834 3.1.5. + subject.Append(templateSubject); + if (!replySubject.IsEmpty()) { + subject.AppendLiteral(u" (was: "); + subject.Append(replySubject); + subject.Append(u')'); + } + + compFields->SetSubject(subject); + compFields->SetRawHeader("Auto-Submitted", "auto-replied"_ns); + + nsCString charset; + rv = mTemplateHdr->GetCharset(getter_Copies(charset)); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsMsgI18NConvertToUnicode(charset, mTemplateBody, body); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "couldn't convert templ body to unicode"); + compFields->SetBody(body); + + nsCString msgUri; + nsCOMPtr<nsIMsgFolder> folder; + mHdrToReplyTo->GetFolder(getter_AddRefs(folder)); + folder->GetUriForMsg(mHdrToReplyTo, msgUri); + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::ReplyWithTemplate); + pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default); + pMsgComposeParams->SetIdentity(mIdentity); + pMsgComposeParams->SetComposeFields(compFields); + pMsgComposeParams->SetOriginalMsgURI(msgUri); + + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose( + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion + * response **/ + + rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<mozilla::dom::Promise> promise; + return pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, mIdentity, nullptr, + nullptr, nullptr, getter_AddRefs(promise)); +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnStartRequest(nsIRequest* request) { return NS_OK; } + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnStopRequest(nsIRequest* request, nsresult status) { + if (NS_SUCCEEDED(status)) { + // now we've got the message body in mTemplateBody - + // need to set body in compose params and send the reply. + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnDataAvailable(nsIRequest* request, + nsIInputStream* inStream, + uint64_t srcOffset, uint32_t count) { + nsresult rv = NS_OK; + + char readBuf[1024]; + + uint64_t available; + uint32_t readCount; + uint32_t maxReadCount = sizeof(readBuf) - 1; + + rv = inStream->Available(&available); + while (NS_SUCCEEDED(rv) && available > 0) { + uint32_t bodyOffset = 0, readOffset = 0; + if (!mInMsgBody && mLastBlockChars[0]) { + memcpy(readBuf, mLastBlockChars, 3); + readOffset = 3; + maxReadCount -= 3; + } + if (maxReadCount > available) maxReadCount = (uint32_t)available; + memset(readBuf, 0, sizeof(readBuf)); + rv = inStream->Read(readBuf + readOffset, maxReadCount, &readCount); + available -= readCount; + readCount += readOffset; + // we're mainly interested in the msg body, so we need to + // find the header/body delimiter of a blank line. A blank line + // looks like <CR><CR>, <LF><LF>, or <CRLF><CRLF> + if (!mInMsgBody) { + for (uint32_t charIndex = 0; charIndex < readCount && !bodyOffset; + charIndex++) { + if (readBuf[charIndex] == '\r' || readBuf[charIndex] == '\n') { + if (charIndex + 1 < readCount) { + if (readBuf[charIndex] == readBuf[charIndex + 1]) { + // got header+body separator + bodyOffset = charIndex + 2; + break; + } else if ((charIndex + 3 < readCount) && + !strncmp(readBuf + charIndex, "\r\n\r\n", 4)) { + bodyOffset = charIndex + 4; + break; + } + } + } + } + mInMsgBody = bodyOffset != 0; + if (!mInMsgBody && readCount > 3) // still in msg hdrs + strncpy(mLastBlockChars, readBuf + readCount - 3, 3); + } + mTemplateBody.Append(readBuf + bodyOffset); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeService::ReplyWithTemplate( + nsIMsgDBHdr* aMsgHdr, const nsACString& templateUri, + nsIMsgWindow* aMsgWindow, nsIMsgIncomingServer* aServer) { + // To reply with template, we need the message body of the template. + // I think we're going to need to stream the template message to ourselves, + // and construct the body, and call setBody on the compFields. + nsresult rv; + const nsPromiseFlatCString& templateUriFlat = PromiseFlatCString(templateUri); + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgIdentity>> identities; + rv = account->GetIdentities(identities); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString recipients; + aMsgHdr->GetRecipients(getter_Copies(recipients)); + + nsAutoCString ccList; + aMsgHdr->GetCcList(getter_Copies(ccList)); + + // Go through the identities to see to whom this was addressed. + // In case we get no match, this is likely a list/bulk/bcc/spam mail and we + // shouldn't reply. RFC 3834 2. + nsCOMPtr<nsIMsgIdentity> identity; // identity to reply from + for (auto anIdentity : identities) { + nsAutoCString identityEmail; + anIdentity->GetEmail(identityEmail); + + if (FindInReadable(identityEmail, recipients, + nsCaseInsensitiveCStringComparator) || + FindInReadable(identityEmail, ccList, + nsCaseInsensitiveCStringComparator)) { + identity = anIdentity; + break; + } + } + if (!identity) // Found no match -> don't reply. + return NS_ERROR_ABORT; + + RefPtr<nsMsgTemplateReplyHelper> helper = new nsMsgTemplateReplyHelper; + + helper->mHdrToReplyTo = aMsgHdr; + helper->mMsgWindow = aMsgWindow; + helper->mIdentity = identity; + + nsAutoCString replyTo; + aMsgHdr->GetStringProperty("replyTo", replyTo); + if (replyTo.IsEmpty()) aMsgHdr->GetAuthor(getter_Copies(replyTo)); + if (replyTo.IsEmpty()) return NS_ERROR_FAILURE; // nowhere to send the reply + + nsCOMPtr<nsIMsgFolder> templateFolder; + nsCOMPtr<nsIMsgDatabase> templateDB; + nsCString templateMsgHdrUri; + const char* query = PL_strstr(templateUriFlat.get(), "?messageId="); + if (!query) return NS_ERROR_FAILURE; + + nsAutoCString folderUri(Substring(templateUriFlat.get(), query)); + rv = GetExistingFolder(folderUri, getter_AddRefs(templateFolder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = templateFolder->GetMsgDatabase(getter_AddRefs(templateDB)); + NS_ENSURE_SUCCESS(rv, rv); + + const char* subject = PL_strstr(templateUriFlat.get(), "&subject="); + if (subject) { + const char* subjectEnd = subject + strlen(subject); + nsAutoCString messageId(Substring(query + 11, subject)); + nsAutoCString subjectString(Substring(subject + 9, subjectEnd)); + templateDB->GetMsgHdrForMessageID(messageId.get(), + getter_AddRefs(helper->mTemplateHdr)); + if (helper->mTemplateHdr) + templateFolder->GetUriForMsg(helper->mTemplateHdr, templateMsgHdrUri); + // to use the subject, we'd need to expose a method to find a message by + // subject, or painfully iterate through messages...We'll try to make the + // message-id not change when saving a template first. + } + if (templateMsgHdrUri.IsEmpty()) { + // ### probably want to return a specific error and + // have the calling code disable the filter. + NS_ASSERTION(false, "failed to get msg hdr"); + return NS_ERROR_FAILURE; + } + // we need to convert the template uri, which is of the form + // <folder uri>?messageId=<messageId>&subject=<subject> + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(templateMsgHdrUri, getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> listenerSupports; + helper->QueryInterface(NS_GET_IID(nsISupports), + getter_AddRefs(listenerSupports)); + + nsCOMPtr<nsIURI> dummyNull; + rv = msgService->StreamMessage( + templateMsgHdrUri, listenerSupports, aMsgWindow, helper, + false, // convert data + EmptyCString(), false, getter_AddRefs(dummyNull)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> folder; + aMsgHdr->GetFolder(getter_AddRefs(folder)); + if (!folder) return NS_ERROR_NULL_POINTER; + + // We're sending a new message. Conceptually it's a reply though, so mark the + // original message as replied. + return folder->AddMessageDispositionState( + aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Replied); +} + +NS_IMETHODIMP +nsMsgComposeService::ForwardMessage(const nsAString& forwardTo, + nsIMsgDBHdr* aMsgHdr, + nsIMsgWindow* aMsgWindow, + nsIMsgIncomingServer* aServer, + uint32_t aForwardType) { + NS_ENSURE_ARG_POINTER(aMsgHdr); + + nsresult rv; + if (aForwardType == nsIMsgComposeService::kForwardAsDefault) { + int32_t forwardPref = 0; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + prefBranch->GetIntPref("mail.forward_message_mode", &forwardPref); + // 0=default as attachment 2=forward as inline with attachments, + // (obsolete 4.x value)1=forward as quoted (mapped to 2 in mozilla) + aForwardType = forwardPref == 0 ? nsIMsgComposeService::kForwardAsAttachment + : nsIMsgComposeService::kForwardInline; + } + nsCString msgUri; + + nsCOMPtr<nsIMsgFolder> folder; + aMsgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER); + + folder->GetUriForMsg(aMsgHdr, msgUri); + + nsAutoCString uriToOpen(msgUri); + + // get the MsgIdentity for the above key using AccountManager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + nsCOMPtr<nsIMsgIdentity> identity; + + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + rv = account->GetDefaultIdentity(getter_AddRefs(identity)); + // Use default identity if no identity has been found on this account + if (NS_FAILED(rv) || !identity) { + rv = GetDefaultIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aForwardType == nsIMsgComposeService::kForwardInline) + return RunMessageThroughMimeDraft( + uriToOpen, nsMimeOutput::nsMimeMessageDraftOrTemplate, identity, + uriToOpen, aMsgHdr, true, forwardTo, false, aMsgWindow, false); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow; + if (aMsgWindow) { + nsCOMPtr<nsIDocShell> docShell; + rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + parentWindow = do_GetInterface(docShell); + NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE); + } + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgCompFields> compFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + + compFields->SetTo(forwardTo); + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::ForwardAsAttachment); + pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetComposeFields(compFields); + pMsgComposeParams->SetOriginalMsgURI(uriToOpen); + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose( + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion + * response **/ + rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<Promise> promise; + rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, + nullptr, nullptr, getter_AddRefs(promise)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsMsgCompose::ProcessReplyFlags usually takes care of marking messages + // as forwarded. ProcessReplyFlags is normally called from + // nsMsgComposeSendListener::OnStopSending but for this case the msgCompose + // object is not set so ProcessReplyFlags won't get called. + // Therefore, let's just mark it here instead. + return folder->AddMessageDispositionState( + aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded); +} + +nsresult nsMsgComposeService::AddGlobalHtmlDomains() { + nsresult rv; + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> defaultsPrefBranch; + rv = prefs->GetDefaultBranch(MAILNEWS_ROOT_PREF, + getter_AddRefs(defaultsPrefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + /** + * Check to see if we need to add any global domains. + * If so, make sure the following prefs are added to mailnews.js + * + * 1. pref("mailnews.global_html_domains.version", version number); + * This pref registers the current version in the user prefs file. A default + * value is stored in mailnews file. Depending the changes we plan to make we + * can move the default version number. Comparing version number from user's + * prefs file and the default one from mailnews.js, we can effect ppropriate + * changes. + * + * 2. pref("mailnews.global_html_domains", <comma separated domain list>); + * This pref contains the list of html domains that ISP can add to make that + * user's contain all of these under the HTML domains in the + * Mail&NewsGrpus|Send Format under global preferences. + */ + int32_t htmlDomainListCurrentVersion, htmlDomainListDefaultVersion; + rv = prefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, + &htmlDomainListCurrentVersion); + NS_ENSURE_SUCCESS(rv, rv); + + rv = defaultsPrefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, + &htmlDomainListDefaultVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // Update the list as needed + if (htmlDomainListCurrentVersion <= htmlDomainListDefaultVersion) { + // Get list of global domains need to be added + nsCString globalHtmlDomainList; + rv = prefBranch->GetCharPref(HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME, + globalHtmlDomainList); + + if (NS_SUCCEEDED(rv) && !globalHtmlDomainList.IsEmpty()) { + nsTArray<nsCString> domainArray; + + // Get user's current HTML domain set for send format + nsCString currentHtmlDomainList; + rv = prefBranch->GetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, + currentHtmlDomainList); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString newHtmlDomainList(currentHtmlDomainList); + // Get the current html domain list into new list var + ParseString(currentHtmlDomainList, DOMAIN_DELIMITER, domainArray); + + // Get user's current Plaintext domain set for send format + nsCString currentPlaintextDomainList; + rv = prefBranch->GetCharPref(USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME, + currentPlaintextDomainList); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current plaintext domain list into new list var + ParseString(currentPlaintextDomainList, DOMAIN_DELIMITER, domainArray); + + size_t i = domainArray.Length(); + if (i > 0) { + // Append each domain in the preconfigured html domain list + globalHtmlDomainList.StripWhitespace(); + ParseString(globalHtmlDomainList, DOMAIN_DELIMITER, domainArray); + + // Now add each domain that does not already appear in + // the user's current html or plaintext domain lists + for (; i < domainArray.Length(); i++) { + if (domainArray.IndexOf(domainArray[i]) == i) { + if (!newHtmlDomainList.IsEmpty()) + newHtmlDomainList += DOMAIN_DELIMITER; + newHtmlDomainList += domainArray[i]; + } + } + } else { + // User has no domains listed either in html or plain text category. + // Assign the global list to be the user's current html domain list + newHtmlDomainList = globalHtmlDomainList; + } + + // Set user's html domain pref with the updated list + rv = prefBranch->SetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, + newHtmlDomainList); + NS_ENSURE_SUCCESS(rv, rv); + + // Increase the version to avoid running the update code unless needed + // (based on default version) + rv = prefBranch->SetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, + htmlDomainListCurrentVersion + 1); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeService::RegisterComposeDocShell(nsIDocShell* aDocShell, + nsIMsgCompose* aComposeObject) { + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aComposeObject); + + nsresult rv; + + // add the msg compose / dom window mapping to our hash table + nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsWeakPtr weakMsgComposePtr = do_GetWeakReference(aComposeObject); + NS_ENSURE_SUCCESS(rv, rv); + mOpenComposeWindows.InsertOrUpdate(weakDocShell, weakMsgComposePtr); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::UnregisterComposeDocShell(nsIDocShell* aDocShell) { + NS_ENSURE_ARG_POINTER(aDocShell); + + nsresult rv; + nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mOpenComposeWindows.Remove(weakDocShell); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::GetMsgComposeForDocShell(nsIDocShell* aDocShell, + nsIMsgCompose** aComposeObject) { + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aComposeObject); + + if (!mOpenComposeWindows.Count()) return NS_ERROR_FAILURE; + + // get the weak reference for our dom window + nsresult rv; + nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsWeakPtr weakMsgComposePtr; + + if (!mOpenComposeWindows.Get(weakDocShell, getter_AddRefs(weakMsgComposePtr))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(weakMsgComposePtr, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aComposeObject = msgCompose); + return rv; +} + +/** + * LoadDraftOrTemplate + * Helper routine used to run msgURI through libmime in order to fetch the + * contents for a draft or template. + */ +nsresult nsMsgComposeService::LoadDraftOrTemplate( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI, + nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, bool overrideComposeFormat, + nsIMsgWindow* aMsgWindow, bool autodetectCharset) { + return RunMessageThroughMimeDraft( + aMsgURI, aOutType, aIdentity, aOriginalMsgURI, aOrigMsgHdr, + aForwardInline, EmptyString(), overrideComposeFormat, aMsgWindow, + autodetectCharset); +} + +/** + * Run the aMsgURI message through libmime. We set various attributes of the + * nsIMimeStreamConverter so mimedrft.cpp will know what to do with the message + * when its done streaming. Usually that will be opening a compose window + * with the contents of the message, but if forwardTo is non-empty, mimedrft.cpp + * will forward the contents directly. + * + * @param aMsgURI URI to stream, which is the msgUri + any extra terms, e.g., + * "redirect=true". + * @param aOutType nsMimeOutput::nsMimeMessageDraftOrTemplate or + * nsMimeOutput::nsMimeMessageEditorTemplate + * @param aIdentity identity to use for the new message + * @param aOriginalMsgURI msgURI w/o any extra terms + * @param aOrigMsgHdr nsIMsgDBHdr corresponding to aOriginalMsgURI + * @param aForwardInline true if doing a forward inline + * @param aForwardTo e-mail address to forward msg to. This is used for + * forward inline message filter actions. + * @param aOverrideComposeFormat True if the user had shift key down when + doing a command that opens the compose window, + * which means we switch the compose window used + * from the default. + * @param aMsgWindow msgWindow to pass into LoadMessage. + */ +nsresult nsMsgComposeService::RunMessageThroughMimeDraft( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI, + nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, const nsAString& aForwardTo, + bool aOverrideComposeFormat, nsIMsgWindow* aMsgWindow, + bool autodetectCharset) { + nsCOMPtr<nsIMsgMessageService> messageService; + nsresult rv = + GetMessageServiceFromURI(aMsgURI, getter_AddRefs(messageService)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a mime parser (nsIMimeStreamConverter)to do the conversion. + nsCOMPtr<nsIMimeStreamConverter> mimeConverter = do_CreateInstance( + "@mozilla.org/streamconv;1?from=message/rfc822&to=application/xhtml+xml", + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mimeConverter->SetMimeOutputType( + aOutType); // Set the type of output for libmime + mimeConverter->SetForwardInline(aForwardInline); + if (!aForwardTo.IsEmpty()) { + mimeConverter->SetForwardInlineFilter(true); + mimeConverter->SetForwardToAddress(aForwardTo); + } + mimeConverter->SetOverrideComposeFormat(aOverrideComposeFormat); + mimeConverter->SetIdentity(aIdentity); + mimeConverter->SetOriginalMsgURI(aOriginalMsgURI); + mimeConverter->SetOrigMsgHdr(aOrigMsgHdr); + + nsCOMPtr<nsIURI> url; + bool fileUrl = StringBeginsWith(aMsgURI, "file:"_ns); + nsCString mailboxUri(aMsgURI); + if (fileUrl) { + // We loaded a .eml file from a file: url. Construct equivalent mailbox url. + mailboxUri.Replace(0, 5, "mailbox:"_ns); + mailboxUri.AppendLiteral("&number=0"); + // Need this to prevent nsMsgCompose::TagEmbeddedObjects from setting + // inline images as moz-do-not-send. + mimeConverter->SetOriginalMsgURI(mailboxUri); + } + if (fileUrl || PromiseFlatCString(aMsgURI).Find( + "&type=application/x-message-display") >= 0) + rv = NS_NewURI(getter_AddRefs(url), mailboxUri); + else + rv = messageService->GetUrlForUri(aMsgURI, aMsgWindow, getter_AddRefs(url)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(url); + if (!mailnewsurl) { + NS_WARNING( + "Trying to run a message through MIME which doesn't have a " + "nsIMsgMailNewsUrl?"); + return NS_ERROR_UNEXPECTED; + } + // SetSpecInternal must not fail, or else the URL won't have a base URL and + // we'll crash later. + rv = mailnewsurl->SetSpecInternal(mailboxUri); + NS_ENSURE_SUCCESS(rv, rv); + + // if we are forwarding a message and that message used a charset override + // then forward that as auto-detect flag, too. + nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(url)); + if (i18nUrl) (void)i18nUrl->SetAutodetectCharset(autodetectCharset); + + nsCOMPtr<nsIPrincipal> nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannel( + getter_AddRefs(channel), url, nullptr, nullPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed."); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIStreamConverter> converter = do_QueryInterface(mimeConverter); + rv = converter->AsyncConvertData(nullptr, nullptr, nullptr, channel); + NS_ENSURE_SUCCESS(rv, rv); + + // Now, just plug the two together and get the hell out of the way! + nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(mimeConverter); + return messageService->LoadMessage(aMsgURI, streamListener, aMsgWindow, + nullptr, autodetectCharset); +} + +NS_IMETHODIMP +nsMsgComposeService::Handle(nsICommandLine* aCmdLine) { + NS_ENSURE_ARG_POINTER(aCmdLine); + + nsresult rv; + int32_t found, end, count; + nsAutoString uristr; + bool composeShouldHandle = true; + + rv = aCmdLine->FindFlag(u"compose"_ns, false, &found); + NS_ENSURE_SUCCESS(rv, rv); + +#ifndef MOZ_SUITE + // MAC OS X passes in -url mailto:mscott@mozilla.org into the command line + // instead of -compose. + if (found == -1) { + rv = aCmdLine->FindFlag(u"url"_ns, false, &found); + NS_ENSURE_SUCCESS(rv, rv); + // we don't want to consume the argument for -url unless we're sure it is a + // mailto url and we'll figure that out shortly. + composeShouldHandle = false; + } +#endif + + if (found == -1) return NS_OK; + + end = found; + + rv = aCmdLine->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count > found + 1) { + aCmdLine->GetArgument(found + 1, uristr); + if (StringBeginsWith(uristr, u"mailto:"_ns) || + StringBeginsWith(uristr, u"preselectid="_ns) || + StringBeginsWith(uristr, u"to="_ns) || + StringBeginsWith(uristr, u"cc="_ns) || + StringBeginsWith(uristr, u"bcc="_ns) || + StringBeginsWith(uristr, u"newsgroups="_ns) || + StringBeginsWith(uristr, u"subject="_ns) || + StringBeginsWith(uristr, u"format="_ns) || + StringBeginsWith(uristr, u"body="_ns) || + StringBeginsWith(uristr, u"attachment="_ns) || + StringBeginsWith(uristr, u"message="_ns) || + StringBeginsWith(uristr, u"from="_ns)) { + composeShouldHandle = true; // the -url argument looks like mailto + end++; + // mailto: URIs are frequently passed with spaces in them. They should be + // escaped with %20, but we hack around broken clients. See bug 231032. + while (end + 1 < count) { + nsAutoString curarg; + aCmdLine->GetArgument(end + 1, curarg); + if (curarg.First() == '-') break; + + uristr.Append(' '); + uristr.Append(curarg); + ++end; + } + } else { + uristr.Truncate(); + } + } + if (composeShouldHandle) { + aCmdLine->RemoveArguments(found, end); + + nsCOMPtr<nsIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupportsString> arg( + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + if (arg) arg->SetData(uristr); + + nsCOMPtr<nsIMutableArray> params(do_CreateInstance(NS_ARRAY_CONTRACTID)); + params->AppendElement(arg); + params->AppendElement(aCmdLine); + + nsCOMPtr<mozIDOMWindowProxy> opened; + wwatch->OpenWindow(nullptr, DEFAULT_CHROME, "_blank"_ns, + "chrome,dialog=no,all"_ns, params, + getter_AddRefs(opened)); + + aCmdLine->SetPreventDefault(true); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeService::GetHelpInfo(nsACString& aResult) { + // clang-format off + aResult.AssignLiteral( + " -compose [ <options> ] Compose a mail or news message. Options are specified\n" + " as string \"option='value,...',option=value,...\" and\n" + " include: from, to, cc, bcc, newsgroups, subject, body,\n" + " message (file), attachment (file), format (html | text).\n" + " Example: \"to=john@example.com,subject='Dinner tonight?'\"\n"); + return NS_OK; + // clang-format on +} |