/* -*- 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/. */

#ifndef INITGUID
#  define INITGUID
#endif

#ifndef USES_IID_IMessage
#  define USES_IID_IMessage
#endif

#include "nscore.h"
#include <time.h>
#include "nsString.h"
#include "nsDirectoryServiceDefs.h"
#include "nsMsgUtils.h"
#include "nsMimeTypes.h"
#include "nsNativeCharsetUtils.h"
#include "nsIOutputStream.h"

#include "MapiDbgLog.h"
#include "MapiApi.h"

#include "MapiMimeTypes.h"

#include "nsMsgI18N.h"
#include "nsCRT.h"
#include "nsNetUtil.h"
#include "MapiMessage.h"

#include "nsOutlookMail.h"

#include "mozilla/Encoding.h"

#include <stdlib.h>
#include <tuple>

// needed for the call the OpenStreamOnFile
extern LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer;
extern LPMAPIFREEBUFFER gpMapiFreeBuffer;

// Sample From line: From - 1 Jan 1965 00:00:00

typedef const char* PC_S8;

static const char* kWhitespace = "\b\t\r\n ";
static const char* sFromLine = "From - ";
static const char* sFromDate = "Mon Jan 1 00:00:00 1965";
static const char* sDaysOfWeek[7] = {"Sun", "Mon", "Tue", "Wed",
                                     "Thu", "Fri", "Sat"};

static const char* sMonths[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

CMapiMessage::CMapiMessage(LPMESSAGE lpMsg)
    : m_lpMsg(lpMsg), m_dldStateHeadersOnly(false), m_msgFlags(0) {
  nsresult rv;
  m_pIOService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return;

  FetchHeaders();
  if (ValidState()) {
    BuildFromLine();
    FetchFlags();
    GetDownloadState();
    if (FullMessageDownloaded()) {
      FetchBody();
      ProcessAttachments();
    }
  }
}

CMapiMessage::~CMapiMessage() {
  ClearAttachments();
  if (m_lpMsg) m_lpMsg->Release();
}

void CMapiMessage::FormatDateTime(SYSTEMTIME& tm, nsCString& s,
                                  bool includeTZ) {
  long offset = _timezone;
  s += sDaysOfWeek[tm.wDayOfWeek];
  s += ", ";
  s.AppendInt((int32_t)tm.wDay);
  s += " ";
  s += sMonths[tm.wMonth - 1];
  s += " ";
  s.AppendInt((int32_t)tm.wYear);
  s += " ";
  int val = tm.wHour;
  if (val < 10) s += "0";
  s.AppendInt((int32_t)val);
  s += ":";
  val = tm.wMinute;
  if (val < 10) s += "0";
  s.AppendInt((int32_t)val);
  s += ":";
  val = tm.wSecond;
  if (val < 10) s += "0";
  s.AppendInt((int32_t)val);
  if (includeTZ) {
    s += " ";
    if (offset < 0) {
      offset *= -1;
      s += "+";
    } else
      s += "-";
    offset /= 60;
    val = (int)(offset / 60);
    if (val < 10) s += "0";
    s.AppendInt((int32_t)val);
    val = (int)(offset % 60);
    if (val < 10) s += "0";
    s.AppendInt((int32_t)val);
  }
}

bool CMapiMessage::EnsureHeader(CMapiMessageHeaders::SpecialHeader special,
                                ULONG mapiTag) {
  if (m_headers.Value(special)) return true;

  LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, mapiTag);
  bool success = false;
  if (pVal) {
    if (PROP_TYPE(pVal->ulPropTag) == PT_STRING8) {
      if (pVal->Value.lpszA && strlen(pVal->Value.lpszA)) {
        m_headers.SetValue(special, pVal->Value.lpszA);
        success = true;
      }
    } else if (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) {
      if (pVal->Value.lpszW && wcslen(pVal->Value.lpszW)) {
        m_headers.SetValue(special,
                           NS_ConvertUTF16toUTF8(pVal->Value.lpszW).get());
        success = true;
      }
    }
    CMapiApi::MAPIFreeBuffer(pVal);
  }

  return success;
}

bool CMapiMessage::EnsureDate() {
  if (m_headers.Value(CMapiMessageHeaders::hdrDate)) return true;

  LPSPropValue pVal =
      CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_DELIVERY_TIME);
  if (!pVal) pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME);
  if (pVal) {
    SYSTEMTIME st;
    // the following call returns UTC
    ::FileTimeToSystemTime(&(pVal->Value.ft), &st);
    CMapiApi::MAPIFreeBuffer(pVal);
    // FormatDateTime would append the local time zone, so don't use it.
    // Instead, we just append +0000 for GMT/UTC here.
    nsCString str;
    FormatDateTime(st, str, false);
    str += " +0000";
    m_headers.SetValue(CMapiMessageHeaders::hdrDate, str.get());
    return true;
  }

  return false;
}

void CMapiMessage::BuildFromLine(void) {
  m_fromLine = sFromLine;
  LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME);
  if (pVal) {
    SYSTEMTIME st;
    ::FileTimeToSystemTime(&(pVal->Value.ft), &st);
    CMapiApi::MAPIFreeBuffer(pVal);
    FormatDateTime(st, m_fromLine, FALSE);
  } else
    m_fromLine += sFromDate;

  m_fromLine += "\x0D\x0A";
}

#ifndef dispidHeaderItem
#  define dispidHeaderItem 0x8578
#endif
DEFINE_OLEGUID(PSETID_Common, MAKELONG(0x2000 + (8), 0x0006), 0, 0);

void CMapiMessage::GetDownloadState() {
  // See http://support.microsoft.com/kb/912239
  ULONG ulVal = 0;
  LPSPropValue lpPropVal = NULL;
  LPSPropTagArray lpNamedPropTag = NULL;
  MAPINAMEID NamedID = {0};
  LPMAPINAMEID lpNamedID = NULL;

  NamedID.lpguid = (LPGUID)&PSETID_Common;
  NamedID.ulKind = MNID_ID;
  NamedID.Kind.lID = dispidHeaderItem;
  lpNamedID = &NamedID;

  m_lpMsg->GetIDsFromNames(1, &lpNamedID, NULL, &lpNamedPropTag);

  if (lpNamedPropTag && 1 == lpNamedPropTag->cValues) {
    lpNamedPropTag->aulPropTag[0] =
        CHANGE_PROP_TYPE(lpNamedPropTag->aulPropTag[0], PT_LONG);

    // Get the value of the property.
    m_lpMsg->GetProps(lpNamedPropTag, 0, &ulVal, &lpPropVal);
    if (lpPropVal && 1 == ulVal && PT_LONG == PROP_TYPE(lpPropVal->ulPropTag) &&
        lpPropVal->Value.ul)
      m_dldStateHeadersOnly = true;
  }

  CMapiApi::MAPIFreeBuffer(lpPropVal);
  CMapiApi::MAPIFreeBuffer(lpNamedPropTag);
}

// Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS
// or if they do not exist will build a header from
//  PR_DISPLAY_TO, _CC, _BCC
//  PR_SUBJECT
//  PR_MESSAGE_RECIPIENTS
// and PR_CREATION_TIME if needed?
bool CMapiMessage::FetchHeaders(void) {
  ULONG tag = PR_TRANSPORT_MESSAGE_HEADERS_A;
  LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag);
  if (!pVal)
    pVal = CMapiApi::GetMapiProperty(m_lpMsg,
                                     tag = PR_TRANSPORT_MESSAGE_HEADERS_W);
  if (pVal) {
    if (CMapiApi::IsLargeProperty(pVal)) {
      nsCString headers;
      CMapiApi::GetLargeStringProperty(m_lpMsg, tag, headers);
      m_headers.Assign(headers.get());
    } else if ((PROP_TYPE(pVal->ulPropTag) == PT_STRING8) &&
               (pVal->Value.lpszA) && (*(pVal->Value.lpszA)))
      m_headers.Assign(pVal->Value.lpszA);
    else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
             (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) {
      nsCString headers;
      LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), headers);
      m_headers.Assign(headers.get());
    }

    CMapiApi::MAPIFreeBuffer(pVal);
  }

  EnsureDate();
  if (!EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_NAME_W))
    EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_EMAIL_ADDRESS_W);
  EnsureHeader(CMapiMessageHeaders::hdrSubject, PR_SUBJECT_W);
  EnsureHeader(CMapiMessageHeaders::hdrTo, PR_DISPLAY_TO_W);
  EnsureHeader(CMapiMessageHeaders::hdrCc, PR_DISPLAY_CC_W);
  EnsureHeader(CMapiMessageHeaders::hdrBcc, PR_DISPLAY_BCC_W);

  ProcessContentType();

  return !m_headers.IsEmpty();
}

// Mime-Version: 1.0
// Content-Type: text/plain; charset="US-ASCII"
// Content-Type: multipart/mixed; boundary="=====================_874475278==_"

void CMapiMessage::ProcessContentType() {
  m_mimeContentType.Truncate();
  m_mimeBoundary.Truncate();
  m_mimeCharset.Truncate();

  const char* contentType =
      m_headers.Value(CMapiMessageHeaders::hdrContentType);
  if (!contentType) return;

  const char *begin = contentType, *end;
  nsCString tStr;

  // Note: this isn't a complete parser, the content type
  // we extract could have rfc822 comments in it
  while (*begin && IsSpace(*begin)) begin++;
  if (!(*begin)) return;
  end = begin;
  while (*end && (*end != ';')) end++;
  m_mimeContentType.Assign(begin, end - begin);
  if (!(*end)) return;
  // look for "boundary="
  begin = end + 1;
  bool haveB;
  bool haveC;
  while (*begin) {
    haveB = false;
    haveC = false;
    while (*begin && IsSpace(*begin)) begin++;
    if (!(*begin)) return;
    end = begin;
    while (*end && (*end != '=')) end++;
    if (end - begin) {
      tStr.Assign(begin, end - begin);
      if (tStr.LowerCaseEqualsLiteral("boundary"))
        haveB = true;
      else if (tStr.LowerCaseEqualsLiteral("charset"))
        haveC = true;
    }
    if (!(*end)) return;
    begin = end + 1;
    while (*begin && IsSpace(*begin)) begin++;
    if (*begin == '"') {
      begin++;
      bool slash = false;
      tStr.Truncate();
      while (*begin) {
        if (slash) {
          slash = false;
          tStr.Append(*begin);
        } else if (*begin == '"')
          break;
        else if (*begin != '\\')
          tStr.Append(*begin);
        else
          slash = true;
        begin++;
      }
      if (haveB) {
        m_mimeBoundary = tStr;
        haveB = false;
      }
      if (haveC) {
        m_mimeCharset = tStr;
        haveC = false;
      }
      if (!(*begin)) return;
      begin++;
    }
    tStr.Truncate();
    while (*begin && (*begin != ';')) {
      tStr.Append(*(begin++));
    }
    if (haveB) {
      tStr.Trim(kWhitespace);
      m_mimeBoundary = tStr;
    }
    if (haveC) {
      tStr.Trim(kWhitespace);
      m_mimeCharset = tStr;
    }
    if (*begin) begin++;
  }
}

