summaryrefslogtreecommitdiff
path: root/mailnews/mime/emitters/nsMimeBaseEmitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/mime/emitters/nsMimeBaseEmitter.cpp')
-rw-r--r--mailnews/mime/emitters/nsMimeBaseEmitter.cpp1092
1 files changed, 1092 insertions, 0 deletions
diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.cpp b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp
new file mode 100644
index 0000000000..223eef4335
--- /dev/null
+++ b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp
@@ -0,0 +1,1092 @@
+/* -*- 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 "nsMimeBaseEmitter.h"
+#include "nsMailHeaders.h"
+#include "nscore.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIServiceManager.h"
+#include "prmem.h"
+#include "nsEmitterUtils.h"
+#include "nsMimeStringResources.h"
+#include "msgCore.h"
+#include "nsIComponentManager.h"
+#include "nsEmitterUtils.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMimeCID.h"
+#include "mozilla/Logging.h"
+#include "prprf.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsDateTimeFormatCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsTextFormatter.h"
+#include "mozilla/Services.h"
+#include <algorithm>
+
+static PRLogModuleInfo * gMimeEmitterLogModule = nullptr;
+
+#define MIME_HEADER_URL "chrome://messenger/locale/mimeheader.properties"
+#define MIME_URL "chrome://messenger/locale/mime.properties"
+
+NS_IMPL_ISUPPORTS(nsMimeBaseEmitter, nsIMimeEmitter, nsIInterfaceRequestor)
+
+nsMimeBaseEmitter::nsMimeBaseEmitter()
+{
+ // Initialize data output vars...
+ mFirstHeaders = true;
+
+ mBufferMgr = nullptr;
+ mTotalWritten = 0;
+ mTotalRead = 0;
+ mInputStream = nullptr;
+ mOutStream = nullptr;
+ mOutListener = nullptr;
+
+ // Display output control vars...
+ mDocHeader = false;
+ m_stringBundle = nullptr;
+ mURL = nullptr;
+ mHeaderDisplayType = nsMimeHeaderDisplayTypes::NormalHeaders;
+
+ // Setup array for attachments
+ mAttachCount = 0;
+ mAttachArray = new nsTArray<attachmentInfoType*>();
+ mCurrentAttachment = nullptr;
+
+ // Header cache...
+ mHeaderArray = new nsTArray<headerInfoType*>();
+
+ // Embedded Header Cache...
+ mEmbeddedHeaderArray = nullptr;
+
+ // HTML Header Data...
+// mHTMLHeaders = "";
+// mCharset = "";
+
+ // Init the body...
+ mBodyStarted = false;
+// mBody = "";
+
+ // This is needed for conversion of I18N Strings...
+ mUnicodeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+
+ if (!gMimeEmitterLogModule)
+ gMimeEmitterLogModule = PR_NewLogModule("MIME");
+
+ // Do prefs last since we can live without this if it fails...
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (pPrefBranch)
+ pPrefBranch->GetIntPref("mail.show_headers", &mHeaderDisplayType);
+}
+
+nsMimeBaseEmitter::~nsMimeBaseEmitter(void)
+{
+ // Delete the buffer manager...
+ if (mBufferMgr)
+ {
+ delete mBufferMgr;
+ mBufferMgr = nullptr;
+ }
+
+ // Clean up the attachment array structures...
+ if (mAttachArray)
+ {
+ for (size_t i = 0; i < mAttachArray->Length(); i++)
+ {
+ attachmentInfoType *attachInfo = mAttachArray->ElementAt(i);
+ if (!attachInfo)
+ continue;
+
+ PR_FREEIF(attachInfo->contentType);
+ if (attachInfo->displayName)
+ NS_Free(attachInfo->displayName);
+ PR_FREEIF(attachInfo->urlSpec);
+ PR_FREEIF(attachInfo);
+ }
+ delete mAttachArray;
+ }
+
+ // Cleanup allocated header arrays...
+ CleanupHeaderArray(mHeaderArray);
+ mHeaderArray = nullptr;
+
+ CleanupHeaderArray(mEmbeddedHeaderArray);
+ mEmbeddedHeaderArray = nullptr;
+}
+
+NS_IMETHODIMP nsMimeBaseEmitter::GetInterface(const nsIID & aIID, void * *aInstancePtr)
+{
+ NS_ENSURE_ARG_POINTER(aInstancePtr);
+ return QueryInterface(aIID, aInstancePtr);
+}
+
+void
+nsMimeBaseEmitter::CleanupHeaderArray(nsTArray<headerInfoType*> *aArray)
+{
+ if (!aArray)
+ return;
+
+ for (size_t i = 0; i < aArray->Length(); i++)
+ {
+ headerInfoType *headerInfo = aArray->ElementAt(i);
+ if (!headerInfo)
+ continue;
+
+ PR_FREEIF(headerInfo->name);
+ PR_FREEIF(headerInfo->value);
+ PR_FREEIF(headerInfo);
+ }
+
+ delete aArray;
+}
+
+static int32_t MapHeaderNameToID(const char *header)
+{
+ // emitter passes UPPERCASE for header names
+ if (!strcmp(header, "DATE"))
+ return MIME_MHTML_DATE;
+ else if (!strcmp(header, "FROM"))
+ return MIME_MHTML_FROM;
+ else if (!strcmp(header, "SUBJECT"))
+ return MIME_MHTML_SUBJECT;
+ else if (!strcmp(header, "TO"))
+ return MIME_MHTML_TO;
+ else if (!strcmp(header, "SENDER"))
+ return MIME_MHTML_SENDER;
+ else if (!strcmp(header, "RESENT-TO"))
+ return MIME_MHTML_RESENT_TO;
+ else if (!strcmp(header, "RESENT-SENDER"))
+ return MIME_MHTML_RESENT_SENDER;
+ else if (!strcmp(header, "RESENT-FROM"))
+ return MIME_MHTML_RESENT_FROM;
+ else if (!strcmp(header, "RESENT-CC"))
+ return MIME_MHTML_RESENT_CC;
+ else if (!strcmp(header, "REPLY-TO"))
+ return MIME_MHTML_REPLY_TO;
+ else if (!strcmp(header, "REFERENCES"))
+ return MIME_MHTML_REFERENCES;
+ else if (!strcmp(header, "NEWSGROUPS"))
+ return MIME_MHTML_NEWSGROUPS;
+ else if (!strcmp(header, "MESSAGE-ID"))
+ return MIME_MHTML_MESSAGE_ID;
+ else if (!strcmp(header, "FOLLOWUP-TO"))
+ return MIME_MHTML_FOLLOWUP_TO;
+ else if (!strcmp(header, "CC"))
+ return MIME_MHTML_CC;
+ else if (!strcmp(header, "ORGANIZATION"))
+ return MIME_MHTML_ORGANIZATION;
+ else if (!strcmp(header, "BCC"))
+ return MIME_MHTML_BCC;
+
+ return 0;
+}
+
+char *
+nsMimeBaseEmitter::MimeGetStringByName(const char *aHeaderName)
+{
+ nsresult res = NS_OK;
+
+ if (!m_headerStringBundle)
+ {
+ static const char propertyURL[] = MIME_HEADER_URL;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService)
+ {
+ res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_headerStringBundle));
+ }
+ }
+
+ if (m_headerStringBundle)
+ {
+ nsString val;
+
+ res = m_headerStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aHeaderName).get(),
+ getter_Copies(val));
+
+ if (NS_FAILED(res))
+ return nullptr;
+
+ // Here we need to return a new copy of the string
+ // This returns a UTF-8 string so the caller needs to perform a conversion
+ // if this is used as UCS-2 (e.g. cannot do nsString(utfStr);
+ //
+ return ToNewUTF8String(val);
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+char *
+nsMimeBaseEmitter::MimeGetStringByID(int32_t aID)
+{
+ nsresult res = NS_OK;
+
+ if (!m_stringBundle)
+ {
+ static const char propertyURL[] = MIME_URL;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::services::GetStringBundleService();
+ if (sBundleService)
+ res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_stringBundle));
+ }
+
+ if (m_stringBundle)
+ {
+ nsString val;
+
+ res = m_stringBundle->GetStringFromID(aID, getter_Copies(val));
+
+ if (NS_FAILED(res))
+ return nullptr;
+
+ return ToNewUTF8String(val);
+ }
+ else
+ return nullptr;
+}
+
+//
+// This will search a string bundle (eventually) to find a descriptive header
+// name to match what was found in the mail message. aHeaderName is passed in
+// in all caps and a dropback default name is provided. The caller needs to free
+// the memory returned by this function.
+//
+char *
+nsMimeBaseEmitter::LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName)
+{
+ char *retVal = nullptr;
+
+ // prefer to use translated strings if not for quoting
+ if (mFormat != nsMimeOutput::nsMimeMessageQuoting &&
+ mFormat != nsMimeOutput::nsMimeMessageBodyQuoting)
+ {
+ // map name to id and get the translated string
+ int32_t id = MapHeaderNameToID(aHeaderName);
+ if (id > 0)
+ retVal = MimeGetStringByID(id);
+ }
+
+ // get the string from the other bundle (usually not translated)
+ if (!retVal)
+ retVal = MimeGetStringByName(aHeaderName);
+
+ if (retVal)
+ return retVal;
+ else
+ return strdup(aDefaultName);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// nsMimeBaseEmitter Interface
+///////////////////////////////////////////////////////////////////////////
+NS_IMETHODIMP
+nsMimeBaseEmitter::SetPipe(nsIInputStream * aInputStream, nsIOutputStream *outStream)
+{
+ mInputStream = aInputStream;
+ mOutStream = outStream;
+ return NS_OK;
+}
+
+// Note - these is setup only...you should not write
+// anything to the stream since these may be image data
+// output streams, etc...
+NS_IMETHODIMP
+nsMimeBaseEmitter::Initialize(nsIURI *url, nsIChannel * aChannel, int32_t aFormat)
+{
+ // set the url
+ mURL = url;
+ mChannel = aChannel;
+
+ // Create rebuffering object
+ delete mBufferMgr;
+ mBufferMgr = new MimeRebuffer();
+
+ // Counters for output stream
+ mTotalWritten = 0;
+ mTotalRead = 0;
+ mFormat = aFormat;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::SetOutputListener(nsIStreamListener *listener)
+{
+ mOutListener = listener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::GetOutputListener(nsIStreamListener **listener)
+{
+ NS_ENSURE_ARG_POINTER(listener);
+
+ NS_IF_ADDREF(*listener = mOutListener);
+ return NS_OK;
+}
+
+
+// Attachment handling routines
+nsresult
+nsMimeBaseEmitter::StartAttachment(const nsACString &name,
+ const char *contentType,
+ const char *url,
+ bool aIsExternalAttachment)
+{
+ // Ok, now we will setup the attachment info
+ mCurrentAttachment = (attachmentInfoType *) PR_NEWZAP(attachmentInfoType);
+ if ( (mCurrentAttachment) && mAttachArray)
+ {
+ ++mAttachCount;
+
+ mCurrentAttachment->displayName = ToNewCString(name);
+ mCurrentAttachment->urlSpec = strdup(url);
+ mCurrentAttachment->contentType = strdup(contentType);
+ mCurrentAttachment->isExternalAttachment = aIsExternalAttachment;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::EndAttachment()
+{
+ // Ok, add the attachment info to the attachment array...
+ if ( (mCurrentAttachment) && (mAttachArray) )
+ {
+ mAttachArray->AppendElement(mCurrentAttachment);
+ mCurrentAttachment = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::EndAllAttachments()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::AddAttachmentField(const char *field, const char *value)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWrite(const char *buf)
+{
+ NS_ENSURE_ARG_POINTER(buf);
+
+ uint32_t written;
+ Write(nsDependentCString(buf), &written);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWrite(const nsACString &buf)
+{
+ uint32_t written;
+ Write(buf, &written);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWriteCRLF(const char *buf)
+{
+ NS_ENSURE_ARG_POINTER(buf);
+
+ uint32_t written;
+ Write(nsDependentCString(buf), &written);
+ Write(NS_LITERAL_CSTRING(CRLF), &written);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::Write(const nsACString &buf, uint32_t *amountWritten)
+{
+ unsigned int written = 0;
+ nsresult rv = NS_OK;
+ uint32_t needToWrite;
+
+#ifdef DEBUG_BenB
+ // If you want to see libmime output...
+ printf("%s", buf);
+#endif
+
+ MOZ_LOG(gMimeEmitterLogModule, mozilla::LogLevel::Info, ("%s", PromiseFlatCString(buf).get()));
+ //
+ // Make sure that the buffer we are "pushing" into has enough room
+ // for the write operation. If not, we have to buffer, return, and get
+ // it on the next time through
+ //
+ *amountWritten = 0;
+
+ needToWrite = mBufferMgr->GetSize();
+ // First, handle any old buffer data...
+ if (needToWrite > 0)
+ {
+ rv = WriteHelper(mBufferMgr->GetBuffer(), &written);
+
+ mTotalWritten += written;
+ mBufferMgr->ReduceBuffer(written);
+ *amountWritten = written;
+
+ // if we couldn't write all the old data, buffer the new data
+ // and return
+ if (mBufferMgr->GetSize() > 0)
+ {
+ mBufferMgr->IncreaseBuffer(buf);
+ return rv;
+ }
+ }
+
+
+ // if we get here, we are dealing with new data...try to write
+ // and then do the right thing...
+ rv = WriteHelper(buf, &written);
+ *amountWritten = written;
+ mTotalWritten += written;
+
+ if (written < buf.Length()) {
+ const nsACString &remainder = Substring(buf, written);
+ mBufferMgr->IncreaseBuffer(remainder);
+ }
+
+ return rv;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHelper(const nsACString &buf, uint32_t *countWritten)
+{
+ NS_ENSURE_TRUE(mOutStream, NS_ERROR_NOT_INITIALIZED);
+
+ nsresult rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // pipe is full, push contents of pipe to listener...
+ uint64_t avail;
+ rv = mInputStream->Available(&avail);
+ if (NS_SUCCEEDED(rv) && avail) {
+ mOutListener->OnDataAvailable(mChannel, mURL, mInputStream, 0,
+ std::min(avail, uint64_t(PR_UINT32_MAX)));
+
+ // try writing again...
+ rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten);
+ }
+ }
+ return rv;
+}
+
+//
+// Find a cached header! Note: Do NOT free this value!
+//
+const char *
+nsMimeBaseEmitter::GetHeaderValue(const char *aHeaderName)
+{
+ char *retVal = nullptr;
+ nsTArray<headerInfoType*> *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray;
+
+ if (!array)
+ return nullptr;
+
+ for (size_t i = 0; i < array->Length(); i++)
+ {
+ headerInfoType *headerInfo = array->ElementAt(i);
+ if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) )
+ continue;
+
+ if (!PL_strcasecmp(aHeaderName, headerInfo->name))
+ {
+ retVal = headerInfo->value;
+ break;
+ }
+ }
+
+ return retVal;
+}
+
+//
+// This is called at the start of the header block for all header information in ANY
+// AND ALL MESSAGES (yes, quoted, attached, etc...)
+//
+// NOTE: This will be called even when headers are will not follow. This is
+// to allow us to be notified of the charset of the original message. This is
+// important for forward and reply operations
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID,
+ const char *outCharset)
+{
+ NS_ENSURE_ARG_POINTER(outCharset);
+
+ mDocHeader = rootMailHeader;
+
+ // If this is not the mail messages header, then we need to create
+ // the mEmbeddedHeaderArray structure for use with this internal header
+ // structure.
+ if (!mDocHeader)
+ {
+ if (mEmbeddedHeaderArray)
+ CleanupHeaderArray(mEmbeddedHeaderArray);
+
+ mEmbeddedHeaderArray = new nsTArray<headerInfoType*>();
+ NS_ENSURE_TRUE(mEmbeddedHeaderArray, NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ // If the main doc, check on updated character set
+ if (mDocHeader)
+ UpdateCharacterSet(outCharset);
+ CopyASCIItoUTF16(nsDependentCString(outCharset), mCharset);
+ return NS_OK;
+}
+
+// Ok, if we are here, and we have a aCharset passed in that is not
+// UTF-8 or US-ASCII, then we should tag the mChannel member with this
+// charset. This is because replying to messages with specified charset's
+// need to be tagged as that charset by default.
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::UpdateCharacterSet(const char *aCharset)
+{
+ if (aCharset)
+ {
+ nsAutoCString contentType;
+
+ if (NS_SUCCEEDED(mChannel->GetContentType(contentType)) && !contentType.IsEmpty())
+ {
+ char *cBegin = contentType.BeginWriting();
+
+ const char *cPtr = PL_strcasestr(cBegin, "charset=");
+
+ if (cPtr)
+ {
+ char *ptr = cBegin;
+ while (*ptr)
+ {
+ if ( (*ptr == ' ') || (*ptr == ';') )
+ {
+ if ((ptr + 1) >= cPtr)
+ {
+ *ptr = '\0';
+ break;
+ }
+ }
+
+ ++ptr;
+ }
+ }
+
+ // have to set content-type since it could have an embedded null byte
+ mChannel->SetContentType(nsDependentCString(cBegin));
+ if (PL_strcasecmp(aCharset, "US-ASCII") == 0) {
+ mChannel->SetContentCharset(NS_LITERAL_CSTRING("ISO-8859-1"));
+ } else {
+ mChannel->SetContentCharset(nsDependentCString(aCharset));
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// This will be called for every header field regardless if it is in an
+// internal body or the outer message.
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::AddHeaderField(const char *field, const char *value)
+{
+ if ( (!field) || (!value) )
+ return NS_OK;
+
+ nsTArray<headerInfoType*> *tPtr;
+ if (mDocHeader)
+ tPtr = mHeaderArray;
+ else
+ tPtr = mEmbeddedHeaderArray;
+
+ // This is a header so we need to cache and output later.
+ // Ok, now we will setup the header info for the header array!
+ headerInfoType *ptr = (headerInfoType *) PR_NEWZAP(headerInfoType);
+ if ( (ptr) && tPtr)
+ {
+ ptr->name = strdup(field);
+ ptr->value = strdup(value);
+ tPtr->AppendElement(ptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::AddAllHeaders(const nsACString &allheaders)
+{
+ if (mDocHeader) //We want to set only the main headers of a message, not the potentially embedded one
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(mURL));
+ if (msgurl)
+ {
+ nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mimeHeaders->Initialize(allheaders);
+ msgurl->SetMimeHeaders(mimeHeaders);
+ }
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The following code is responsible for formatting headers in a manner that is
+// identical to the normal XUL output.
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+nsMimeBaseEmitter::GenerateDateString(const char * dateString,
+ nsACString &formattedDate,
+ bool showDateForToday)
+{
+ nsresult rv = NS_OK;
+
+ if (!mDateFormatter) {
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ /**
+ * See if the user wants to have the date displayed in the senders
+ * timezone (including the timezone offset).
+ * We also evaluate the pref original_date which was introduced
+ * as makeshift in bug 118899.
+ */
+ bool displaySenderTimezone = false;
+ bool displayOriginalDate = false;
+
+ nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> dateFormatPrefs;
+ rv = prefs->GetBranch("mailnews.display.", getter_AddRefs(dateFormatPrefs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dateFormatPrefs->GetBoolPref("date_senders_timezone", &displaySenderTimezone);
+ dateFormatPrefs->GetBoolPref("original_date", &displayOriginalDate);
+ // migrate old pref to date_senders_timezone
+ if (displayOriginalDate && !displaySenderTimezone)
+ dateFormatPrefs->SetBoolPref("date_senders_timezone", true);
+
+ PRExplodedTime explodedMsgTime;
+
+ // Bogus date string may leave some fields uninitialized, so take precaution.
+ memset(&explodedMsgTime, 0, sizeof (PRExplodedTime));
+
+ if (PR_ParseTimeStringToExplodedTime(dateString, false, &explodedMsgTime) != PR_SUCCESS)
+ return NS_ERROR_FAILURE;
+
+ /**
+ * To determine the date format to use, comparison of current and message
+ * time has to be made. If displaying in local time, both timestamps have
+ * to be in local time. If displaying in senders time zone, leave the compare
+ * time in that time zone.
+ * Otherwise in TZ+0100 on 2009-03-12 a message from 2009-03-11T20:49-0700
+ * would be displayed as "20:49 -0700" though it in fact is not from the
+ * same day.
+ */
+ PRExplodedTime explodedCompTime;
+ if (displaySenderTimezone)
+ explodedCompTime = explodedMsgTime;
+ else
+ PR_ExplodeTime(PR_ImplodeTime(&explodedMsgTime), PR_LocalTimeParameters, &explodedCompTime);
+
+ PRExplodedTime explodedCurrentTime;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &explodedCurrentTime);
+
+ // If we want short dates, check if the message is from today, and if so
+ // only show the time (e.g. 3:15 pm).
+ nsDateFormatSelector dateFormat = kDateFormatShort;
+ if (!showDateForToday &&
+ explodedCurrentTime.tm_year == explodedCompTime.tm_year &&
+ explodedCurrentTime.tm_month == explodedCompTime.tm_month &&
+ explodedCurrentTime.tm_mday == explodedCompTime.tm_mday)
+ {
+ // same day...
+ dateFormat = kDateFormatNone;
+ }
+
+ nsAutoString formattedDateString;
+
+ rv = mDateFormatter->FormatPRExplodedTime(nullptr /* nsILocale* locale */,
+ dateFormat,
+ kTimeFormatNoSeconds,
+ &explodedCompTime,
+ formattedDateString);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (displaySenderTimezone)
+ {
+ // offset of local time from UTC in minutes
+ int32_t senderoffset = (explodedMsgTime.tm_params.tp_gmt_offset +
+ explodedMsgTime.tm_params.tp_dst_offset) / 60;
+ // append offset to date string
+ char16_t *tzstring =
+ nsTextFormatter::smprintf(u" %+05d",
+ (senderoffset / 60 * 100) +
+ (senderoffset % 60));
+ formattedDateString.Append(tzstring);
+ nsTextFormatter::smprintf_free(tzstring);
+ }
+
+ CopyUTF16toUTF8(formattedDateString, formattedDate);
+ }
+
+ return rv;
+}
+
+char*
+nsMimeBaseEmitter::GetLocalizedDateString(const char * dateString)
+{
+ char *i18nValue = nullptr;
+
+ bool displayOriginalDate = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ if (prefBranch)
+ prefBranch->GetBoolPref("mailnews.display.original_date",
+ &displayOriginalDate);
+
+ if (!displayOriginalDate)
+ {
+ nsAutoCString convertedDateString;
+ nsresult rv = GenerateDateString(dateString, convertedDateString, true);
+ if (NS_SUCCEEDED(rv))
+ i18nValue = strdup(convertedDateString.get());
+ else
+ i18nValue = strdup(dateString);
+ }
+ else
+ i18nValue = strdup(dateString);
+
+ return i18nValue;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHeaderFieldHTML(const char *field, const char *value)
+{
+ char *newValue = nullptr;
+ char *i18nValue = nullptr;
+
+ if ( (!field) || (!value) )
+ return NS_OK;
+
+ //
+ // This is a check to see what the pref is for header display. If
+ // We should only output stuff that corresponds with that setting.
+ //
+ if (!EmitThisHeaderForPrefSetting(mHeaderDisplayType, field))
+ return NS_OK;
+
+ //
+ // If we encounter the 'Date' header we try to convert it's value
+ // into localized format.
+ //
+ if ( strcmp(field, "Date") == 0 )
+ i18nValue = GetLocalizedDateString(value);
+ else
+ i18nValue = strdup(value);
+
+ if ( (mUnicodeConverter) && (mFormat != nsMimeOutput::nsMimeMessageSaveAs) )
+ {
+ nsCString tValue;
+
+ // we're going to need a converter to convert
+ nsresult rv = mUnicodeConverter->DecodeMimeHeaderToUTF8(
+ nsDependentCString(i18nValue), nullptr, false, true, tValue);
+ if (NS_SUCCEEDED(rv) && !tValue.IsEmpty())
+ newValue = MsgEscapeHTML(tValue.get());
+ else
+ newValue = MsgEscapeHTML(i18nValue);
+ }
+ else
+ {
+ newValue = MsgEscapeHTML(i18nValue);
+ }
+
+ free(i18nValue);
+
+ if (!newValue)
+ return NS_OK;
+
+ mHTMLHeaders.Append("<tr>");
+ mHTMLHeaders.Append("<td>");
+
+ if (mFormat == nsMimeOutput::nsMimeMessageSaveAs)
+ mHTMLHeaders.Append("<b>");
+ else
+ mHTMLHeaders.Append("<div class=\"headerdisplayname\" style=\"display:inline;\">");
+
+ // Here is where we are going to try to L10N the tagName so we will always
+ // get a field name next to an emitted header value. Note: Default will always
+ // be the name of the header itself.
+ //
+ nsCString newTagName(field);
+ newTagName.StripWhitespace();
+ ToUpperCase(newTagName);
+
+ char *l10nTagName = LocalizeHeaderName(newTagName.get(), field);
+ if ( (!l10nTagName) || (!*l10nTagName) )
+ mHTMLHeaders.Append(field);
+ else
+ {
+ mHTMLHeaders.Append(l10nTagName);
+ PR_FREEIF(l10nTagName);
+ }
+
+ mHTMLHeaders.Append(": ");
+ if (mFormat == nsMimeOutput::nsMimeMessageSaveAs)
+ mHTMLHeaders.Append("</b>");
+ else
+ mHTMLHeaders.Append("</div>");
+
+ // Now write out the actual value itself and move on!
+ //
+ mHTMLHeaders.Append(newValue);
+ mHTMLHeaders.Append("</td>");
+
+ mHTMLHeaders.Append("</tr>");
+
+ PR_FREEIF(newValue);
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name)
+{
+ if (
+ ( (mFormat == nsMimeOutput::nsMimeMessageSaveAs) && (mFirstHeaders) ) ||
+ ( (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) && (mFirstHeaders) )
+ )
+ /* DO NOTHING */ ; // rhp: Do nothing...leaving the conditional like this so its
+ // easier to see the logic of what is going on.
+ else {
+ mHTMLHeaders.Append("<br><fieldset class=\"mimeAttachmentHeader\">");
+ if (!name.IsEmpty()) {
+ mHTMLHeaders.Append("<legend class=\"mimeAttachmentHeaderName\">");
+ nsCString escapedName;
+ escapedName.Adopt(MsgEscapeHTML(nsCString(name).get()));
+ mHTMLHeaders.Append(escapedName);
+ mHTMLHeaders.Append("</legend>");
+ }
+ mHTMLHeaders.Append("</fieldset>");
+ }
+
+ mFirstHeaders = false;
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix()
+{
+ mHTMLHeaders.Append("<br>");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::WriteHTMLHeaders(const nsACString &name)
+{
+ WriteHeaderFieldHTMLPrefix(name);
+
+ // Start with the subject, from date info!
+ DumpSubjectFromDate();
+
+ // Continue with the to and cc headers
+ DumpToCC();
+
+ // Do the rest of the headers, but these will only be written if
+ // the user has the "show all headers" pref set
+ if (mHeaderDisplayType == nsMimeHeaderDisplayTypes::AllHeaders)
+ DumpRestOfHeaders();
+
+ WriteHeaderFieldHTMLPostfix();
+
+ // Now, we need to either append the headers we built up to the
+ // overall body or output to the stream.
+ UtilityWriteCRLF(mHTMLHeaders.get());
+
+ mHTMLHeaders = "";
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::DumpSubjectFromDate()
+{
+ mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part1\">");
+
+ // This is the envelope information
+ OutputGenericHeader(HEADER_SUBJECT);
+ OutputGenericHeader(HEADER_FROM);
+ OutputGenericHeader(HEADER_DATE);
+
+ // If we are Quoting a message, then we should dump the To: also
+ if ( ( mFormat == nsMimeOutput::nsMimeMessageQuoting ) ||
+ ( mFormat == nsMimeOutput::nsMimeMessageBodyQuoting ) )
+ OutputGenericHeader(HEADER_TO);
+
+ mHTMLHeaders.Append("</table>");
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::DumpToCC()
+{
+ const char * toField = GetHeaderValue(HEADER_TO);
+ const char * ccField = GetHeaderValue(HEADER_CC);
+ const char * bccField = GetHeaderValue(HEADER_BCC);
+ const char * newsgroupField = GetHeaderValue(HEADER_NEWSGROUPS);
+
+ // only dump these fields if we have at least one of them! When displaying news
+ // messages that didn't have a To or Cc field, we'd always get an empty box
+ // which looked weird.
+ if (toField || ccField || bccField || newsgroupField)
+ {
+ mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part2\">");
+
+ if (toField)
+ WriteHeaderFieldHTML(HEADER_TO, toField);
+ if (ccField)
+ WriteHeaderFieldHTML(HEADER_CC, ccField);
+ if (bccField)
+ WriteHeaderFieldHTML(HEADER_BCC, bccField);
+ if (newsgroupField)
+ WriteHeaderFieldHTML(HEADER_NEWSGROUPS, newsgroupField);
+
+ mHTMLHeaders.Append("</table>");
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::DumpRestOfHeaders()
+{
+ nsTArray<headerInfoType*> *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray;
+
+ mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part3\">");
+
+ for (size_t i = 0; i < array->Length(); i++)
+ {
+ headerInfoType *headerInfo = array->ElementAt(i);
+ if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) ||
+ (!headerInfo->value) || (!(*headerInfo->value)))
+ continue;
+
+ if ( (!PL_strcasecmp(HEADER_SUBJECT, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_DATE, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_FROM, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_TO, headerInfo->name)) ||
+ (!PL_strcasecmp(HEADER_CC, headerInfo->name)) )
+ continue;
+
+ WriteHeaderFieldHTML(headerInfo->name, headerInfo->value);
+ }
+
+ mHTMLHeaders.Append("</table>");
+ return NS_OK;
+}
+
+nsresult
+nsMimeBaseEmitter::OutputGenericHeader(const char *aHeaderVal)
+{
+ const char *val = GetHeaderValue(aHeaderVal);
+
+ if (val)
+ return WriteHeaderFieldHTML(aHeaderVal, val);
+
+ return NS_ERROR_FAILURE;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+// These are the methods that should be implemented by the child class!
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+//
+// This should be implemented by the child class if special processing
+// needs to be done when the entire message is read.
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::Complete()
+{
+ // If we are here and still have data to write, we should try
+ // to flush it...if we try and fail, we should probably return
+ // an error!
+ uint32_t written;
+
+ nsresult rv = NS_OK;
+ while ( NS_SUCCEEDED(rv) && (mBufferMgr) && (mBufferMgr->GetSize() > 0))
+ rv = Write(EmptyCString(), &written);
+
+ if (mOutListener)
+ {
+ uint64_t bytesInStream = 0;
+ mozilla::DebugOnly<nsresult> rv2 = mInputStream->Available(&bytesInStream);
+ NS_ASSERTION(NS_SUCCEEDED(rv2), "Available failed");
+ if (bytesInStream)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mChannel);
+ mOutListener->OnDataAvailable(request, mURL, mInputStream, 0, std::min(bytesInStream, uint64_t(PR_UINT32_MAX)));
+ }
+ }
+
+ return NS_OK;
+}
+
+//
+// This needs to do the right thing with the stored information. It only
+// has to do the output functions, this base class will take care of the
+// memory cleanup
+//
+NS_IMETHODIMP
+nsMimeBaseEmitter::EndHeader(const nsACString &name)
+{
+ return NS_OK;
+}
+
+// body handling routines
+NS_IMETHODIMP
+nsMimeBaseEmitter::StartBody(bool bodyOnly, const char *msgID, const char *outCharset)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMimeBaseEmitter::EndBody()
+{
+ return NS_OK;
+}