Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef INITGUID
# define INITGUID
#endif
#ifndef USES_IID_IMessage
# define USES_IID_IMessage
#endif
#include "nscore.h"
#include <time.h>
#include "nsString.h"
#include "nsDirectoryServiceDefs.h"
#include "nsMsgUtils.h"
#include "nsMimeTypes.h"
#include "nsNativeCharsetUtils.h"
#include "nsIOutputStream.h"
#include "MapiDbgLog.h"
#include "MapiApi.h"
#include "MapiMimeTypes.h"
#include "nsMsgI18N.h"
#include "nsCRT.h"
#include "nsNetUtil.h"
#include "MapiMessage.h"
#include "nsOutlookMail.h"
#include "mozilla/Encoding.h"
#include <stdlib.h>
#include <tuple>
// needed for the call the OpenStreamOnFile
extern LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer;
extern LPMAPIFREEBUFFER gpMapiFreeBuffer;
// Sample From line: From - 1 Jan 1965 00:00:00
typedef const char* PC_S8;
static const char* kWhitespace = "\b\t\r\n ";
static const char* 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()) {
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;
}
#ifndef dispidHeaderItem
# define dispidHeaderItem 0x8578
#endif
DEFINE_OLEGUID(PSETID_Common, MAKELONG(0x2000 + (8), 0x0006), 0, 0);
void CMapiMessage::GetDownloadState() {
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
// extend as appropriate. The codepage values are sorted ascending.
static const CODEPAGE_TO_CHARSET cptocharset[] = {
{37, "IBM037"}, // IBM EBCDIC US-Canada
{437, "IBM437"}, // OEM United States
{500, "IBM500"}, // IBM EBCDIC International
{708, "ASMO-708"}, // Arabic (ASMO 708)
// 709 Arabic (ASMO-449+, BCON V4)
// 710 Arabic - Transparent Arabic
{720, "DOS-720"}, // Arabic (Transparent ASMO); Arabic (DOS)
{737, "ibm737"}, // OEM Greek (formerly 437G); Greek (DOS)
{775, "ibm775"}, // OEM Baltic; Baltic (DOS)
{850, "ibm850"}, // OEM Multilingual Latin 1; Western European (DOS)
{852, "ibm852"}, // OEM Latin 2; Central European (DOS)
{855, "IBM855"}, // OEM Cyrillic (primarily Russian)
{857, "ibm857"}, // OEM Turkish; Turkish (DOS)
{858, "IBM00858"}, // OEM Multilingual Latin 1 + Euro symbol
{860, "IBM860"}, // OEM Portuguese; Portuguese (DOS)
{861, "ibm861"}, // OEM Icelandic; Icelandic (DOS)
{862, "DOS-862"}, // OEM Hebrew; Hebrew (DOS)
{863, "IBM863"}, // OEM French Canadian; French Canadian (DOS)
{864, "IBM864"}, // OEM Arabic; Arabic (864)
{865, "IBM865"}, // OEM Nordic; Nordic (DOS)
{866, "cp866"}, // OEM Russian; Cyrillic (DOS)
{869, "ibm869"}, // OEM Modern Greek; Greek, Modern (DOS)
{870, "IBM870"}, // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC
// Multilingual Latin 2
{874, "windows-874"}, // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai
// (Windows)
{875, "cp875"}, // IBM EBCDIC Greek Modern
{932, "shift_jis"}, // ANSI/OEM Japanese; Japanese (Shift-JIS)
{936, "gb2312"}, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese
// Simplified (GB2312)
{949, "ks_c_5601-1987"}, // ANSI/OEM Korean (Unified Hangul Code)
{950, "big5"}, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR,
// PRC); Chinese Traditional (Big5)
{1026, "IBM1026"}, // IBM EBCDIC Turkish (Latin 5)
{1047, "IBM01047"}, // IBM EBCDIC Latin 1/Open System
{1140, "IBM01140"}, // IBM EBCDIC US-Canada (037 + Euro symbol); IBM
// EBCDIC (US-Canada-Euro)
{1141, "IBM01141"}, // IBM EBCDIC Germany (20273 + Euro symbol); IBM
// EBCDIC (Germany-Euro)
{1142, "IBM01142"}, // IBM EBCDIC Denmark-Norway (20277 + Euro symbol);
// IBM EBCDIC (Denmark-Norway-Euro)
{1143, "IBM01143"}, // IBM EBCDIC Finland-Sweden (20278 + Euro symbol);
// IBM EBCDIC (Finland-Sweden-Euro)
{1144, "IBM01144"}, // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC
// (Italy-Euro)
{1145, "IBM01145"}, // IBM EBCDIC Latin America-Spain (20284 + Euro
// symbol); IBM EBCDIC (Spain-Euro)
{1146, "IBM01146"}, // IBM EBCDIC United Kingdom (20285 + Euro symbol);
// IBM EBCDIC (UK-Euro)
{1147, "IBM01147"}, // IBM EBCDIC France (20297 + Euro symbol); IBM
// EBCDIC (France-Euro)
{1148, "IBM01148"}, // IBM EBCDIC International (500 + Euro symbol); IBM
// EBCDIC (International-Euro)
{1149, "IBM01149"}, // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM
// EBCDIC (Icelandic-Euro)
{1200, "utf-16"}, // Unicode UTF-16, little endian byte order (BMP of ISO
// 10646); available only to managed applications
{1201, "unicodeFFFE"}, // Unicode UTF-16, big endian byte order;
// available only to managed applications
{1250,
"windows-1250"}, // ANSI Central European; Central European (Windows)
{1251, "windows-1251"}, // ANSI Cyrillic; Cyrillic (Windows)
{1252, "windows-1252"}, // ANSI Latin 1; Western European (Windows)
{1253, "windows-1253"}, // ANSI Greek; Greek (Windows)
{1254, "windows-1254"}, // ANSI Turkish; Turkish (Windows)
{1255, "windows-1255"}, // ANSI Hebrew; Hebrew (Windows)
{1256, "windows-1256"}, // ANSI Arabic; Arabic (Windows)
{1257, "windows-1257"}, // ANSI Baltic; Baltic (Windows)
{1258, "windows-1258"}, // ANSI/OEM Vietnamese; Vietnamese (Windows)
{1361, "Johab"}, // Korean (Johab)
{10000, "macintosh"}, // MAC Roman; Western European (Mac)
{10001, "x-mac-japanese"}, // Japanese (Mac)
{10002, "x-mac-chinesetrad"}, // MAC Traditional Chinese (Big5); Chinese
// Traditional (Mac)
{10003, "x-mac-korean"}, // Korean (Mac)
{10004, "x-mac-arabic"}, // Arabic (Mac)
{10005, "x-mac-hebrew"}, // Hebrew (Mac)
{10006, "x-mac-greek"}, // Greek (Mac)
{10007, "x-mac-cyrillic"}, // Cyrillic (Mac)
{10008, "x-mac-chinesesimp"}, // MAC Simplified Chinese (GB 2312);
// Chinese Simplified (Mac)
{10010, "x-mac-romanian"}, // Romanian (Mac)
{10017, "x-mac-ukrainian"}, // Ukrainian (Mac)
{10021, "x-mac-thai"}, // Thai (Mac)
{10029, "x-mac-ce"}, // MAC Latin 2; Central European (Mac)
{10079, "x-mac-icelandic"}, // Icelandic (Mac)
{10081, "x-mac-turkish"}, // Turkish (Mac)
{10082, "x-mac-croatian"}, // Croatian (Mac)
// Unicode UTF-32, little endian byte order; available only to managed
// applications impossible in 8-bit mail
{12000, "utf-32"},
// Unicode UTF-32, big endian byte order; available only to managed
// applications impossible in 8-bit mail
{12001, "utf-32BE"},
{20000, "x-Chinese_CNS"}, // CNS Taiwan; Chinese Traditional (CNS)
{20001, "x-cp20001"}, // TCA Taiwan
{20002, "x_Chinese-Eten"}, // Eten Taiwan; Chinese Traditional (Eten)
{20003, "x-cp20003"}, // IBM5550 Taiwan
{20004, "x-cp20004"}, // TeleText Taiwan
{20005, "x-cp20005"}, // Wang Taiwan
{20105, "x-IA5"}, // IA5 (IRV International Alphabet No. 5, 7-bit);
// Western European (IA5)
{20106, "x-IA5-German"}, // IA5 German (7-bit)
{20107, "x-IA5-Swedish"}, // IA5 Swedish (7-bit)
{20108, "x-IA5-Norwegian"}, // IA5 Norwegian (7-bit)
{20127, "us-ascii"}, // US-ASCII (7-bit)
{20261, "x-cp20261"}, // T.61
{20269, "x-cp20269"}, // ISO 6937 Non-Spacing Accent
{20273, "IBM273"}, // IBM EBCDIC Germany
{20277, "IBM277"}, // IBM EBCDIC Denmark-Norway
{20278, "IBM278"}, // IBM EBCDIC Finland-Sweden
{20280, "IBM280"}, // IBM EBCDIC Italy
{20284, "IBM284"}, // IBM EBCDIC Latin America-Spain
{20285, "IBM285"}, // IBM EBCDIC United Kingdom
{20290, "IBM290"}, // IBM EBCDIC Japanese Katakana Extended
{20297, "IBM297"}, // IBM EBCDIC France
{20420, "IBM420"}, // IBM EBCDIC Arabic
{20423, "IBM423"}, // IBM EBCDIC Greek
{20424, "IBM424"}, // IBM EBCDIC Hebrew
{20833, "x-EBCDIC-KoreanExtended"}, // IBM EBCDIC Korean Extended
{20838, "IBM-Thai"}, // IBM EBCDIC Thai
{20866, "koi8-r"}, // Russian (KOI8-R); Cyrillic (KOI8-R)
{20871, "IBM871"}, // IBM EBCDIC Icelandic
{20880, "IBM880"}, // IBM EBCDIC Cyrillic Russian
{20905, "IBM905"}, // IBM EBCDIC Turkish
{20924,
"IBM00924"}, // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
{20932, "EUC-JP"}, // Japanese (JIS 0208-1990 and 0121-1990)
{20936, "x-cp20936"}, // Simplified Chinese (GB2312); Chinese Simplified
// (GB2312-80)
{20949, "x-cp20949"}, // Korean Wansung
{21025, "cp1025"}, // IBM EBCDIC Cyrillic Serbian-Bulgarian
// 21027 (deprecated)
{21866, "koi8-u"}, // Ukrainian (KOI8-U); Cyrillic (KOI8-U)
{28591, "iso-8859-1"}, // ISO 8859-1 Latin 1; Western European (ISO)
{28592,
"iso-8859-2"}, // ISO 8859-2 Central European; Central European (ISO)
{28593, "iso-8859-3"}, // ISO 8859-3 Latin 3
{28594, "iso-8859-4"}, // ISO 8859-4 Baltic
{28595, "iso-8859-5"}, // ISO 8859-5 Cyrillic
{28596, "iso-8859-6"}, // ISO 8859-6 Arabic
{28597, "iso-8859-7"}, // ISO 8859-7 Greek
{28598, "iso-8859-8"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
{28599, "iso-8859-9"}, // ISO 8859-9 Turkish
{28603, "iso-8859-13"}, // ISO 8859-13 Estonian
{28605, "iso-8859-15"}, // ISO 8859-15 Latin 9
{29001, "x-Europa"}, // Europa 3
{38598, "iso-8859-8-i"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
{50220, "iso-2022-jp"}, // ISO 2022 Japanese with no halfwidth Katakana;
// Japanese (JIS)
{50221, "csISO2022JP"}, // ISO 2022 Japanese with halfwidth Katakana;
// Japanese (JIS-Allow 1 byte Kana)
{50222, "iso-2022-jp"}, // ISO 2022 Japanese JIS X 0201-1989; Japanese
// (JIS-Allow 1 byte Kana - SO/SI)
{50225, "iso-2022-kr"}, // ISO 2022 Korean
{50227, "x-cp50227"}, // ISO 2022 Simplified Chinese; Chinese Simplified
// (ISO 2022)
// 50229 ISO 2022 Traditional Chinese
// 50930 EBCDIC Japanese (Katakana) Extended
// 50931 EBCDIC US-Canada and Japanese
// 50933 EBCDIC Korean Extended and Korean
// 50935 EBCDIC Simplified Chinese Extended and Simplified Chinese
// 50936 EBCDIC Simplified Chinese
// 50937 EBCDIC US-Canada and Traditional Chinese
// 50939 EBCDIC Japanese (Latin) Extended and Japanese
{51932, "euc-jp"}, // EUC Japanese
{51936, "EUC-CN"}, // EUC Simplified Chinese; Chinese Simplified (EUC)
{51949, "euc-kr"}, // EUC Korean
// 51950 EUC Traditional Chinese
{52936,
"hz-gb-2312"}, // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
{54936, "GB18030"}, // Windows XP and later: GB18030 Simplified Chinese
// (4 byte); Chinese Simplified (GB18030)
{57002, "x-iscii-de"}, // ISCII Devanagari
{57003, "x-iscii-be"}, // ISCII Bengali
{57004, "x-iscii-ta"}, // ISCII Tamil
{57005, "x-iscii-te"}, // ISCII Telugu
{57006, "x-iscii-as"}, // ISCII Assamese
{57007, "x-iscii-or"}, // ISCII Oriya (Odia)
{57008, "x-iscii-ka"}, // ISCII Kannada
{57009, "x-iscii-ma"}, // ISCII Malayalam
{57010, "x-iscii-gu"}, // ISCII Gujarati
{57011, "x-iscii-pa"}, // ISCII Punjabi
{65000, "utf-7"}, // Unicode (UTF-7)
{65001, "utf-8"}, // Unicode (UTF-8)
};
// Binary search
int begin = 0, end = sizeof(cptocharset) / sizeof(cptocharset[0]) - 1;
while (begin <= end) {
int mid = (begin + end) / 2;
unsigned int mid_cp = cptocharset[mid].cp;
if (cp == mid_cp) return cptocharset[mid].charset;
if (cp < mid_cp)
end = mid - 1;
else // cp > cptocharset[mid].cp
begin = mid + 1;
}
return 0; // not found
}
// This function returns true only if the unicode (utf-16) text can be
// losslessly represented in specified charset
bool CMapiMessage::CheckBodyInCharsetRange(const char* charset) {
if (m_body.IsEmpty()) return true;
if (!_stricmp(charset, "utf-8")) return true;
auto encoding =
mozilla::Encoding::ForLabelNoReplacement(nsDependentCString(charset));
if (!encoding) return false;
auto encoder = encoding->NewEncoder();
uint8_t buffer[512];
auto src = mozilla::Span(m_body);
auto dst = mozilla::Span(buffer);
while (true) {
uint32_t result;
size_t read;
std::tie(result, read, std::ignore) =
encoder->EncodeFromUTF16WithoutReplacement(src, dst, false);
if (result == mozilla::kInputEmpty) {
// All converted successfully.
break;
} else if (result != mozilla::kOutputFull) {
// Didn't use all the input but the output isn't full, hence
// there was an unencodable character.
return false;
}
src = src.From(read);
}
return true;
}
bool CaseInsensitiveComp(wchar_t elem1, wchar_t elem2) {
return _wcsnicmp(&elem1, &elem2, 1) == 0;
}
void ExtractMetaCharset(const wchar_t* body, int bodySz,
/*out*/ nsCString& charset) {
charset.Truncate();
const wchar_t* body_end = body + bodySz;
const wchar_t str_eohd[] = L"/head";
const wchar_t* str_eohd_end =
str_eohd + sizeof(str_eohd) / sizeof(str_eohd[0]) - 1;
const wchar_t* eohd_pos =
std::search(body, body_end, str_eohd, str_eohd_end, CaseInsensitiveComp);
if (eohd_pos == body_end) // No header!
return;
const wchar_t str_chset[] = L"charset=";
const wchar_t* str_chset_end =
str_chset + sizeof(str_chset) / sizeof(str_chset[0]) - 1;
const wchar_t* chset_pos = std::search(body, eohd_pos, str_chset,
str_chset_end, CaseInsensitiveComp);
if (chset_pos == eohd_pos) // No charset!
return;
chset_pos += 8;
// remove everything from the string after the next ; or " or space,
// whichever comes first.
// The initial string looks something like
// <META content="text/html; charset=utf-8" http-equiv=Content-Type>
// <META content="text/html; charset=utf-8;" http-equiv=Content-Type>
// <META content="text/html; charset=utf-8 ;" http-equiv=Content-Type>
// <META content="text/html; charset=utf-8 " http-equiv=Content-Type>
const wchar_t term[] = L";\" ",
*term_end = term + sizeof(term) / sizeof(term[0]) - 1;
const wchar_t* chset_end =
std::find_first_of(chset_pos, eohd_pos, term, term_end);
if (chset_end != eohd_pos)
LossyCopyUTF16toASCII(
Substring(char16ptr_t(chset_pos), char16ptr_t(chset_end)), charset);
}
bool CMapiMessage::FetchBody(void) {
m_bodyIsHtml = false;
m_body.Truncate();
// Get the Outlook codepage info; if unsuccessful then it defaults to 0
// (CP_ACP) -> system default Maybe we can use this info later?
unsigned int codepage = 0;
LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_INTERNET_CPID);
if (pVal) {
if (PROP_TYPE(pVal->ulPropTag) == PT_LONG) codepage = pVal->Value.l;
CMapiApi::MAPIFreeBuffer(pVal);
}
unsigned long nativeBodyType = 0;
if (CMapiApi::GetRTFPropertyDecodedAsUTF16(m_lpMsg, m_body, nativeBodyType,
codepage)) {
m_bodyIsHtml = nativeBodyType == MAPI_NATIVE_BODY_TYPE_HTML;
} else { // Cannot get RTF version
// Is it html?
pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_HTML_W);
if (pVal) {
if (CMapiApi::IsLargeProperty(pVal))
CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_HTML_W, m_body);
else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
(pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
m_body.Assign(pVal->Value.lpszW);
CMapiApi::MAPIFreeBuffer(pVal);
}
// Kind-hearted Outlook will give us html even for a plain text message.
// But it will include a comment saying it did the conversion.
// We'll use this as a hack to really use the plain text part.
//
// Sadly there are cases where this string is returned despite the fact
// that the message is indeed HTML.
//
// To detect the "true" plain text messages, we look for our string
// immediately following the <BODY> tag.
if (!m_body.IsEmpty() &&
m_body.Find(u"<BODY>\r\n<!-- Converted from text/plain format -->") ==
kNotFound) {
m_bodyIsHtml = true;
} else {
pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_W);
if (pVal) {
if (CMapiApi::IsLargeProperty(pVal))
CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_W, m_body);
else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
(pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
m_body.Assign(pVal->Value.lpszW);
CMapiApi::MAPIFreeBuffer(pVal);
}
}
}
// OK, now let's restore the original encoding!
// 1. We may have a header defining the charset (we already called the
// FetchHeaders(), and there ProcessHeaders();
// in this case, the m_mimeCharset is set. See
// nsOutlookMail::ImportMailbox())
// 2. We may have the codepage walue provided by Outlook ("codepage" at the
// very beginning of this function)
// 3. We may have an HTML charset header.
bool bFoundCharset = false;
if (!m_mimeCharset.IsEmpty()) // The top-level header data
bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
// No valid charset in the message header - try the HTML header.
// arguably may be useless
if (!bFoundCharset && m_bodyIsHtml) {
ExtractMetaCharset(m_body.get(), m_body.Length(), m_mimeCharset);
if (!m_mimeCharset.IsEmpty())
bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
}
// Get from Outlook (seems like it keeps the MIME part header encoding info)
if (!bFoundCharset && codepage) {
const char* charset = CpToCharset(codepage);
if (charset) {
bFoundCharset = CheckBodyInCharsetRange(charset);
if (bFoundCharset) m_mimeCharset.Assign(charset);
}
}
if (!bFoundCharset) // Everything else failed, let's use the lossless
// utf-8...
m_mimeCharset.AssignLiteral("utf-8");
MAPI_DUMP_STRING(m_body.get());
MAPI_TRACE0("\r\n");
return true;
}
void CMapiMessage::GetBody(nsCString& dest) const {
nsMsgI18NConvertFromUnicode(m_mimeCharset, m_body, dest);
}
void CMapiMessage::FetchFlags(void) {
LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_FLAGS);
if (pVal) m_msgFlags = CMapiApi::GetLongFromProp(pVal);
pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_LAST_VERB_EXECUTED);
if (pVal) m_msgLastVerb = CMapiApi::GetLongFromProp(pVal);
}
enum { ieidPR_ATTACH_NUM = 0, ieidAttachMax };
static const SizedSPropTagArray(ieidAttachMax, ptaEid) = {ieidAttachMax,
{PR_ATTACH_NUM}};
bool CMapiMessage::IterateAttachTable(LPMAPITABLE lpTable) {
ULONG rowCount;
HRESULT hr = lpTable->GetRowCount(0, &rowCount);
if (!rowCount) {
return true;
}
hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
if (FAILED(hr)) {
MAPI_TRACE2("SetColumns for attachment table failed: 0x%lx, %d\r\n",
(long)hr, (int)hr);
return false;
}
hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
if (FAILED(hr)) {
MAPI_TRACE2("SeekRow for attachment table failed: 0x%lx, %d\r\n", (long)hr,
(int)hr);
return false;
}
int cNumRows = 0;
LPSRowSet lpRow;
bool bResult = true;
do {
lpRow = NULL;
hr = lpTable->QueryRows(1, 0, &lpRow);
if (HR_FAILED(hr)) {
MAPI_TRACE2("QueryRows for attachment table failed: 0x%lx, %d\n",
(long)hr, (int)hr);
bResult = false;
break;
}
if (lpRow) {
cNumRows = lpRow->cRows;
if (cNumRows) {
DWORD aNum = lpRow->aRow[0].lpProps[ieidPR_ATTACH_NUM].Value.ul;
AddAttachment(aNum);
MAPI_TRACE1("\t\t****Attachment found - #%d\r\n", (int)aNum);
}
CMapiApi::FreeProws(lpRow);
}
} while (SUCCEEDED(hr) && cNumRows && lpRow);
return bResult;
}
bool CMapiMessage::GetTmpFile(/*out*/ nsIFile** aResult) {
nsCOMPtr<nsIFile> tmpFile;
nsresult rv = GetSpecialDirectoryWithFileName(
NS_OS_TEMP_DIR, "mapiattach.tmp", getter_AddRefs(tmpFile));
if (NS_FAILED(rv)) return false;
rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
if (NS_FAILED(rv)) return false;
tmpFile.forget(aResult);
return true;
}
bool CMapiMessage::CopyMsgAttachToFile(LPATTACH lpAttach,
/*out*/ nsIFile** tmp_file) {
LPMESSAGE lpMsg;
HRESULT hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 0,
reinterpret_cast<LPUNKNOWN*>(&lpMsg));
if (HR_FAILED(hr)) return false;
if (!GetTmpFile(tmp_file)) return false;
nsCOMPtr<nsIOutputStream> destOutputStream;
nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(destOutputStream),
*tmp_file, -1, 0600);
if (NS_SUCCEEDED(rv))
rv = ImportMailboxRunnable::ImportMessage(lpMsg, destOutputStream,
nsIMsgSend::nsMsgSaveAsDraft);
if (NS_FAILED(rv)) {
(*tmp_file)->Remove(false);
(*tmp_file)->Release();
*tmp_file = 0;
}
return NS_SUCCEEDED(rv);
}
bool CMapiMessage::CopyBinAttachToFile(LPATTACH lpAttach, nsIFile** tmp_file) {
nsCOMPtr<nsIFile> _tmp_file;
nsresult rv = GetSpecialDirectoryWithFileName(
NS_OS_TEMP_DIR, "mapiattach.tmp", getter_AddRefs(_tmp_file));
NS_ENSURE_SUCCESS(rv, false);
rv = _tmp_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
NS_ENSURE_SUCCESS(rv, false);
nsString tmpPath = _tmp_file->NativePath();
// We have to use native charset unless we migrate to Outlook 2013 "W" API.
nsCString tmpNativePath;
rv = NS_CopyUnicodeToNative(tmpPath, tmpNativePath);
NS_ENSURE_SUCCESS(rv, false);
LPSTREAM lpStreamFile;
HRESULT hr = CMapiApi::OpenStreamOnFile(
gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE,
tmpNativePath.get(), NULL, &lpStreamFile);
if (HR_FAILED(hr)) {
MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n",
tmpNativePath.get());
return false;
}
bool bResult = true;
LPSTREAM lpAttachStream;
hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0,
(LPUNKNOWN*)&lpAttachStream);
if (HR_FAILED(hr)) {
MAPI_TRACE0("~~ERROR~~ OpenProperty failed for PR_ATTACH_DATA_BIN.\r\n");
lpAttachStream = NULL;
bResult = false;
} else {
STATSTG st;
hr = lpAttachStream->Stat(&st, STATFLAG_NONAME);
if (HR_FAILED(hr)) {
MAPI_TRACE0("~~ERROR~~ Stat failed for attachment stream\r\n");
bResult = false;
} else {
hr = lpAttachStream->CopyTo(lpStreamFile, st.cbSize, NULL, NULL);
if (HR_FAILED(hr)) {
MAPI_TRACE0("~~ERROR~~ Attach Stream CopyTo temp file failed.\r\n");
bResult = false;
}
}
}
if (lpAttachStream) lpAttachStream->Release();
lpStreamFile->Release();
if (!bResult)
_tmp_file->Remove(false);
else
_tmp_file.forget(tmp_file);
return bResult;
}
bool CMapiMessage::GetURL(nsIFile* aFile, nsIURI** url) {
if (!m_pIOService) return false;
nsresult rv = m_pIOService->NewFileURI(aFile, url);
return NS_SUCCEEDED(rv);
}
bool CMapiMessage::AddAttachment(DWORD aNum) {
LPATTACH lpAttach = NULL;
HRESULT hr = m_lpMsg->OpenAttach(aNum, NULL, 0, &lpAttach);
if (HR_FAILED(hr)) {
MAPI_TRACE2(
"\t\t****Attachment error, unable to open attachment: %d, 0x%lx\r\n",
idx, hr);
return false;
}
bool bResult = false;
attach_data* data = new attach_data;
ULONG aMethod;
if (data) {
bResult = true;
// 1. Get the file that contains the attachment data
LPSPropValue pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_METHOD);
if (pVal) {
aMethod = CMapiApi::GetLongFromProp(pVal);
switch (aMethod) {
case ATTACH_BY_VALUE:
MAPI_TRACE1("\t\t** Attachment #%d by value.\r\n", aNum);
bResult =
CopyBinAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
data->delete_file = true;
break;
case ATTACH_BY_REFERENCE:
case ATTACH_BY_REF_RESOLVE:
case ATTACH_BY_REF_ONLY:
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_PATHNAME_W);
if (pVal) {
nsCString path;
CMapiApi::GetStringFromProp(pVal, path);
nsresult rv;
data->tmp_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
if (NS_FAILED(rv) || !data->tmp_file) {
MAPI_TRACE0("*** Error creating file spec for attachment\n");
bResult = false;
} else
data->tmp_file->InitWithNativePath(path);
}
MAPI_TRACE2("\t\t** Attachment #%d by ref: %s\r\n", aNum,
m_attachPath.get());
break;
case ATTACH_EMBEDDED_MSG:
MAPI_TRACE1("\t\t** Attachment #%d by Embedded Message??\r\n", aNum);
// Convert the embedded IMessage from PR_ATTACH_DATA_OBJ to rfc822
// attachment (see
// recursive call.
bResult =
CopyMsgAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
data->delete_file = true;
break;
case ATTACH_OLE:
MAPI_TRACE1("\t\t** Attachment #%d by OLE - yuck!!!\r\n", aNum);
break;
default:
MAPI_TRACE2(
"\t\t** Attachment #%d unknown attachment method - 0x%lx\r\n",
aNum, aMethod);
bResult = false;
}
} else
bResult = false;
if (bResult) bResult = data->tmp_file;
if (bResult) {
bool isFile = false;
bool exists = false;
data->tmp_file->Exists(&exists);
data->tmp_file->IsFile(&isFile);
if (!exists || !isFile) {
bResult = false;
MAPI_TRACE0("Attachment file does not exist\n");
}
}
if (bResult)
bResult = GetURL(data->tmp_file, getter_AddRefs(data->orig_url));
if (bResult) {
// Now we have the file; proceed to the other properties
data->encoding = NS_xstrdup(ENCODING_BINARY);
nsString fname, fext;
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_LONG_FILENAME_W);
if (!pVal)
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FILENAME_W);
CMapiApi::GetStringFromProp(pVal, fname);
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_EXTENSION_W);
CMapiApi::GetStringFromProp(pVal, fext);
MAPI_TRACE2("\t\t\t--- File name: %s, extension: %s\r\n", fname.get(),
fext.get());
if (fext.IsEmpty()) {
int idx = fname.RFindChar(L'.');
if (idx != -1) fext = Substring(fname, idx);
} else if (fname.RFindChar(L'.') == -1) {
fname += L".";
fname += fext;
}
if (fname.IsEmpty()) {
// If no description use "Attachment i" format.
fname = L"Attachment ";
fname.AppendInt(static_cast<uint32_t>(aNum));
}
data->real_name = ToNewUTF8String(fname);
nsCString tmp;
// We have converted it to the rfc822 document
if (aMethod == ATTACH_EMBEDDED_MSG) {
data->type = NS_xstrdup(MESSAGE_RFC822);
} else {
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_MIME_TAG_A);
CMapiApi::GetStringFromProp(pVal, tmp);
MAPI_TRACE1("\t\t\t--- Mime type: %s\r\n", tmp.get());
if (tmp.IsEmpty()) {
uint8_t* pType = NULL;
if (!fext.IsEmpty()) {
pType = CMimeTypes::GetMimeType(fext);
}
if (pType)
data->type = NS_xstrdup((PC_S8)pType);
else
data->type = NS_xstrdup(APPLICATION_OCTET_STREAM);
} else
data->type = ToNewCString(tmp);
}
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_CONTENT_ID_A);
CMapiApi::GetStringFromProp(pVal, tmp);
if (!tmp.IsEmpty()) data->cid = ToNewCString(tmp);
}
if (bResult) {
// Now we need to decide if this attachment is embedded or not.
// At first, I tried to simply check for the presence of the Content-Id.
// But it turned out that this method is unreliable, since there exist
// cases when an attachment has a Content-Id while isn't embedded (even in
// a message with a plain-text body!). So next I tried to look for <img>
// tags that contain the found Content-Id. But this is unreliable, too,
// because there exist cases where other places of HTML reference the
// embedded messages (e.g. it may be a background of a table cell, or some
// CSS; further, it is possible that the reference to an embedded object
// is not in the main body, but in another embedded object - like body
// references a CSS attachment that in turn references a picture as a
// background of its element). From the other hand, it's unreliable to
// relax the search criteria to any occurrence of the Content-Id string in
// the body - partly because the string may be simply in a text or other
// non-referencing part, partly because of the abovementioned possibility
// that the reference is outside the body at all. There exist the
// PR_ATTACH_FLAGS property of the attachment. The MS documentation tells
// about two possible flags in it: ATT_INVISIBLE_IN_HTML and
// ATT_INVISIBLE_IN_RTF. There is at least one more undocumented flag:
// ATT_MHTML_REF. Some sources in Internet suggest simply check for the
// latter flag to distinguish between the embedded and ordinary
// attachments. But my observations indicate that even if the flags don't
// include ATT_MHTML_REF, the attachment is still may be embedded.
// However, my observations always show that the message is embedded if
// the flags is not 0. So now I will simply test for the non-zero flags to
// decide whether the attachment is embedded or not. Possible advantage is
// reliability (I hope). Another advantage is that it's much faster than
// search the body for Content-Id.
DWORD flags = 0;
pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FLAGS);
if (pVal) flags = CMapiApi::GetLongFromProp(pVal);
if (m_bodyIsHtml && data->cid &&
(flags != 0)) // this is the embedded attachment
m_embattachments.push_back(data);
else // this is ordinary attachment
m_stdattachments.push_back(data);
} else {
delete data;
}
}
lpAttach->Release();
return bResult;
}
void CMapiMessage::ClearAttachment(attach_data* data) {
if (data->delete_file && data->tmp_file) data->tmp_file->Remove(false);
if (data->type) free(data->type);
if (data->encoding) free(data->encoding);
if (data->real_name) free(data->real_name);
if (data->cid) free(data->cid);
delete data;
}
void CMapiMessage::ClearAttachments() {
std::for_each(m_stdattachments.begin(), m_stdattachments.end(),
ClearAttachment);
m_stdattachments.clear();
std::for_each(m_embattachments.begin(), m_embattachments.end(),
ClearAttachment);
m_embattachments.clear();
}
// This method must be called AFTER the retrieval of the body,
// since the decision if an attachment is embedded or not is made
// based on the body type and contents
void CMapiMessage::ProcessAttachments() {
LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_HASATTACH);
bool hasAttach = true;
if (pVal) {
if (PROP_TYPE(pVal->ulPropTag) == PT_BOOLEAN)
hasAttach = (pVal->Value.b != 0);
CMapiApi::MAPIFreeBuffer(pVal);
}
if (!hasAttach) return;
// Get the attachment table?
LPMAPITABLE pTable = NULL;
HRESULT hr = m_lpMsg->GetAttachmentTable(0, &pTable);
if (FAILED(hr) || !pTable) return;
IterateAttachTable(pTable);
pTable->Release();
}
nsresult CMapiMessage::GetAttachments(
nsTArray<RefPtr<nsIMsgAttachedFile>>& attachments) {
attachments.Clear();
attachments.SetCapacity(m_stdattachments.size());
for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin();
it != m_stdattachments.end(); it++) {
nsresult rv;
nsCOMPtr<nsIMsgAttachedFile> a(
do_CreateInstance("@mozilla.org/messengercompose/attachedfile;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
a->SetOrigUrl((*it)->orig_url);
a->SetTmpFile((*it)->tmp_file);
a->SetEncoding(nsDependentCString((*it)->encoding));
a->SetRealName(nsDependentCString((*it)->real_name));
a->SetType(nsDependentCString((*it)->type));
attachments.AppendElement(a);
}
return NS_OK;
}
bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI** uri,
const char** cid,
const char** name) const {
if (i >= m_embattachments.size()) return false;
attach_data* data = m_embattachments[i];
if (!data) return false;
*uri = data->orig_url;
*cid = data->cid;
*name = data->real_name;
return true;
}
//////////////////////////////////////////////////////
// begin and end MUST point to the same string
char* dup(const char* begin, const char* end) {
if (begin >= end) return 0;
char* str = new char[end - begin + 1];
memcpy(str, begin, (end - begin) * sizeof(begin[0]));
str[end - begin] = 0;
return str;
}
// See RFC822
// 9 = '\t', 32 = ' '.
inline bool IsPrintableASCII(char c) { return (c > ' ') && (c < 127); }
inline bool IsWSP(char c) { return (c == ' ') || (c == '\t'); }
CMapiMessageHeaders::CHeaderField::CHeaderField(const char* begin, int len)
: m_fname(0), m_fbody(0), m_fbody_utf8(false) {
const char *end = begin + len, *fname_end = begin;
while ((fname_end < end) && IsPrintableASCII(*fname_end) &&
(*fname_end != ':'))
++fname_end;
if ((fname_end == end) || (*fname_end != ':')) return; // Not a valid header!
m_fname = dup(begin, fname_end + 1); // including colon
m_fbody = dup(fname_end + 1, end);
}
CMapiMessageHeaders::CHeaderField::CHeaderField(const char* name,
const char* body, bool utf8)
: m_fname(dup(name, name + strlen(name))),
m_fbody(dup(body, body + strlen(body))),
m_fbody_utf8(utf8) {}
CMapiMessageHeaders::CHeaderField::~CHeaderField() {
delete[] m_fname;
delete[] m_fbody;
}
void CMapiMessageHeaders::CHeaderField::set_fbody(const char* txt) {
if (m_fbody == txt) return; // to avoid assigning to self
char* oldbody = m_fbody;
m_fbody = dup(txt, txt + strlen(txt));
delete[] oldbody;
m_fbody_utf8 = true;
}
void CMapiMessageHeaders::CHeaderField::GetUnfoldedString(
nsString& dest, const char* fallbackCharset) const {
dest.Truncate();
if (!m_fbody) return;
nsCString unfolded;
const char* pos = m_fbody;
while (*pos) {
if ((*pos == nsCRT::CR) && (*(pos + 1) == nsCRT::LF) && IsWSP(*(pos + 2)))
pos += 2; // Skip CRLF if it is followed by SPACE or TAB
else
unfolded.Append(*(pos++));
}
if (m_fbody_utf8)
CopyUTF8toUTF16(unfolded, dest);
else
nsMsgI18NConvertToUnicode(
fallbackCharset ? nsDependentCString(fallbackCharset) : EmptyCString(),
unfolded, dest);
}
////////////////////////////////////////
const char* CMapiMessageHeaders::Specials[hdrMax] = {
"Date:", "From:", "Sender:", "Reply-To:",
"To:", "Cc:", "Bcc:", "Message-ID:",
"Subject:", "Mime-Version:", "Content-Type:", "Content-Transfer-Encoding:"};
CMapiMessageHeaders::~CMapiMessageHeaders() { ClearHeaderFields(); }
void CMapiMessageHeaders::Delete(CHeaderField* p) { delete p; }
void CMapiMessageHeaders::ClearHeaderFields() {
std::for_each(m_headerFields.begin(), m_headerFields.end(), Delete);
m_headerFields.clear();
}
void CMapiMessageHeaders::Assign(const char* headers) {
for (int i = 0; i < hdrMax; i++) m_SpecialHeaders[i] = 0;
ClearHeaderFields();
if (!headers) return;
const char *start = headers, *end = headers;
while (*end) {
if ((*end == nsCRT::CR) && (*(end + 1) == nsCRT::LF)) {
if (!IsWSP(
*(end +
2))) { // Not SPACE nor TAB (avoid FSP) -> next header or EOF
Add(new CHeaderField(start, end - start));
start = ++end + 1;
}
}
++end;
}
if (start < end) { // Last header left
Add(new CHeaderField(start, end - start));
}
}
void CMapiMessageHeaders::Add(CHeaderField* f) {
if (!f) return;
if (!f->Valid()) {
delete f;
return;
}
SpecialHeader idx = CheckSpecialHeader(f->fname());
if (idx != hdrNone) {
// Now check if the special header was already inserted;
// if so, remove previous and add this new
CHeaderField* PrevSpecial = m_SpecialHeaders[idx];
if (PrevSpecial) {
std::vector<CHeaderField*>::iterator iter =
std::find(m_headerFields.begin(), m_headerFields.end(), PrevSpecial);
if (iter != m_headerFields.end()) m_headerFields.erase(iter);
delete PrevSpecial;
}
m_SpecialHeaders[idx] = f;
}
m_headerFields.push_back(f);
}
CMapiMessageHeaders::SpecialHeader CMapiMessageHeaders::CheckSpecialHeader(
const char* fname) {
for (int i = hdrFirst; i < hdrMax; i++)
if (stricmp(fname, Specials[i]) == 0) return static_cast<SpecialHeader>(i);
return hdrNone;
}
const CMapiMessageHeaders::CHeaderField* CMapiMessageHeaders::CFind(
const char* name) const {
SpecialHeader special = CheckSpecialHeader(name);
if ((special > hdrNone) && (special < hdrMax))
return m_SpecialHeaders[special]; // No need to search further, because it
// MUST be here
std::vector<CHeaderField*>::const_iterator iter = std::find_if(
m_headerFields.begin(), m_headerFields.end(), fname_equals(name));
if (iter == m_headerFields.end()) return 0;
return *iter;
}
const char* CMapiMessageHeaders::SpecialName(SpecialHeader special) {
if ((special <= hdrNone) || (special >= hdrMax)) return 0;
return Specials[special];
}
const char* CMapiMessageHeaders::Value(SpecialHeader special) const {
if ((special <= hdrNone) || (special >= hdrMax)) return 0;
return (m_SpecialHeaders[special]) ? m_SpecialHeaders[special]->fbody() : 0;
}
const char* CMapiMessageHeaders::Value(const char* name) const {
const CHeaderField* result = CFind(name);
return result ? result->fbody() : 0;
}
void CMapiMessageHeaders::UnfoldValue(const char* name, nsString& dest,
const char* fallbackCharset) const {
const CHeaderField* result = CFind(name);
if (result)
result->GetUnfoldedString(dest, fallbackCharset);
else
dest.Truncate();
}
void CMapiMessageHeaders::UnfoldValue(SpecialHeader special, nsString& dest,
const char* fallbackCharset) const {
if ((special <= hdrNone) || (special >= hdrMax) ||
(!m_SpecialHeaders[special]))
dest.Truncate();
else
m_SpecialHeaders[special]->GetUnfoldedString(dest, fallbackCharset);
}
int CMapiMessageHeaders::SetValue(const char* name, const char* value,
bool replace) {
if (!replace) {
CHeaderField* result = Find(name);
if (result) {
result->set_fbody(value);
return 0;
}
}
Add(new CHeaderField(name, value, true));
return 0; // No sensible result is returned; maybe do something senseful
// later
}
int CMapiMessageHeaders::SetValue(SpecialHeader special, const char* value) {
CHeaderField* result = m_SpecialHeaders[special];
if (result)
result->set_fbody(value);
else
Add(new CHeaderField(Specials[special], value, true));
return 0;
}
void CMapiMessageHeaders::write_to_stream::operator()(const CHeaderField* f) {
if (!f || NS_FAILED(m_rv)) return;
uint32_t written;
m_rv = m_pDst->Write(f->fname(), strlen(f->fname()), &written);
NS_ENSURE_SUCCESS_VOID(m_rv);
if (f->fbody()) {
m_rv = m_pDst->Write(f->fbody(), strlen(f->fbody()), &written);
NS_ENSURE_SUCCESS_VOID(m_rv);
}
m_rv = m_pDst->Write("\x0D\x0A", 2, &written);
}
nsresult CMapiMessageHeaders::ToStream(nsIOutputStream* pDst) const {
nsresult rv = std::for_each(m_headerFields.begin(), m_headerFields.end(),
write_to_stream(pDst));
if (NS_SUCCEEDED(rv)) {
uint32_t written;
rv = pDst->Write("\x0D\x0A", 2, &written); // Separator line
}
return rv;
}