const char* CpToCharset(unsigned int cp) {
  struct CODEPAGE_TO_CHARSET {
    unsigned long cp;
    const char* charset;
  };

  // This table is based on
  // http://msdn.microsoft.com/en-us/library/dd317756(v=VS.85).aspx#1; Please
  // extend as appropriate. The codepage values are sorted ascending.
  static const CODEPAGE_TO_CHARSET cptocharset[] = {
      {37, "IBM037"},     // IBM EBCDIC US-Canada
      {437, "IBM437"},    // OEM United States
      {500, "IBM500"},    // IBM EBCDIC International
      {708, "ASMO-708"},  // Arabic (ASMO 708)
      // 709  Arabic (ASMO-449+, BCON V4)
      // 710  Arabic - Transparent Arabic
      {720, "DOS-720"},   // Arabic (Transparent ASMO); Arabic (DOS)
      {737, "ibm737"},    // OEM Greek (formerly 437G); Greek (DOS)
      {775, "ibm775"},    // OEM Baltic; Baltic (DOS)
      {850, "ibm850"},    // OEM Multilingual Latin 1; Western European (DOS)
      {852, "ibm852"},    // OEM Latin 2; Central European (DOS)
      {855, "IBM855"},    // OEM Cyrillic (primarily Russian)
      {857, "ibm857"},    // OEM Turkish; Turkish (DOS)
      {858, "IBM00858"},  // OEM Multilingual Latin 1 + Euro symbol
      {860, "IBM860"},    // OEM Portuguese; Portuguese (DOS)
      {861, "ibm861"},    // OEM Icelandic; Icelandic (DOS)
      {862, "DOS-862"},   // OEM Hebrew; Hebrew (DOS)
      {863, "IBM863"},    // OEM French Canadian; French Canadian (DOS)
      {864, "IBM864"},    // OEM Arabic; Arabic (864)
      {865, "IBM865"},    // OEM Nordic; Nordic (DOS)
      {866, "cp866"},     // OEM Russian; Cyrillic (DOS)
      {869, "ibm869"},    // OEM Modern Greek; Greek, Modern (DOS)
      {870, "IBM870"},    // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC
                          // Multilingual Latin 2
      {874, "windows-874"},  // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai
                             // (Windows)
      {875, "cp875"},        // IBM EBCDIC Greek Modern
      {932, "shift_jis"},    // ANSI/OEM Japanese; Japanese (Shift-JIS)
      {936, "gb2312"},  // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese
                        // Simplified (GB2312)
      {949, "ks_c_5601-1987"},  // ANSI/OEM Korean (Unified Hangul Code)
      {950, "big5"},  // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR,
                      // PRC); Chinese Traditional (Big5)
      {1026, "IBM1026"},   // IBM EBCDIC Turkish (Latin 5)
      {1047, "IBM01047"},  // IBM EBCDIC Latin 1/Open System
      {1140, "IBM01140"},  // IBM EBCDIC US-Canada (037 + Euro symbol); IBM
                           // EBCDIC (US-Canada-Euro)
      {1141, "IBM01141"},  // IBM EBCDIC Germany (20273 + Euro symbol); IBM
                           // EBCDIC (Germany-Euro)
      {1142, "IBM01142"},  // IBM EBCDIC Denmark-Norway (20277 + Euro symbol);
                           // IBM EBCDIC (Denmark-Norway-Euro)
      {1143, "IBM01143"},  // IBM EBCDIC Finland-Sweden (20278 + Euro symbol);
                           // IBM EBCDIC (Finland-Sweden-Euro)
      {1144, "IBM01144"},  // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC
                           // (Italy-Euro)
      {1145, "IBM01145"},  // IBM EBCDIC Latin America-Spain (20284 + Euro
                           // symbol); IBM EBCDIC (Spain-Euro)
      {1146, "IBM01146"},  // IBM EBCDIC United Kingdom (20285 + Euro symbol);
                           // IBM EBCDIC (UK-Euro)
      {1147, "IBM01147"},  // IBM EBCDIC France (20297 + Euro symbol); IBM
                           // EBCDIC (France-Euro)
      {1148, "IBM01148"},  // IBM EBCDIC International (500 + Euro symbol); IBM
                           // EBCDIC (International-Euro)
      {1149, "IBM01149"},  // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM
                           // EBCDIC (Icelandic-Euro)
      {1200, "utf-16"},  // Unicode UTF-16, little endian byte order (BMP of ISO
                         // 10646); available only to managed applications
      {1201, "unicodeFFFE"},  // Unicode UTF-16, big endian byte order;
                              // available only to managed applications
      {1250,
       "windows-1250"},  // ANSI Central European; Central European (Windows)
      {1251, "windows-1251"},     // ANSI Cyrillic; Cyrillic (Windows)
      {1252, "windows-1252"},     // ANSI Latin 1; Western European (Windows)
      {1253, "windows-1253"},     // ANSI Greek; Greek (Windows)
      {1254, "windows-1254"},     // ANSI Turkish; Turkish (Windows)
      {1255, "windows-1255"},     // ANSI Hebrew; Hebrew (Windows)
      {1256, "windows-1256"},     // ANSI Arabic; Arabic (Windows)
      {1257, "windows-1257"},     // ANSI Baltic; Baltic (Windows)
      {1258, "windows-1258"},     // ANSI/OEM Vietnamese; Vietnamese (Windows)
      {1361, "Johab"},            // Korean (Johab)
      {10000, "macintosh"},       // MAC Roman; Western European (Mac)
      {10001, "x-mac-japanese"},  // Japanese (Mac)
      {10002, "x-mac-chinesetrad"},  // MAC Traditional Chinese (Big5); Chinese
                                     // Traditional (Mac)
      {10003, "x-mac-korean"},       // Korean (Mac)
      {10004, "x-mac-arabic"},       // Arabic (Mac)
      {10005, "x-mac-hebrew"},       // Hebrew (Mac)
      {10006, "x-mac-greek"},        // Greek (Mac)
      {10007, "x-mac-cyrillic"},     // Cyrillic (Mac)
      {10008, "x-mac-chinesesimp"},  // MAC Simplified Chinese (GB 2312);
                                     // Chinese Simplified (Mac)
      {10010, "x-mac-romanian"},     // Romanian (Mac)
      {10017, "x-mac-ukrainian"},    // Ukrainian (Mac)
      {10021, "x-mac-thai"},         // Thai (Mac)
      {10029, "x-mac-ce"},           // MAC Latin 2; Central European (Mac)
      {10079, "x-mac-icelandic"},    // Icelandic (Mac)
      {10081, "x-mac-turkish"},      // Turkish (Mac)
      {10082, "x-mac-croatian"},     // Croatian (Mac)
      // Unicode UTF-32, little endian byte order; available only to managed
      // applications impossible in 8-bit mail
      {12000, "utf-32"},
      // Unicode UTF-32, big endian byte order; available only to managed
      // applications impossible in 8-bit mail
      {12001, "utf-32BE"},
      {20000, "x-Chinese_CNS"},   // CNS Taiwan; Chinese Traditional (CNS)
      {20001, "x-cp20001"},       // TCA Taiwan
      {20002, "x_Chinese-Eten"},  // Eten Taiwan; Chinese Traditional (Eten)
      {20003, "x-cp20003"},       // IBM5550 Taiwan
      {20004, "x-cp20004"},       // TeleText Taiwan
      {20005, "x-cp20005"},       // Wang Taiwan
      {20105, "x-IA5"},  // IA5 (IRV International Alphabet No. 5, 7-bit);
                         // Western European (IA5)
      {20106, "x-IA5-German"},     // IA5 German (7-bit)
      {20107, "x-IA5-Swedish"},    // IA5 Swedish (7-bit)
      {20108, "x-IA5-Norwegian"},  // IA5 Norwegian (7-bit)
      {20127, "us-ascii"},         // US-ASCII (7-bit)
      {20261, "x-cp20261"},        // T.61
      {20269, "x-cp20269"},        // ISO 6937 Non-Spacing Accent
      {20273, "IBM273"},           // IBM EBCDIC Germany
      {20277, "IBM277"},           // IBM EBCDIC Denmark-Norway
      {20278, "IBM278"},           // IBM EBCDIC Finland-Sweden
      {20280, "IBM280"},           // IBM EBCDIC Italy
      {20284, "IBM284"},           // IBM EBCDIC Latin America-Spain
      {20285, "IBM285"},           // IBM EBCDIC United Kingdom
      {20290, "IBM290"},           // IBM EBCDIC Japanese Katakana Extended
      {20297, "IBM297"},           // IBM EBCDIC France
      {20420, "IBM420"},           // IBM EBCDIC Arabic
      {20423, "IBM423"},           // IBM EBCDIC Greek
      {20424, "IBM424"},           // IBM EBCDIC Hebrew
      {20833, "x-EBCDIC-KoreanExtended"},  // IBM EBCDIC Korean Extended
      {20838, "IBM-Thai"},                 // IBM EBCDIC Thai
      {20866, "koi8-r"},  // Russian (KOI8-R); Cyrillic (KOI8-R)
      {20871, "IBM871"},  // IBM EBCDIC Icelandic
      {20880, "IBM880"},  // IBM EBCDIC Cyrillic Russian
      {20905, "IBM905"},  // IBM EBCDIC Turkish
      {20924,
       "IBM00924"},       // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
      {20932, "EUC-JP"},  // Japanese (JIS 0208-1990 and 0121-1990)
      {20936, "x-cp20936"},  // Simplified Chinese (GB2312); Chinese Simplified
                             // (GB2312-80)
      {20949, "x-cp20949"},  // Korean Wansung
      {21025, "cp1025"},     // IBM EBCDIC Cyrillic Serbian-Bulgarian
      // 21027  (deprecated)
      {21866, "koi8-u"},      // Ukrainian (KOI8-U); Cyrillic (KOI8-U)
      {28591, "iso-8859-1"},  // ISO 8859-1 Latin 1; Western European (ISO)
      {28592,
       "iso-8859-2"},  // ISO 8859-2 Central European; Central European (ISO)
      {28593, "iso-8859-3"},    // ISO 8859-3 Latin 3
      {28594, "iso-8859-4"},    // ISO 8859-4 Baltic
      {28595, "iso-8859-5"},    // ISO 8859-5 Cyrillic
      {28596, "iso-8859-6"},    // ISO 8859-6 Arabic
      {28597, "iso-8859-7"},    // ISO 8859-7 Greek
      {28598, "iso-8859-8"},    // ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
      {28599, "iso-8859-9"},    // ISO 8859-9 Turkish
      {28603, "iso-8859-13"},   // ISO 8859-13 Estonian
      {28605, "iso-8859-15"},   // ISO 8859-15 Latin 9
      {29001, "x-Europa"},      // Europa 3
      {38598, "iso-8859-8-i"},  // ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
      {50220, "iso-2022-jp"},   // ISO 2022 Japanese with no halfwidth Katakana;
                                // Japanese (JIS)
      {50221, "csISO2022JP"},   // ISO 2022 Japanese with halfwidth Katakana;
                                // Japanese (JIS-Allow 1 byte Kana)
      {50222, "iso-2022-jp"},   // ISO 2022 Japanese JIS X 0201-1989; Japanese
                                // (JIS-Allow 1 byte Kana - SO/SI)
      {50225, "iso-2022-kr"},   // ISO 2022 Korean
      {50227, "x-cp50227"},  // ISO 2022 Simplified Chinese; Chinese Simplified
                             // (ISO 2022)
      // 50229  ISO 2022 Traditional Chinese
      // 50930  EBCDIC Japanese (Katakana) Extended
      // 50931  EBCDIC US-Canada and Japanese
      // 50933  EBCDIC Korean Extended and Korean
      // 50935  EBCDIC Simplified Chinese Extended and Simplified Chinese
      // 50936  EBCDIC Simplified Chinese
      // 50937  EBCDIC US-Canada and Traditional Chinese
      // 50939  EBCDIC Japanese (Latin) Extended and Japanese
      {51932, "euc-jp"},  // EUC Japanese
      {51936, "EUC-CN"},  // EUC Simplified Chinese; Chinese Simplified (EUC)
      {51949, "euc-kr"},  // EUC Korean
      // 51950  EUC Traditional Chinese
      {52936,
       "hz-gb-2312"},  // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
      {54936, "GB18030"},  // Windows XP and later: GB18030 Simplified Chinese
                           // (4 byte); Chinese Simplified (GB18030)
      {57002, "x-iscii-de"},  // ISCII Devanagari
      {57003, "x-iscii-be"},  // ISCII Bengali
      {57004, "x-iscii-ta"},  // ISCII Tamil
      {57005, "x-iscii-te"},  // ISCII Telugu
      {57006, "x-iscii-as"},  // ISCII Assamese
      {57007, "x-iscii-or"},  // ISCII Oriya (Odia)
      {57008, "x-iscii-ka"},  // ISCII Kannada
      {57009, "x-iscii-ma"},  // ISCII Malayalam
      {57010, "x-iscii-gu"},  // ISCII Gujarati
      {57011, "x-iscii-pa"},  // ISCII Punjabi
      {65000, "utf-7"},       // Unicode (UTF-7)
      {65001, "utf-8"},       // Unicode (UTF-8)
  };

  // Binary search
  int begin = 0, end = sizeof(cptocharset) / sizeof(cptocharset[0]) - 1;
  while (begin <= end) {
    int mid = (begin + end) / 2;
    unsigned int mid_cp = cptocharset[mid].cp;
    if (cp == mid_cp) return cptocharset[mid].charset;
    if (cp < mid_cp)
      end = mid - 1;
    else  // cp > cptocharset[mid].cp
      begin = mid + 1;
  }
  return 0;  // not found
}

