diff options
Diffstat (limited to 'mailnews/import/outlook/src/MapiMessage.cpp')
-rw-r--r-- | mailnews/import/outlook/src/MapiMessage.cpp | 1474 |
1 files changed, 1474 insertions, 0 deletions
diff --git a/mailnews/import/outlook/src/MapiMessage.cpp b/mailnews/import/outlook/src/MapiMessage.cpp new file mode 100644 index 000000000..52ec2e4c0 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMessage.cpp @@ -0,0 +1,1474 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsStringGlue.h" +#include "nsDirectoryServiceDefs.h" +#include "nsMsgUtils.h" +#include "nsMimeTypes.h" +#include "nsIOutputStream.h" + +#include "nsMsgCompCID.h" +#include "nsIMutableArray.h" +#include "MapiDbgLog.h" +#include "MapiApi.h" + +#include "MapiMimeTypes.h" + +#include <algorithm> +#include "nsMsgI18N.h" +#include "nsICharsetConverterManager.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "MapiMessage.h" + +#include "nsOutlookMail.h" + +// 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 + HRESULT hRes = S_OK; + 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; + + hRes = 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. + hRes = 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 +} + +// We don't use nsMsgI18Ncheck_data_in_charset_range because it returns true +// even if there's no such charset: +// 1. result initialized by true and returned if, eg, GetUnicodeEncoderRaw fail +// 2. it uses GetUnicodeEncoderRaw(), not GetUnicodeEncoder() (to normalize the +// charset string) (see nsMsgI18N.cpp) +// 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; + if (!_stricmp(charset, "utf-7")) + return true; + + nsresult rv; + static nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIUnicodeEncoder> encoder; + + // get an unicode converter + rv = ccm->GetUnicodeEncoder(charset, getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, false); + rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Signal, nullptr, 0); + NS_ENSURE_SUCCESS(rv, false); + + const char16_t *txt = m_body.get(); + int32_t txtLen = m_body.Length(); + const char16_t *currentSrcPtr = txt; + int srcLength; + int dstLength; + char localbuf[512]; + int consumedLen = 0; + + // convert + while (consumedLen < txtLen) { + srcLength = txtLen - consumedLen; + dstLength = sizeof(localbuf)/sizeof(localbuf[0]); + rv = encoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength); + if (rv == NS_ERROR_UENC_NOMAPPING) + return false; + if (NS_FAILED(rv) || dstLength == 0) + break; + + currentSrcPtr += srcLength; + consumedLen = currentSrcPtr - txt; // src length used so far + } + 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 inital sting 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(wwc(const_cast<wchar_t *>(chset_pos)), + wwc(const_cast<wchar_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("<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) { // Use system default + const char* charset = nsMsgI18NFileSystemCharset(); + 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.Assign("utf-8"); + + MAPI_DUMP_STRING(m_body.get()); + MAPI_TRACE0("\r\n"); + + return true; +} + +void CMapiMessage::GetBody(nsCString& dest) const +{ + nsMsgI18NConvertFromUnicode(m_mimeCharset.get(), 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) +{ + bool bResult = true; + 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 = nsOutlookMail::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); + + nsCString tmpPath; + _tmp_file->GetNativePath(tmpPath); + LPSTREAM lpStreamFile; + HRESULT hr = CMapiApi::OpenStreamOnFile(gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE, + const_cast<char*>(tmpPath.get()), NULL, &lpStreamFile); + if (HR_FAILED(hr)) { + MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n", + tmpPath.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_strdup(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_strdup(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_strdup((PC_S8)pType); + else + data->type = NS_strdup(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 occurence 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) + NS_Free(data->type); + if (data->encoding) + NS_Free(data->encoding); + if (data->real_name) + NS_Free(data->real_name); + if (data->cid) + NS_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(nsIArray **aArray) +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> attachments (do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aArray = attachments); + + for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin(); + it != m_stdattachments.end(); it++) { + nsCOMPtr<nsIMsgAttachedFile> a(do_CreateInstance(NS_MSGATTACHEDFILE_CONTRACTID, &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, false); + } + return rv; +} + +bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI **uri, + const char **cid, + const char **name) const +{ + if ((i < 0) || (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, 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 Delete(void* 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; +} |