From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/import/src/MapiMessage.cpp | 1383 ++++++++++++++++++++++++++++++ 1 file changed, 1383 insertions(+) create mode 100644 comm/mailnews/import/src/MapiMessage.cpp (limited to 'comm/mailnews/import/src/MapiMessage.cpp') diff --git a/comm/mailnews/import/src/MapiMessage.cpp b/comm/mailnews/import/src/MapiMessage.cpp new file mode 100644 index 0000000000..af57aa1d4c --- /dev/null +++ b/comm/mailnews/import/src/MapiMessage.cpp @@ -0,0 +1,1383 @@ +/* -*- 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 +#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 +#include + +// 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 + // + // + // + // + 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 tag. + if (!m_body.IsEmpty() && + m_body.Find(u"\r\n") == + 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 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(&lpMsg)); + if (HR_FAILED(hr)) return false; + + if (!GetTmpFile(tmp_file)) return false; + + nsCOMPtr 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 _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(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 + // 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>& attachments) { + attachments.Clear(); + attachments.SetCapacity(m_stdattachments.size()); + + for (std::vector::const_iterator it = m_stdattachments.begin(); + it != m_stdattachments.end(); it++) { + nsresult rv; + nsCOMPtr 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::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(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::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; +} -- cgit v1.2.3