// This function returns true only if the unicode (utf-16) text can be
// losslessly represented in specified charset
bool CMapiMessage::CheckBodyInCharsetRange(const char* charset) {
  if (m_body.IsEmpty()) return true;
  if (!_stricmp(charset, "utf-8")) return true;

  auto encoding =
      mozilla::Encoding::ForLabelNoReplacement(nsDependentCString(charset));
  if (!encoding) return false;
  auto encoder = encoding->NewEncoder();

  uint8_t buffer[512];
  auto src = mozilla::Span(m_body);
  auto dst = mozilla::Span(buffer);
  while (true) {
    uint32_t result;
    size_t read;
    std::tie(result, read, std::ignore) =
        encoder->EncodeFromUTF16WithoutReplacement(src, dst, false);
    if (result == mozilla::kInputEmpty) {
      // All converted successfully.
      break;
    } else if (result != mozilla::kOutputFull) {
      // Didn't use all the input but the output isn't full, hence
      // there was an unencodable character.
      return false;
    }
    src = src.From(read);
  }

  return true;
}

bool CaseInsensitiveComp(wchar_t elem1, wchar_t elem2) {
  return _wcsnicmp(&elem1, &elem2, 1) == 0;
}

void ExtractMetaCharset(const wchar_t* body, int bodySz,
                        /*out*/ nsCString& charset) {
  charset.Truncate();
  const wchar_t* body_end = body + bodySz;
  const wchar_t str_eohd[] = L"/head";
  const wchar_t* str_eohd_end =
      str_eohd + sizeof(str_eohd) / sizeof(str_eohd[0]) - 1;
  const wchar_t* eohd_pos =
      std::search(body, body_end, str_eohd, str_eohd_end, CaseInsensitiveComp);
  if (eohd_pos == body_end)  // No header!
    return;
  const wchar_t str_chset[] = L"charset=";
  const wchar_t* str_chset_end =
      str_chset + sizeof(str_chset) / sizeof(str_chset[0]) - 1;
  const wchar_t* chset_pos = std::search(body, eohd_pos, str_chset,
                                         str_chset_end, CaseInsensitiveComp);
  if (chset_pos == eohd_pos)  // No charset!
    return;
  chset_pos += 8;

  // remove everything from the string after the next ; or " or space,
  // whichever comes first.
  // The initial string looks something like
  // <META content="text/html; charset=utf-8" http-equiv=Content-Type>
  // <META content="text/html; charset=utf-8;" http-equiv=Content-Type>
  // <META content="text/html; charset=utf-8 ;" http-equiv=Content-Type>
  // <META content="text/html; charset=utf-8 " http-equiv=Content-Type>
  const wchar_t term[] = L";\" ",
                *term_end = term + sizeof(term) / sizeof(term[0]) - 1;
  const wchar_t* chset_end =
      std::find_first_of(chset_pos, eohd_pos, term, term_end);
  if (chset_end != eohd_pos)
    LossyCopyUTF16toASCII(
        Substring(char16ptr_t(chset_pos), char16ptr_t(chset_end)), charset);
}

