/* -*- 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;
}
