diff options
Diffstat (limited to 'mailnews/mime/emitters/nsMimeHtmlEmitter.cpp')
-rw-r--r-- | mailnews/mime/emitters/nsMimeHtmlEmitter.cpp | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp new file mode 100644 index 0000000000..d68d7f15c8 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp @@ -0,0 +1,543 @@ +/* -*- Mode: C; tab-width: 4; 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 "nsCOMPtr.h" +#include <stdio.h> +#include "nsMimeRebuffer.h" +#include "nsMimeHtmlEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "nsEmitterUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMimeTypes.h" +#include "prtime.h" +#include "prprf.h" +#include "nsIStringEnumerator.h" +#include "nsServiceManagerUtils.h" +// hack: include this to fix opening news attachments. +#include "nsINntpUrl.h" +#include "nsComponentManagerUtils.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsMsgUtils.h" +#include "nsAutoPtr.h" +#include "nsINetUtil.h" +#include "nsMemory.h" +#include "mozilla/Services.h" + +#define VIEW_ALL_HEADERS 2 + +/** + * A helper class to implement nsIUTF8StringEnumerator + */ + +class nsMimeStringEnumerator final : public nsIUTF8StringEnumerator { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR + + nsMimeStringEnumerator() : mCurrentIndex(0) {} + + template<class T> + nsCString* Append(T value) { return mValues.AppendElement(value); } + +protected: + ~nsMimeStringEnumerator() {} + nsTArray<nsCString> mValues; + uint32_t mCurrentIndex; // consumers expect first-in first-out enumeration +}; + +NS_IMPL_ISUPPORTS(nsMimeStringEnumerator, nsIUTF8StringEnumerator) + +NS_IMETHODIMP +nsMimeStringEnumerator::HasMore(bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = mCurrentIndex < mValues.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeStringEnumerator::GetNext(nsACString& result) +{ + if (mCurrentIndex >= mValues.Length()) + return NS_ERROR_UNEXPECTED; + + result = mValues[mCurrentIndex++]; + return NS_OK; +} + +/* + * nsMimeHtmlEmitter definitions.... + */ +nsMimeHtmlDisplayEmitter::nsMimeHtmlDisplayEmitter() : nsMimeBaseEmitter() +{ + mFirst = true; + mSkipAttachment = false; +} + +nsMimeHtmlDisplayEmitter::~nsMimeHtmlDisplayEmitter(void) +{ +} + +nsresult nsMimeHtmlDisplayEmitter::Init() +{ + return NS_OK; +} + +bool nsMimeHtmlDisplayEmitter::BroadCastHeadersAndAttachments() +{ + // try to get a header sink if there is one.... + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (NS_SUCCEEDED(rv) && headerSink && mDocHeader) + return true; + else + return false; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name) +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(name); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTML(const char *field, const char *value) +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTML(field, value); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPostfix() +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix(); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink) +{ + nsresult rv = NS_OK; + if ( (mChannel) && (!mHeaderSink) ) + { + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri)); + if (msgurl) + { + msgurl->GetMsgHeaderSink(getter_AddRefs(mHeaderSink)); + if (!mHeaderSink) // if the url is not overriding the header sink, then just get the one from the msg window + { + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(mHeaderSink)); + } + } + } + } + + *aHeaderSink = mHeaderSink; + NS_IF_ADDREF(*aHeaderSink); + return rv; +} + +nsresult nsMimeHtmlDisplayEmitter::BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup) +{ + // two string enumerators to pass out to the header sink + RefPtr<nsMimeStringEnumerator> headerNameEnumerator = new nsMimeStringEnumerator(); + NS_ENSURE_TRUE(headerNameEnumerator, NS_ERROR_OUT_OF_MEMORY); + RefPtr<nsMimeStringEnumerator> headerValueEnumerator = new nsMimeStringEnumerator(); + NS_ENSURE_TRUE(headerValueEnumerator, NS_ERROR_OUT_OF_MEMORY); + + nsCString extraExpandedHeaders; + nsTArray<nsCString> extraExpandedHeadersArray; + nsAutoCString convertedDateString; + + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + pPrefBranch->GetCharPref("mailnews.headers.extraExpandedHeaders", getter_Copies(extraExpandedHeaders)); + // todo - should make this upper case + if (!extraExpandedHeaders.IsEmpty()) + { + ToLowerCase(extraExpandedHeaders); + ParseString(extraExpandedHeaders, ' ', extraExpandedHeadersArray); + } + } + + for (size_t i = 0; i < mHeaderArray->Length(); i++) + { + headerInfoType * headerInfo = mHeaderArray->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || (!headerInfo->value) || (!(*headerInfo->value))) + continue; + + const char * headerValue = headerInfo->value; + + // optimization: if we aren't in view all header view mode, we only show a small set of the total # of headers. + // don't waste time sending those out to the UI since the UI is going to ignore them anyway. + if (aHeaderMode != VIEW_ALL_HEADERS && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) + { + nsDependentCString headerStr(headerInfo->name); + if (PL_strcasecmp("to", headerInfo->name) && PL_strcasecmp("from", headerInfo->name) && + PL_strcasecmp("cc", headerInfo->name) && PL_strcasecmp("newsgroups", headerInfo->name) && + PL_strcasecmp("bcc", headerInfo->name) && PL_strcasecmp("followup-to", headerInfo->name) && + PL_strcasecmp("reply-to", headerInfo->name) && PL_strcasecmp("subject", headerInfo->name) && + PL_strcasecmp("organization", headerInfo->name) && PL_strcasecmp("user-agent", headerInfo->name) && + PL_strcasecmp("content-base", headerInfo->name) && PL_strcasecmp("sender", headerInfo->name) && + PL_strcasecmp("date", headerInfo->name) && PL_strcasecmp("x-mailer", headerInfo->name) && + PL_strcasecmp("content-type", headerInfo->name) && PL_strcasecmp("message-id", headerInfo->name) && + PL_strcasecmp("x-newsreader", headerInfo->name) && PL_strcasecmp("x-mimeole", headerInfo->name) && + PL_strcasecmp("references", headerInfo->name) && PL_strcasecmp("in-reply-to", headerInfo->name) && + PL_strcasecmp("list-post", headerInfo->name) && PL_strcasecmp("delivered-to", headerInfo->name) && + // make headerStr lower case because IndexOf is case-sensitive + (!extraExpandedHeadersArray.Length() || (ToLowerCase(headerStr), + !extraExpandedHeadersArray.Contains(headerStr)))) + continue; + } + + headerNameEnumerator->Append(headerInfo->name); + headerValueEnumerator->Append(headerValue); + + // Add a localized version of the date header if we encounter it. + if (!PL_strcasecmp("Date", headerInfo->name)) + { + headerNameEnumerator->Append("X-Mozilla-LocalizedDate"); + GenerateDateString(headerValue, convertedDateString, false); + headerValueEnumerator->Append(convertedDateString); + } + } + + aHeaderSink->ProcessHeaders(headerNameEnumerator, headerValueEnumerator, aFromNewsgroup); + return rv; +} + +NS_IMETHODIMP nsMimeHtmlDisplayEmitter::WriteHTMLHeaders(const nsACString &name) +{ + // if we aren't broadcasting headers OR printing...just do whatever + // our base class does... + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + return nsMimeBaseEmitter::WriteHTMLHeaders(name); + } + else if (!BroadCastHeadersAndAttachments() || !mDocHeader) + { + // This needs to be here to correct the output format if we are + // not going to broadcast headers to the XUL document. + if (mFormat == nsMimeOutput::nsMimeMessageBodyDisplay) + mFormat = nsMimeOutput::nsMimeMessagePrintOutput; + + return nsMimeBaseEmitter::WriteHTMLHeaders(name); + } + else + mFirstHeaders = false; + + bool bFromNewsgroups = false; + for (size_t j = 0; j < mHeaderArray->Length(); j++) + { + headerInfoType *headerInfo = mHeaderArray->ElementAt(j); + if (!(headerInfo && headerInfo->name && *headerInfo->name)) + continue; + + if (!PL_strcasecmp("Newsgroups", headerInfo->name)) + { + bFromNewsgroups = true; + break; + } + } + + // try to get a header sink if there is one.... + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + + if (headerSink) + { + int32_t viewMode = 0; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + rv = pPrefBranch->GetIntPref("mail.show_headers", &viewMode); + + rv = BroadcastHeaders(headerSink, viewMode, bFromNewsgroups); + } // if header Sink + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndHeader(const nsACString &name) +{ + if (mDocHeader && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) + { + UtilityWriteCRLF("<html>"); + UtilityWriteCRLF("<head>"); + + const char * val = GetHeaderValue(HEADER_SUBJECT); // do not free this value + if (val) + { + char * subject = MsgEscapeHTML(val); + if (subject) + { + int32_t bufLen = strlen(subject) + 16; + char *buf = new char[bufLen]; + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + PR_snprintf(buf, bufLen, "<title>%s</title>", subject); + UtilityWriteCRLF(buf); + delete [] buf; + free(subject); + } + } + + // Stylesheet info! + UtilityWriteCRLF("<link rel=\"important stylesheet\" href=\"chrome://messagebody/skin/messageBody.css\">"); + + UtilityWriteCRLF("</head>"); + UtilityWriteCRLF("<body>"); + } + + WriteHTMLHeaders(name); + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + rv = GetHeaderSink(getter_AddRefs(headerSink)); + + if (NS_SUCCEEDED(rv) && headerSink) + { + nsCString uriString; + + nsCOMPtr<nsIMsgMessageUrl> msgurl (do_QueryInterface(mURL, &rv)); + if (NS_SUCCEEDED(rv)) + { + // HACK: news urls require us to use the originalSpec. Everyone + // else uses GetURI to get the RDF resource which describes the message. + nsCOMPtr<nsINntpUrl> nntpUrl (do_QueryInterface(mURL, &rv)); + if (NS_SUCCEEDED(rv) && nntpUrl) + rv = msgurl->GetOriginalSpec(getter_Copies(uriString)); + else + rv = msgurl->GetUri(getter_Copies(uriString)); + } + + // we need to convert the attachment name from UTF-8 to unicode before + // we emit it. The attachment name has already been rfc2047 processed + // upstream of us. (Namely, mime_decode_filename has been called, deferring + // to nsIMimeHeaderParam.decodeParameter.) + nsString unicodeHeaderValue; + CopyUTF8toUTF16(name, unicodeHeaderValue); + + headerSink->HandleAttachment(contentType, url /* was escapedUrl */, + unicodeHeaderValue.get(), uriString.get(), + aIsExternalAttachment); + + mSkipAttachment = false; + } + else if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + // then we need to deal with the attachments in the body by inserting + // them into a table.. + rv = StartAttachmentInBody(name, contentType, url); + } + else + { + // If we don't need or cannot broadcast attachment info, just ignore it + mSkipAttachment = true; + rv = NS_OK; + } + + return rv; +} + +// Attachment handling routines +// Ok, we are changing the way we handle these now...It used to be that we output +// HTML to make a clickable link, etc... but now, this should just be informational +// and only show up during printing +// XXX should they also show up during quoting? +nsresult +nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const nsACString &name, + const char *contentType, + const char *url) +{ + mSkipAttachment = false; + bool p7mExternal = false; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetBoolPref("mailnews.p7m_external", &p7mExternal); + + if ( (contentType) && + ((!p7mExternal && !strcmp(contentType, APPLICATION_XPKCS7_MIME)) || + (!p7mExternal && !strcmp(contentType, APPLICATION_PKCS7_MIME)) || + (!strcmp(contentType, APPLICATION_XPKCS7_SIGNATURE)) || + (!strcmp(contentType, APPLICATION_PKCS7_SIGNATURE)) || + (!strcmp(contentType, TEXT_VCARD))) + ) + { + mSkipAttachment = true; + return NS_OK; + } + + if (mFirst) + { + UtilityWrite("<br><fieldset class=\"mimeAttachmentHeader\">"); + if (!name.IsEmpty()) + { + nsresult rv; + + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString attachmentsHeader; + bundle->GetStringFromName(u"attachmentsPrintHeader", + getter_Copies(attachmentsHeader)); + + UtilityWrite("<legend class=\"mimeAttachmentHeaderName\">"); + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(NS_ConvertUTF16toUTF8(attachmentsHeader).get())); + UtilityWrite(escapedName.get()); + UtilityWrite("</legend>"); + } + UtilityWrite("</fieldset>"); + UtilityWrite("<div class=\"mimeAttachmentWrap\">"); + UtilityWrite("<table class=\"mimeAttachmentTable\">"); + } + + UtilityWrite("<tr>"); + + UtilityWrite("<td class=\"mimeAttachmentFile\">"); + UtilityWrite(name); + UtilityWrite("</td>"); + + mFirst = false; + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::AddAttachmentField(const char *field, const char *value) +{ + if (mSkipAttachment) + return NS_OK; + + // Don't let bad things happen + if ( !value || !*value ) + return NS_OK; + + // Don't output this ugly header... + if (!strcmp(field, HEADER_X_MOZILLA_PART_URL)) + return NS_OK; + + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (NS_SUCCEEDED(rv) && headerSink) + { + headerSink->AddAttachmentField(field, value); + } + else + { + // Currently, we only care about the part size. + if (strcmp(field, HEADER_X_MOZILLA_PART_SIZE)) + return NS_OK; + + uint64_t size = atoi(value); + nsAutoString sizeString; + rv = FormatFileSize(size, false, sizeString); + UtilityWrite("<td class=\"mimeAttachmentSize\">"); + UtilityWrite(NS_ConvertUTF16toUTF8(sizeString).get()); + UtilityWrite("</td>"); + } + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndAttachment() +{ + if (mSkipAttachment) + return NS_OK; + + mSkipAttachment = false; // reset it for next attachment round + + if (BroadCastHeadersAndAttachments()) + return NS_OK; + + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + UtilityWrite("</tr>"); + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndAllAttachments() +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->OnEndAllAttachments(); + + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + UtilityWrite("</table>"); + UtilityWrite("</div>"); + } + + return rv; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteBody(const nsACString &buf, + uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndBody() +{ + if (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer) + { + UtilityWriteCRLF("</body>"); + UtilityWriteCRLF("</html>"); + } + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl (do_QueryInterface(mURL, &rv)); + if (headerSink) + headerSink->OnEndMsgHeaders(mailnewsUrl); + + return NS_OK; +} + + |