bool CMapiMessage::FetchBody(void) {
  m_bodyIsHtml = false;
  m_body.Truncate();

  // Get the Outlook codepage info; if unsuccessful then it defaults to 0
  // (CP_ACP) -> system default Maybe we can use this info later?
  unsigned int codepage = 0;
  LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_INTERNET_CPID);
  if (pVal) {
    if (PROP_TYPE(pVal->ulPropTag) == PT_LONG) codepage = pVal->Value.l;
    CMapiApi::MAPIFreeBuffer(pVal);
  }

  unsigned long nativeBodyType = 0;
  if (CMapiApi::GetRTFPropertyDecodedAsUTF16(m_lpMsg, m_body, nativeBodyType,
                                             codepage)) {
    m_bodyIsHtml = nativeBodyType == MAPI_NATIVE_BODY_TYPE_HTML;
  } else {  // Cannot get RTF version
    // Is it html?
    pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_HTML_W);
    if (pVal) {
      if (CMapiApi::IsLargeProperty(pVal))
        CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_HTML_W, m_body);
      else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
               (pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
        m_body.Assign(pVal->Value.lpszW);
      CMapiApi::MAPIFreeBuffer(pVal);
    }

    // Kind-hearted Outlook will give us html even for a plain text message.
    // But it will include a comment saying it did the conversion.
    // We'll use this as a hack to really use the plain text part.
    //
    // Sadly there are cases where this string is returned despite the fact
    // that the message is indeed HTML.
    //
    // To detect the "true" plain text messages, we look for our string
    // immediately following the <BODY> tag.
    if (!m_body.IsEmpty() &&
        m_body.Find(u"<BODY>\r\n<!-- Converted from text/plain format -->") ==
            kNotFound) {
      m_bodyIsHtml = true;
    } else {
      pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_W);
      if (pVal) {
        if (CMapiApi::IsLargeProperty(pVal))
          CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_W, m_body);
        else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
                 (pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
          m_body.Assign(pVal->Value.lpszW);
        CMapiApi::MAPIFreeBuffer(pVal);
      }
    }
  }

  // OK, now let's restore the original encoding!
  // 1. We may have a header defining the charset (we already called the
  // FetchHeaders(), and there ProcessHeaders();
  //    in this case, the m_mimeCharset is set. See
  //    nsOutlookMail::ImportMailbox())
  // 2. We may have the codepage walue provided by Outlook ("codepage" at the
  // very beginning of this function)
  // 3. We may have an HTML charset header.

  bool bFoundCharset = false;

  if (!m_mimeCharset.IsEmpty())  // The top-level header data
    bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
  // No valid charset in the message header - try the HTML header.
  // arguably may be useless
  if (!bFoundCharset && m_bodyIsHtml) {
    ExtractMetaCharset(m_body.get(), m_body.Length(), m_mimeCharset);
    if (!m_mimeCharset.IsEmpty())
      bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
  }
  // Get from Outlook (seems like it keeps the MIME part header encoding info)
  if (!bFoundCharset && codepage) {
    const char* charset = CpToCharset(codepage);
    if (charset) {
      bFoundCharset = CheckBodyInCharsetRange(charset);
      if (bFoundCharset) m_mimeCharset.Assign(charset);
    }
  }
  if (!bFoundCharset)  // Everything else failed, let's use the lossless
                       // utf-8...
    m_mimeCharset.AssignLiteral("utf-8");

  MAPI_DUMP_STRING(m_body.get());
  MAPI_TRACE0("\r\n");

  return true;
}

void CMapiMessage::GetBody(nsCString& dest) const {
  nsMsgI18NConvertFromUnicode(m_mimeCharset, m_body, dest);
}

void CMapiMessage::FetchFlags(void) {
  LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_FLAGS);
  if (pVal) m_msgFlags = CMapiApi::GetLongFromProp(pVal);
  pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_LAST_VERB_EXECUTED);
  if (pVal) m_msgLastVerb = CMapiApi::GetLongFromProp(pVal);
}

enum { ieidPR_ATTACH_NUM = 0, ieidAttachMax };

static const SizedSPropTagArray(ieidAttachMax, ptaEid) = {ieidAttachMax,
                                                          {PR_ATTACH_NUM}};

bool CMapiMessage::IterateAttachTable(LPMAPITABLE lpTable) {
  ULONG rowCount;
  HRESULT hr = lpTable->GetRowCount(0, &rowCount);
  if (!rowCount) {
    return true;
  }

  hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
  if (FAILED(hr)) {
    MAPI_TRACE2("SetColumns for attachment table failed: 0x%lx, %d\r\n",
                (long)hr, (int)hr);
    return false;
  }

  hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
  if (FAILED(hr)) {
    MAPI_TRACE2("SeekRow for attachment table failed: 0x%lx, %d\r\n", (long)hr,
                (int)hr);
    return false;
  }

  int cNumRows = 0;
  LPSRowSet lpRow;
  bool bResult = true;
  do {
    lpRow = NULL;
    hr = lpTable->QueryRows(1, 0, &lpRow);

    if (HR_FAILED(hr)) {
      MAPI_TRACE2("QueryRows for attachment table failed: 0x%lx, %d\n",
                  (long)hr, (int)hr);
      bResult = false;
      break;
    }

    if (lpRow) {
      cNumRows = lpRow->cRows;

      if (cNumRows) {
        DWORD aNum = lpRow->aRow[0].lpProps[ieidPR_ATTACH_NUM].Value.ul;
        AddAttachment(aNum);
        MAPI_TRACE1("\t\t****Attachment found - #%d\r\n", (int)aNum);
      }
      CMapiApi::FreeProws(lpRow);
    }

  } while (SUCCEEDED(hr) && cNumRows && lpRow);

  return bResult;
}

bool CMapiMessage::GetTmpFile(/*out*/ nsIFile** aResult) {
  nsCOMPtr<nsIFile> tmpFile;
  nsresult rv = GetSpecialDirectoryWithFileName(
      NS_OS_TEMP_DIR, "mapiattach.tmp", getter_AddRefs(tmpFile));
  if (NS_FAILED(rv)) return false;

  rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
  if (NS_FAILED(rv)) return false;

  tmpFile.forget(aResult);
  return true;
}

bool CMapiMessage::CopyMsgAttachToFile(LPATTACH lpAttach,
                                       /*out*/ nsIFile** tmp_file) {
  LPMESSAGE lpMsg;
  HRESULT hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 0,
                                      reinterpret_cast<LPUNKNOWN*>(&lpMsg));
  if (HR_FAILED(hr)) return false;

  if (!GetTmpFile(tmp_file)) return false;

  nsCOMPtr<nsIOutputStream> destOutputStream;
  nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(destOutputStream),
                                               *tmp_file, -1, 0600);
  if (NS_SUCCEEDED(rv))
    rv = ImportMailboxRunnable::ImportMessage(lpMsg, destOutputStream,
                                              nsIMsgSend::nsMsgSaveAsDraft);

  if (NS_FAILED(rv)) {
    (*tmp_file)->Remove(false);
    (*tmp_file)->Release();
    *tmp_file = 0;
  }

  return NS_SUCCEEDED(rv);
}

bool CMapiMessage::CopyBinAttachToFile(LPATTACH lpAttach, nsIFile** tmp_file) {
  nsCOMPtr<nsIFile> _tmp_file;
  nsresult rv = GetSpecialDirectoryWithFileName(
      NS_OS_TEMP_DIR, "mapiattach.tmp", getter_AddRefs(_tmp_file));
  NS_ENSURE_SUCCESS(rv, false);

  rv = _tmp_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
  NS_ENSURE_SUCCESS(rv, false);

  nsString tmpPath = _tmp_file->NativePath();
  // We have to use native charset unless we migrate to Outlook 2013 "W" API.
  nsCString tmpNativePath;
  rv = NS_CopyUnicodeToNative(tmpPath, tmpNativePath);
  NS_ENSURE_SUCCESS(rv, false);
  LPSTREAM lpStreamFile;
  HRESULT hr = CMapiApi::OpenStreamOnFile(
      gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE,
      tmpNativePath.get(), NULL, &lpStreamFile);
  if (HR_FAILED(hr)) {
    MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n",
                tmpNativePath.get());
    return false;
  }

  bool bResult = true;
  LPSTREAM lpAttachStream;
  hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0,
                              (LPUNKNOWN*)&lpAttachStream);

  if (HR_FAILED(hr)) {
    MAPI_TRACE0("~~ERROR~~ OpenProperty failed for PR_ATTACH_DATA_BIN.\r\n");
    lpAttachStream = NULL;
    bResult = false;
  } else {
    STATSTG st;
    hr = lpAttachStream->Stat(&st, STATFLAG_NONAME);
    if (HR_FAILED(hr)) {
      MAPI_TRACE0("~~ERROR~~ Stat failed for attachment stream\r\n");
      bResult = false;
    } else {
      hr = lpAttachStream->CopyTo(lpStreamFile, st.cbSize, NULL, NULL);
      if (HR_FAILED(hr)) {
        MAPI_TRACE0("~~ERROR~~ Attach Stream CopyTo temp file failed.\r\n");
        bResult = false;
      }
    }
  }

  if (lpAttachStream) lpAttachStream->Release();
  lpStreamFile->Release();
  if (!bResult)
    _tmp_file->Remove(false);
  else
    _tmp_file.forget(tmp_file);

  return bResult;
}

bool CMapiMessage::GetURL(nsIFile* aFile, nsIURI** url) {
  if (!m_pIOService) return false;

  nsresult rv = m_pIOService->NewFileURI(aFile, url);
  return NS_SUCCEEDED(rv);
}

bool CMapiMessage::AddAttachment(DWORD aNum) {
  LPATTACH lpAttach = NULL;
  HRESULT hr = m_lpMsg->OpenAttach(aNum, NULL, 0, &lpAttach);
  if (HR_FAILED(hr)) {
    MAPI_TRACE2(
        "\t\t****Attachment error, unable to open attachment: %d, 0x%lx\r\n",
        idx, hr);
    return false;
  }

  bool bResult = false;
  attach_data* data = new attach_data;
  ULONG aMethod;
  if (data) {
    bResult = true;

    // 1. Get the file that contains the attachment data
    LPSPropValue pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_METHOD);
    if (pVal) {
      aMethod = CMapiApi::GetLongFromProp(pVal);
      switch (aMethod) {
        case ATTACH_BY_VALUE:
          MAPI_TRACE1("\t\t** Attachment #%d by value.\r\n", aNum);
          bResult =
              CopyBinAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
          data->delete_file = true;
          break;
        case ATTACH_BY_REFERENCE:
        case ATTACH_BY_REF_RESOLVE:
        case ATTACH_BY_REF_ONLY:
          pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_PATHNAME_W);
          if (pVal) {
            nsCString path;
            CMapiApi::GetStringFromProp(pVal, path);
            nsresult rv;
            data->tmp_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
            if (NS_FAILED(rv) || !data->tmp_file) {
              MAPI_TRACE0("*** Error creating file spec for attachment\n");
              bResult = false;
            } else
              data->tmp_file->InitWithNativePath(path);
          }
          MAPI_TRACE2("\t\t** Attachment #%d by ref: %s\r\n", aNum,
                      m_attachPath.get());
          break;
        case ATTACH_EMBEDDED_MSG:
          MAPI_TRACE1("\t\t** Attachment #%d by Embedded Message??\r\n", aNum);
          // Convert the embedded IMessage from PR_ATTACH_DATA_OBJ to rfc822
          // attachment (see
          // http://msdn.microsoft.com/en-us/library/cc842329.aspx) This is a
          // recursive call.
          bResult =
              CopyMsgAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
          data->delete_file = true;
          break;
        case ATTACH_OLE:
          MAPI_TRACE1("\t\t** Attachment #%d by OLE - yuck!!!\r\n", aNum);
          break;
        default:
          MAPI_TRACE2(
              "\t\t** Attachment #%d unknown attachment method - 0x%lx\r\n",
              aNum, aMethod);
          bResult = false;
      }
    } else
      bResult = false;

    if (bResult) bResult = data->tmp_file;

    if (bResult) {
      bool isFile = false;
      bool exists = false;
      data->tmp_file->Exists(&exists);
      data->tmp_file->IsFile(&isFile);

      if (!exists || !isFile) {
        bResult = false;
        MAPI_TRACE0("Attachment file does not exist\n");
      }
    }

    if (bResult)
      bResult = GetURL(data->tmp_file, getter_AddRefs(data->orig_url));

    if (bResult) {
      // Now we have the file; proceed to the other properties

      data->encoding = NS_xstrdup(ENCODING_BINARY);

      nsString fname, fext;
      pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_LONG_FILENAME_W);
      if (!pVal)
        pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FILENAME_W);
      CMapiApi::GetStringFromProp(pVal, fname);
      pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_EXTENSION_W);
      CMapiApi::GetStringFromProp(pVal, fext);
      MAPI_TRACE2("\t\t\t--- File name: %s, extension: %s\r\n", fname.get(),
                  fext.get());

      if (fext.IsEmpty()) {
        int idx = fname.RFindChar(L'.');
        if (idx != -1) fext = Substring(fname, idx);
      } else if (fname.RFindChar(L'.') == -1) {
        fname += L".";
        fname += fext;
      }
      if (fname.IsEmpty()) {
        // If no description use "Attachment i" format.
        fname = L"Attachment ";
        fname.AppendInt(static_cast<uint32_t>(aNum));
      }
      data->real_name = ToNewUTF8String(fname);

      nsCString tmp;
      // We have converted it to the rfc822 document
      if (aMethod == ATTACH_EMBEDDED_MSG) {
        data->type = NS_xstrdup(MESSAGE_RFC822);
      } else {
        pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_MIME_TAG_A);
        CMapiApi::GetStringFromProp(pVal, tmp);
        MAPI_TRACE1("\t\t\t--- Mime type: %s\r\n", tmp.get());
        if (tmp.IsEmpty()) {
          uint8_t* pType = NULL;
          if (!fext.IsEmpty()) {
            pType = CMimeTypes::GetMimeType(fext);
          }
          if (pType)
            data->type = NS_xstrdup((PC_S8)pType);
          else
            data->type = NS_xstrdup(APPLICATION_OCTET_STREAM);
        } else
          data->type = ToNewCString(tmp);
      }

      pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_CONTENT_ID_A);
      CMapiApi::GetStringFromProp(pVal, tmp);
      if (!tmp.IsEmpty()) data->cid = ToNewCString(tmp);
    }
    if (bResult) {
      // Now we need to decide if this attachment is embedded or not.
      // At first, I tried to simply check for the presence of the Content-Id.
      // But it turned out that this method is unreliable, since there exist
      // cases when an attachment has a Content-Id while isn't embedded (even in
      // a message with a plain-text body!). So next I tried to look for <img>
      // tags that contain the found Content-Id. But this is unreliable, too,
      // because there exist cases where other places of HTML reference the
      // embedded messages (e.g. it may be a background of a table cell, or some
      // CSS; further, it is possible that the reference to an embedded object
      // is not in the main body, but in another embedded object - like body
      // references a CSS attachment that in turn references a picture as a
      // background of its element). From the other hand, it's unreliable to
      // relax the search criteria to any occurrence of the Content-Id string in
      // the body - partly because the string may be simply in a text or other
      // non-referencing part, partly because of the abovementioned possibility
      // that the reference is outside the body at all. There exist the
      // PR_ATTACH_FLAGS property of the attachment. The MS documentation tells
      // about two possible flags in it: ATT_INVISIBLE_IN_HTML and
      // ATT_INVISIBLE_IN_RTF. There is at least one more undocumented flag:
      // ATT_MHTML_REF. Some sources in Internet suggest simply check for the
      // latter flag to distinguish between the embedded and ordinary
      // attachments. But my observations indicate that even if the flags don't
      // include ATT_MHTML_REF, the attachment is still may be embedded.
      // However, my observations always show that the message is embedded if
      // the flags is not 0. So now I will simply test for the non-zero flags to
      // decide whether the attachment is embedded or not. Possible advantage is
      // reliability (I hope). Another advantage is that it's much faster than
      // search the body for Content-Id.

      DWORD flags = 0;

      pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FLAGS);
      if (pVal) flags = CMapiApi::GetLongFromProp(pVal);
      if (m_bodyIsHtml && data->cid &&
          (flags != 0))  // this is the embedded attachment
        m_embattachments.push_back(data);
      else  // this is ordinary attachment
        m_stdattachments.push_back(data);
    } else {
      delete data;
    }
  }

  lpAttach->Release();
  return bResult;
}

void CMapiMessage::ClearAttachment(attach_data* data) {
  if (data->delete_file && data->tmp_file) data->tmp_file->Remove(false);

  if (data->type) free(data->type);
  if (data->encoding) free(data->encoding);
  if (data->real_name) free(data->real_name);
  if (data->cid) free(data->cid);

  delete data;
}

void CMapiMessage::ClearAttachments() {
  std::for_each(m_stdattachments.begin(), m_stdattachments.end(),
                ClearAttachment);
  m_stdattachments.clear();
  std::for_each(m_embattachments.begin(), m_embattachments.end(),
                ClearAttachment);
  m_embattachments.clear();
}

// This method must be called AFTER the retrieval of the body,
// since the decision if an attachment is embedded or not is made
// based on the body type and contents
void CMapiMessage::ProcessAttachments() {
  LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_HASATTACH);
  bool hasAttach = true;

  if (pVal) {
    if (PROP_TYPE(pVal->ulPropTag) == PT_BOOLEAN)
      hasAttach = (pVal->Value.b != 0);
    CMapiApi::MAPIFreeBuffer(pVal);
  }

  if (!hasAttach) return;

  // Get the attachment table?
  LPMAPITABLE pTable = NULL;
  HRESULT hr = m_lpMsg->GetAttachmentTable(0, &pTable);
  if (FAILED(hr) || !pTable) return;
  IterateAttachTable(pTable);
  pTable->Release();
}

nsresult CMapiMessage::GetAttachments(
    nsTArray<RefPtr<nsIMsgAttachedFile>>& attachments) {
  attachments.Clear();
  attachments.SetCapacity(m_stdattachments.size());

  for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin();
       it != m_stdattachments.end(); it++) {
    nsresult rv;
    nsCOMPtr<nsIMsgAttachedFile> a(
        do_CreateInstance("@mozilla.org/messengercompose/attachedfile;1", &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    a->SetOrigUrl((*it)->orig_url);
    a->SetTmpFile((*it)->tmp_file);
    a->SetEncoding(nsDependentCString((*it)->encoding));
    a->SetRealName(nsDependentCString((*it)->real_name));
    a->SetType(nsDependentCString((*it)->type));
    attachments.AppendElement(a);
  }
  return NS_OK;
}

bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI** uri,
                                             const char** cid,
                                             const char** name) const {
  if (i >= m_embattachments.size()) return false;
  attach_data* data = m_embattachments[i];
  if (!data) return false;
  *uri = data->orig_url;
  *cid = data->cid;
  *name = data->real_name;
  return true;
}

//////////////////////////////////////////////////////

// begin and end MUST point to the same string
char* dup(const char* begin, const char* end) {
  if (begin >= end) return 0;
  char* str = new char[end - begin + 1];
  memcpy(str, begin, (end - begin) * sizeof(begin[0]));
  str[end - begin] = 0;
  return str;
}

// See RFC822
// 9 = '\t', 32 = ' '.
inline bool IsPrintableASCII(char c) { return (c > ' ') && (c < 127); }
inline bool IsWSP(char c) { return (c == ' ') || (c == '\t'); }

CMapiMessageHeaders::CHeaderField::CHeaderField(const char* begin, int len)
    : m_fname(0), m_fbody(0), m_fbody_utf8(false) {
  const char *end = begin + len, *fname_end = begin;
  while ((fname_end < end) && IsPrintableASCII(*fname_end) &&
         (*fname_end != ':'))
    ++fname_end;
  if ((fname_end == end) || (*fname_end != ':')) return;  // Not a valid header!
  m_fname = dup(begin, fname_end + 1);                    // including colon
  m_fbody = dup(fname_end + 1, end);
}

CMapiMessageHeaders::CHeaderField::CHeaderField(const char* name,
                                                const char* body, bool utf8)
    : m_fname(dup(name, name + strlen(name))),
      m_fbody(dup(body, body + strlen(body))),
      m_fbody_utf8(utf8) {}

CMapiMessageHeaders::CHeaderField::~CHeaderField() {
  delete[] m_fname;
  delete[] m_fbody;
}

void CMapiMessageHeaders::CHeaderField::set_fbody(const char* txt) {
  if (m_fbody == txt) return;  // to avoid assigning to self
  char* oldbody = m_fbody;
  m_fbody = dup(txt, txt + strlen(txt));
  delete[] oldbody;
  m_fbody_utf8 = true;
}

void CMapiMessageHeaders::CHeaderField::GetUnfoldedString(
    nsString& dest, const char* fallbackCharset) const {
  dest.Truncate();
  if (!m_fbody) return;

  nsCString unfolded;
  const char* pos = m_fbody;
  while (*pos) {
    if ((*pos == nsCRT::CR) && (*(pos + 1) == nsCRT::LF) && IsWSP(*(pos + 2)))
      pos += 2;  // Skip CRLF if it is followed by SPACE or TAB
    else
      unfolded.Append(*(pos++));
  }
  if (m_fbody_utf8)
    CopyUTF8toUTF16(unfolded, dest);
  else
    nsMsgI18NConvertToUnicode(
        fallbackCharset ? nsDependentCString(fallbackCharset) : EmptyCString(),
        unfolded, dest);
}

////////////////////////////////////////

const char* CMapiMessageHeaders::Specials[hdrMax] = {
    "Date:",    "From:",         "Sender:",       "Reply-To:",
    "To:",      "Cc:",           "Bcc:",          "Message-ID:",
    "Subject:", "Mime-Version:", "Content-Type:", "Content-Transfer-Encoding:"};

CMapiMessageHeaders::~CMapiMessageHeaders() { ClearHeaderFields(); }

void CMapiMessageHeaders::Delete(CHeaderField* p) { delete p; }

void CMapiMessageHeaders::ClearHeaderFields() {
  std::for_each(m_headerFields.begin(), m_headerFields.end(), Delete);
  m_headerFields.clear();
}

void CMapiMessageHeaders::Assign(const char* headers) {
  for (int i = 0; i < hdrMax; i++) m_SpecialHeaders[i] = 0;
  ClearHeaderFields();
  if (!headers) return;

  const char *start = headers, *end = headers;
  while (*end) {
    if ((*end == nsCRT::CR) && (*(end + 1) == nsCRT::LF)) {
      if (!IsWSP(
              *(end +
                2))) {  // Not SPACE nor TAB (avoid FSP) -> next header or EOF
        Add(new CHeaderField(start, end - start));
        start = ++end + 1;
      }
    }
    ++end;
  }

  if (start < end) {  // Last header left
    Add(new CHeaderField(start, end - start));
  }
}

void CMapiMessageHeaders::Add(CHeaderField* f) {
  if (!f) return;
  if (!f->Valid()) {
    delete f;
    return;
  }

  SpecialHeader idx = CheckSpecialHeader(f->fname());
  if (idx != hdrNone) {
    // Now check if the special header was already inserted;
    // if so, remove previous and add this new
    CHeaderField* PrevSpecial = m_SpecialHeaders[idx];
    if (PrevSpecial) {
      std::vector<CHeaderField*>::iterator iter =
          std::find(m_headerFields.begin(), m_headerFields.end(), PrevSpecial);
      if (iter != m_headerFields.end()) m_headerFields.erase(iter);
      delete PrevSpecial;
    }
    m_SpecialHeaders[idx] = f;
  }
  m_headerFields.push_back(f);
}

CMapiMessageHeaders::SpecialHeader CMapiMessageHeaders::CheckSpecialHeader(
    const char* fname) {
  for (int i = hdrFirst; i < hdrMax; i++)
    if (stricmp(fname, Specials[i]) == 0) return static_cast<SpecialHeader>(i);

  return hdrNone;
}

const CMapiMessageHeaders::CHeaderField* CMapiMessageHeaders::CFind(
    const char* name) const {
  SpecialHeader special = CheckSpecialHeader(name);
  if ((special > hdrNone) && (special < hdrMax))
    return m_SpecialHeaders[special];  // No need to search further, because it
                                       // MUST be here

  std::vector<CHeaderField*>::const_iterator iter = std::find_if(
      m_headerFields.begin(), m_headerFields.end(), fname_equals(name));
  if (iter == m_headerFields.end()) return 0;
  return *iter;
}

const char* CMapiMessageHeaders::SpecialName(SpecialHeader special) {
  if ((special <= hdrNone) || (special >= hdrMax)) return 0;
  return Specials[special];
}

const char* CMapiMessageHeaders::Value(SpecialHeader special) const {
  if ((special <= hdrNone) || (special >= hdrMax)) return 0;
  return (m_SpecialHeaders[special]) ? m_SpecialHeaders[special]->fbody() : 0;
}

const char* CMapiMessageHeaders::Value(const char* name) const {
  const CHeaderField* result = CFind(name);
  return result ? result->fbody() : 0;
}

void CMapiMessageHeaders::UnfoldValue(const char* name, nsString& dest,
                                      const char* fallbackCharset) const {
  const CHeaderField* result = CFind(name);
  if (result)
    result->GetUnfoldedString(dest, fallbackCharset);
  else
    dest.Truncate();
}

void CMapiMessageHeaders::UnfoldValue(SpecialHeader special, nsString& dest,
                                      const char* fallbackCharset) const {
  if ((special <= hdrNone) || (special >= hdrMax) ||
      (!m_SpecialHeaders[special]))
    dest.Truncate();
  else
    m_SpecialHeaders[special]->GetUnfoldedString(dest, fallbackCharset);
}

int CMapiMessageHeaders::SetValue(const char* name, const char* value,
                                  bool replace) {
  if (!replace) {
    CHeaderField* result = Find(name);
    if (result) {
      result->set_fbody(value);
      return 0;
    }
  }
  Add(new CHeaderField(name, value, true));
  return 0;  // No sensible result is returned; maybe do something senseful
             // later
}

int CMapiMessageHeaders::SetValue(SpecialHeader special, const char* value) {
  CHeaderField* result = m_SpecialHeaders[special];
  if (result)
    result->set_fbody(value);
  else
    Add(new CHeaderField(Specials[special], value, true));
  return 0;
}

void CMapiMessageHeaders::write_to_stream::operator()(const CHeaderField* f) {
  if (!f || NS_FAILED(m_rv)) return;

  uint32_t written;
  m_rv = m_pDst->Write(f->fname(), strlen(f->fname()), &written);
  NS_ENSURE_SUCCESS_VOID(m_rv);
  if (f->fbody()) {
    m_rv = m_pDst->Write(f->fbody(), strlen(f->fbody()), &written);
    NS_ENSURE_SUCCESS_VOID(m_rv);
  }
  m_rv = m_pDst->Write("\x0D\x0A", 2, &written);
}

nsresult CMapiMessageHeaders::ToStream(nsIOutputStream* pDst) const {
  nsresult rv = std::for_each(m_headerFields.begin(), m_headerFields.end(),
                              write_to_stream(pDst));
  if (NS_SUCCEEDED(rv)) {
    uint32_t written;
    rv = pDst->Write("\x0D\x0A", 2, &written);  // Separator line
  }
  return rv;
}
