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/ImportCharSet.cpp | 58 + comm/mailnews/import/src/ImportCharSet.h | 201 +++ comm/mailnews/import/src/ImportDebug.h | 25 + comm/mailnews/import/src/ImportOutFile.cpp | 257 +++ comm/mailnews/import/src/ImportOutFile.h | 94 + comm/mailnews/import/src/ImportTranslate.cpp | 100 ++ comm/mailnews/import/src/ImportTranslate.h | 23 + comm/mailnews/import/src/MapiApi.cpp | 1842 ++++++++++++++++++++ comm/mailnews/import/src/MapiApi.h | 284 +++ comm/mailnews/import/src/MapiDbgLog.h | 36 + comm/mailnews/import/src/MapiMessage.cpp | 1383 +++++++++++++++ comm/mailnews/import/src/MapiMessage.h | 290 +++ comm/mailnews/import/src/MapiMimeTypes.cpp | 81 + comm/mailnews/import/src/MapiMimeTypes.h | 27 + comm/mailnews/import/src/MapiTagStrs.cpp | 1473 ++++++++++++++++ comm/mailnews/import/src/MorkImport.cpp | 343 ++++ comm/mailnews/import/src/MorkImport.h | 50 + comm/mailnews/import/src/SeamonkeyImport.jsm | 253 +++ comm/mailnews/import/src/ThunderbirdImport.jsm | 145 ++ comm/mailnews/import/src/components.conf | 104 ++ comm/mailnews/import/src/moz.build | 86 + comm/mailnews/import/src/nsAddrDatabase.cpp | 864 +++++++++ comm/mailnews/import/src/nsAddrDatabase.h | 158 ++ comm/mailnews/import/src/nsAppleMailImport.cpp | 609 +++++++ comm/mailnews/import/src/nsAppleMailImport.h | 86 + comm/mailnews/import/src/nsBeckyAddressBooks.cpp | 311 ++++ comm/mailnews/import/src/nsBeckyAddressBooks.h | 36 + comm/mailnews/import/src/nsBeckyFilters.cpp | 711 ++++++++ comm/mailnews/import/src/nsBeckyFilters.h | 73 + comm/mailnews/import/src/nsBeckyImport.cpp | 143 ++ comm/mailnews/import/src/nsBeckyImport.h | 38 + comm/mailnews/import/src/nsBeckyMail.cpp | 566 ++++++ comm/mailnews/import/src/nsBeckyMail.h | 41 + comm/mailnews/import/src/nsBeckySettings.cpp | 379 ++++ comm/mailnews/import/src/nsBeckySettings.h | 50 + comm/mailnews/import/src/nsBeckyStringBundle.cpp | 55 + comm/mailnews/import/src/nsBeckyStringBundle.h | 32 + comm/mailnews/import/src/nsBeckyUtils.cpp | 302 ++++ comm/mailnews/import/src/nsBeckyUtils.h | 35 + comm/mailnews/import/src/nsEmlxHelperUtils.h | 61 + comm/mailnews/import/src/nsEmlxHelperUtils.mm | 230 +++ comm/mailnews/import/src/nsImportABDescriptor.cpp | 19 + comm/mailnews/import/src/nsImportABDescriptor.h | 100 ++ comm/mailnews/import/src/nsImportAddressBooks.cpp | 571 ++++++ comm/mailnews/import/src/nsImportAddressBooks.h | 81 + .../import/src/nsImportEmbeddedImageData.cpp | 52 + .../import/src/nsImportEmbeddedImageData.h | 31 + comm/mailnews/import/src/nsImportEncodeScan.cpp | 334 ++++ comm/mailnews/import/src/nsImportEncodeScan.h | 39 + comm/mailnews/import/src/nsImportFieldMap.cpp | 325 ++++ comm/mailnews/import/src/nsImportFieldMap.h | 44 + comm/mailnews/import/src/nsImportMail.cpp | 1007 +++++++++++ comm/mailnews/import/src/nsImportMail.h | 86 + .../import/src/nsImportMailboxDescriptor.cpp | 25 + .../import/src/nsImportMailboxDescriptor.h | 94 + comm/mailnews/import/src/nsImportScanFile.cpp | 154 ++ comm/mailnews/import/src/nsImportScanFile.h | 56 + comm/mailnews/import/src/nsImportService.cpp | 293 ++++ comm/mailnews/import/src/nsImportService.h | 54 + comm/mailnews/import/src/nsImportStringBundle.cpp | 67 + comm/mailnews/import/src/nsImportStringBundle.h | 43 + comm/mailnews/import/src/nsImportTranslator.cpp | 308 ++++ comm/mailnews/import/src/nsImportTranslator.h | 87 + comm/mailnews/import/src/nsOutlookCompose.cpp | 669 +++++++ comm/mailnews/import/src/nsOutlookCompose.h | 63 + comm/mailnews/import/src/nsOutlookImport.cpp | 522 ++++++ comm/mailnews/import/src/nsOutlookImport.h | 38 + comm/mailnews/import/src/nsOutlookMail.cpp | 830 +++++++++ comm/mailnews/import/src/nsOutlookMail.h | 84 + comm/mailnews/import/src/nsOutlookSettings.cpp | 500 ++++++ comm/mailnews/import/src/nsOutlookSettings.h | 27 + comm/mailnews/import/src/nsOutlookStringBundle.cpp | 53 + comm/mailnews/import/src/nsOutlookStringBundle.h | 36 + comm/mailnews/import/src/nsTextAddress.cpp | 426 +++++ comm/mailnews/import/src/nsTextAddress.h | 60 + comm/mailnews/import/src/nsTextImport.cpp | 646 +++++++ comm/mailnews/import/src/nsTextImport.h | 40 + comm/mailnews/import/src/nsVCardAddress.cpp | 151 ++ comm/mailnews/import/src/nsVCardAddress.h | 28 + comm/mailnews/import/src/nsVCardImport.cpp | 352 ++++ comm/mailnews/import/src/nsVCardImport.h | 39 + comm/mailnews/import/src/nsWMImport.cpp | 199 +++ comm/mailnews/import/src/nsWMImport.h | 38 + comm/mailnews/import/src/nsWMSettings.cpp | 679 ++++++++ comm/mailnews/import/src/nsWMSettings.h | 22 + comm/mailnews/import/src/nsWMStringBundle.cpp | 52 + comm/mailnews/import/src/nsWMStringBundle.h | 36 + comm/mailnews/import/src/nsWMUtils.cpp | 153 ++ comm/mailnews/import/src/nsWMUtils.h | 23 + comm/mailnews/import/src/rtfDecoder.cpp | 561 ++++++ comm/mailnews/import/src/rtfDecoder.h | 21 + comm/mailnews/import/src/rtfMailDecoder.cpp | 71 + comm/mailnews/import/src/rtfMailDecoder.h | 44 + 93 files changed, 22268 insertions(+) create mode 100644 comm/mailnews/import/src/ImportCharSet.cpp create mode 100644 comm/mailnews/import/src/ImportCharSet.h create mode 100644 comm/mailnews/import/src/ImportDebug.h create mode 100644 comm/mailnews/import/src/ImportOutFile.cpp create mode 100644 comm/mailnews/import/src/ImportOutFile.h create mode 100644 comm/mailnews/import/src/ImportTranslate.cpp create mode 100644 comm/mailnews/import/src/ImportTranslate.h create mode 100644 comm/mailnews/import/src/MapiApi.cpp create mode 100644 comm/mailnews/import/src/MapiApi.h create mode 100644 comm/mailnews/import/src/MapiDbgLog.h create mode 100644 comm/mailnews/import/src/MapiMessage.cpp create mode 100644 comm/mailnews/import/src/MapiMessage.h create mode 100644 comm/mailnews/import/src/MapiMimeTypes.cpp create mode 100644 comm/mailnews/import/src/MapiMimeTypes.h create mode 100644 comm/mailnews/import/src/MapiTagStrs.cpp create mode 100644 comm/mailnews/import/src/MorkImport.cpp create mode 100644 comm/mailnews/import/src/MorkImport.h create mode 100644 comm/mailnews/import/src/SeamonkeyImport.jsm create mode 100644 comm/mailnews/import/src/ThunderbirdImport.jsm create mode 100644 comm/mailnews/import/src/components.conf create mode 100644 comm/mailnews/import/src/moz.build create mode 100644 comm/mailnews/import/src/nsAddrDatabase.cpp create mode 100644 comm/mailnews/import/src/nsAddrDatabase.h create mode 100644 comm/mailnews/import/src/nsAppleMailImport.cpp create mode 100644 comm/mailnews/import/src/nsAppleMailImport.h create mode 100644 comm/mailnews/import/src/nsBeckyAddressBooks.cpp create mode 100644 comm/mailnews/import/src/nsBeckyAddressBooks.h create mode 100644 comm/mailnews/import/src/nsBeckyFilters.cpp create mode 100644 comm/mailnews/import/src/nsBeckyFilters.h create mode 100644 comm/mailnews/import/src/nsBeckyImport.cpp create mode 100644 comm/mailnews/import/src/nsBeckyImport.h create mode 100644 comm/mailnews/import/src/nsBeckyMail.cpp create mode 100644 comm/mailnews/import/src/nsBeckyMail.h create mode 100644 comm/mailnews/import/src/nsBeckySettings.cpp create mode 100644 comm/mailnews/import/src/nsBeckySettings.h create mode 100644 comm/mailnews/import/src/nsBeckyStringBundle.cpp create mode 100644 comm/mailnews/import/src/nsBeckyStringBundle.h create mode 100644 comm/mailnews/import/src/nsBeckyUtils.cpp create mode 100644 comm/mailnews/import/src/nsBeckyUtils.h create mode 100644 comm/mailnews/import/src/nsEmlxHelperUtils.h create mode 100644 comm/mailnews/import/src/nsEmlxHelperUtils.mm create mode 100644 comm/mailnews/import/src/nsImportABDescriptor.cpp create mode 100644 comm/mailnews/import/src/nsImportABDescriptor.h create mode 100644 comm/mailnews/import/src/nsImportAddressBooks.cpp create mode 100644 comm/mailnews/import/src/nsImportAddressBooks.h create mode 100644 comm/mailnews/import/src/nsImportEmbeddedImageData.cpp create mode 100644 comm/mailnews/import/src/nsImportEmbeddedImageData.h create mode 100644 comm/mailnews/import/src/nsImportEncodeScan.cpp create mode 100644 comm/mailnews/import/src/nsImportEncodeScan.h create mode 100644 comm/mailnews/import/src/nsImportFieldMap.cpp create mode 100644 comm/mailnews/import/src/nsImportFieldMap.h create mode 100644 comm/mailnews/import/src/nsImportMail.cpp create mode 100644 comm/mailnews/import/src/nsImportMail.h create mode 100644 comm/mailnews/import/src/nsImportMailboxDescriptor.cpp create mode 100644 comm/mailnews/import/src/nsImportMailboxDescriptor.h create mode 100644 comm/mailnews/import/src/nsImportScanFile.cpp create mode 100644 comm/mailnews/import/src/nsImportScanFile.h create mode 100644 comm/mailnews/import/src/nsImportService.cpp create mode 100644 comm/mailnews/import/src/nsImportService.h create mode 100644 comm/mailnews/import/src/nsImportStringBundle.cpp create mode 100644 comm/mailnews/import/src/nsImportStringBundle.h create mode 100644 comm/mailnews/import/src/nsImportTranslator.cpp create mode 100644 comm/mailnews/import/src/nsImportTranslator.h create mode 100644 comm/mailnews/import/src/nsOutlookCompose.cpp create mode 100644 comm/mailnews/import/src/nsOutlookCompose.h create mode 100644 comm/mailnews/import/src/nsOutlookImport.cpp create mode 100644 comm/mailnews/import/src/nsOutlookImport.h create mode 100644 comm/mailnews/import/src/nsOutlookMail.cpp create mode 100644 comm/mailnews/import/src/nsOutlookMail.h create mode 100644 comm/mailnews/import/src/nsOutlookSettings.cpp create mode 100644 comm/mailnews/import/src/nsOutlookSettings.h create mode 100644 comm/mailnews/import/src/nsOutlookStringBundle.cpp create mode 100644 comm/mailnews/import/src/nsOutlookStringBundle.h create mode 100644 comm/mailnews/import/src/nsTextAddress.cpp create mode 100644 comm/mailnews/import/src/nsTextAddress.h create mode 100644 comm/mailnews/import/src/nsTextImport.cpp create mode 100644 comm/mailnews/import/src/nsTextImport.h create mode 100644 comm/mailnews/import/src/nsVCardAddress.cpp create mode 100644 comm/mailnews/import/src/nsVCardAddress.h create mode 100644 comm/mailnews/import/src/nsVCardImport.cpp create mode 100644 comm/mailnews/import/src/nsVCardImport.h create mode 100644 comm/mailnews/import/src/nsWMImport.cpp create mode 100644 comm/mailnews/import/src/nsWMImport.h create mode 100644 comm/mailnews/import/src/nsWMSettings.cpp create mode 100644 comm/mailnews/import/src/nsWMSettings.h create mode 100644 comm/mailnews/import/src/nsWMStringBundle.cpp create mode 100644 comm/mailnews/import/src/nsWMStringBundle.h create mode 100644 comm/mailnews/import/src/nsWMUtils.cpp create mode 100644 comm/mailnews/import/src/nsWMUtils.h create mode 100644 comm/mailnews/import/src/rtfDecoder.cpp create mode 100644 comm/mailnews/import/src/rtfDecoder.h create mode 100644 comm/mailnews/import/src/rtfMailDecoder.cpp create mode 100644 comm/mailnews/import/src/rtfMailDecoder.h (limited to 'comm/mailnews/import/src') diff --git a/comm/mailnews/import/src/ImportCharSet.cpp b/comm/mailnews/import/src/ImportCharSet.cpp new file mode 100644 index 0000000000..ea79221210 --- /dev/null +++ b/comm/mailnews/import/src/ImportCharSet.cpp @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +#include "ImportCharSet.h" + +char ImportCharSet::m_upperCaseMap[256]; +char ImportCharSet::m_Ascii[256] = {0}; // the initialiser makes it strong + +class UInitMaps { + public: + UInitMaps(); +}; + +UInitMaps gInitMaps; + +UInitMaps::UInitMaps() { + int i; + + for (i = 0; i < 256; i++) ImportCharSet::m_upperCaseMap[i] = i; + for (i = 'a'; i <= 'z'; i++) ImportCharSet::m_upperCaseMap[i] = i - 'a' + 'A'; + + for (i = 0; i < 256; i++) ImportCharSet::m_Ascii[i] = 0; + + for (i = ImportCharSet::cUpperAChar; i <= ImportCharSet::cUpperZChar; i++) + ImportCharSet::m_Ascii[i] |= + (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar); + for (i = ImportCharSet::cLowerAChar; i <= ImportCharSet::cLowerZChar; i++) + ImportCharSet::m_Ascii[i] |= + (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar); + for (i = ImportCharSet::cZeroChar; i <= ImportCharSet::cNineChar; i++) + ImportCharSet::m_Ascii[i] |= + (ImportCharSet::cAlphaNumChar | ImportCharSet::cDigitChar); + + ImportCharSet::m_Ascii[ImportCharSet::cTabChar] |= + ImportCharSet::cWhiteSpaceChar; + ImportCharSet::m_Ascii[ImportCharSet::cCRChar] |= + ImportCharSet::cWhiteSpaceChar; + ImportCharSet::m_Ascii[ImportCharSet::cLinefeedChar] |= + ImportCharSet::cWhiteSpaceChar; + ImportCharSet::m_Ascii[ImportCharSet::cSpaceChar] |= + ImportCharSet::cWhiteSpaceChar; + + ImportCharSet::m_Ascii[uint8_t('(')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(')')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('<')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('>')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('@')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(',')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(';')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(':')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('\\')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('"')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('.')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t('[')] |= ImportCharSet::c822SpecialChar; + ImportCharSet::m_Ascii[uint8_t(']')] |= ImportCharSet::c822SpecialChar; +} diff --git a/comm/mailnews/import/src/ImportCharSet.h b/comm/mailnews/import/src/ImportCharSet.h new file mode 100644 index 0000000000..0feb8d2a98 --- /dev/null +++ b/comm/mailnews/import/src/ImportCharSet.h @@ -0,0 +1,201 @@ +/* -*- 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 ImportCharSet_h___ +#define ImportCharSet_h___ + +#include "nscore.h" + +// Some useful ASCII values +// 'A' = 65, 0x41 +// 'Z' = 90, 0x5a +// '_' = 95, 0x5f +// 'a' = 97, 0x61 +// 'z' = 122, 0x7a +// '0' = 48, 0x30 +// '1' = 49, 0x31 +// '9' = 57, 0x39 +// ' ' = 32, 0x20 +// whitespace, 10, 13, 32, 9 (linefeed, cr, space, tab) - 0x0a, 0x0d, 0x20, +// 0x09 +// ':' = 58, 0x3a + +// a typedef enum would be nicer but some compilers still have trouble with +// treating enum's as plain numbers when needed + +class ImportCharSet { + public: + enum { + cTabChar = 9, + cLinefeedChar = 10, + cCRChar = 13, + cSpaceChar = 32, + cUpperAChar = 65, + cUpperZChar = 90, + cUnderscoreChar = 95, + cLowerAChar = 97, + cLowerZChar = 122, + cZeroChar = 48, + cNineChar = 57, + + cAlphaNumChar = 1, + cAlphaChar = 2, + cWhiteSpaceChar = 4, + cDigitChar = 8, + c822SpecialChar = 16 + }; + + static char m_upperCaseMap[256]; + static char m_Ascii[256]; + + inline static bool IsUSAscii(uint8_t ch) { + return (((ch & (uint8_t)0x80) == 0)); + } + inline static bool Is822CtlChar(uint8_t ch) { return (ch < 32); } + inline static bool Is822SpecialChar(uint8_t ch) { + return ((m_Ascii[ch] & c822SpecialChar) == c822SpecialChar); + } + inline static bool IsWhiteSpace(uint8_t ch) { + return ((m_Ascii[ch] & cWhiteSpaceChar) == cWhiteSpaceChar); + } + inline static bool IsAlphaNum(uint8_t ch) { + return ((m_Ascii[ch] & cAlphaNumChar) == cAlphaNumChar); + } + inline static bool IsDigit(uint8_t ch) { + return ((m_Ascii[ch] & cDigitChar) == cDigitChar); + } + + inline static uint8_t ToLower(uint8_t ch) { + if ((m_Ascii[ch] & cAlphaChar) == cAlphaChar) { + return cLowerAChar + (m_upperCaseMap[ch] - cUpperAChar); + } else + return ch; + } + + inline static long AsciiToLong(const uint8_t* pChar, uint32_t len) { + long num = 0; + while (len) { + if ((m_Ascii[*pChar] & cDigitChar) == 0) return num; + num *= 10; + num += (*pChar - cZeroChar); + len--; + pChar++; + } + return num; + } + + inline static void ByteToHex(uint8_t byte, uint8_t* pHex) { + uint8_t val = byte; + val /= 16; + if (val < 10) + *pHex = '0' + val; + else + *pHex = 'A' + (val - 10); + pHex++; + val = byte; + val &= 0x0F; + if (val < 10) + *pHex = '0' + val; + else + *pHex = 'A' + (val - 10); + } + + inline static void LongToHexBytes(uint32_t type, uint8_t* pStr) { + ByteToHex((uint8_t)(type >> 24), pStr); + pStr += 2; + ByteToHex((uint8_t)((type >> 16) & 0x0FF), pStr); + pStr += 2; + ByteToHex((uint8_t)((type >> 8) & 0x0FF), pStr); + pStr += 2; + ByteToHex((uint8_t)(type & 0x0FF), pStr); + } + + inline static void SkipWhiteSpace(const uint8_t*& pChar, uint32_t& pos, + uint32_t max) { + while ((pos < max) && (IsWhiteSpace(*pChar))) { + pos++; + pChar++; + } + } + + inline static void SkipSpaceTab(const uint8_t*& pChar, uint32_t& pos, + uint32_t max) { + while ((pos < max) && + ((*pChar == (uint8_t)cSpaceChar) || (*pChar == (uint8_t)cTabChar))) { + pos++; + pChar++; + } + } + + inline static void SkipTilSpaceTab(const uint8_t*& pChar, uint32_t& pos, + uint32_t max) { + while ((pos < max) && (*pChar != (uint8_t)cSpaceChar) && + (*pChar != (uint8_t)cTabChar)) { + pos++; + pChar++; + } + } + + inline static bool StrNICmp(const uint8_t* pChar, const uint8_t* pSrc, + uint32_t len) { + while (len && (m_upperCaseMap[*pChar] == m_upperCaseMap[*pSrc])) { + pChar++; + pSrc++; + len--; + } + return len == 0; + } + + inline static bool StrNCmp(const uint8_t* pChar, const uint8_t* pSrc, + uint32_t len) { + while (len && (*pChar == *pSrc)) { + pChar++; + pSrc++; + len--; + } + return len == 0; + } + + inline static int FindChar(const uint8_t* pChar, uint8_t ch, uint32_t max) { + uint32_t pos = 0; + while ((pos < max) && (*pChar != ch)) { + pos++; + pChar++; + } + if (pos < max) + return (int)pos; + else + return -1; + } + + inline static bool NextChar(const uint8_t*& pChar, uint8_t ch, uint32_t& pos, + uint32_t max) { + if ((pos < max) && (*pChar == ch)) { + pos++; + pChar++; + return true; + } + return false; + } + + inline static int32_t strcmp(const char* pS1, const char* pS2) { + while (*pS1 && *pS2 && (*pS1 == *pS2)) { + pS1++; + pS2++; + } + return *pS1 - *pS2; + } + + inline static int32_t stricmp(const char* pS1, const char* pS2) { + while (*pS1 && *pS2 && + (m_upperCaseMap[uint8_t(*pS1)] == m_upperCaseMap[uint8_t(*pS2)])) { + pS1++; + pS2++; + } + return m_upperCaseMap[uint8_t(*pS1)] - m_upperCaseMap[uint8_t(*pS2)]; + } +}; + +#endif /* ImportCharSet_h__ */ diff --git a/comm/mailnews/import/src/ImportDebug.h b/comm/mailnews/import/src/ImportDebug.h new file mode 100644 index 0000000000..f1698ed442 --- /dev/null +++ b/comm/mailnews/import/src/ImportDebug.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ImportDebug_h___ +#define ImportDebug_h___ + +#ifdef NS_DEBUG +# define IMPORT_DEBUG 1 +#endif + +#include "mozilla/Logging.h" +extern mozilla::LazyLogModule + IMPORTLOGMODULE; // defined in nsImportService.cpp + +#define IMPORT_LOG0(x) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) \ + MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) \ + MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) \ + MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + +#endif diff --git a/comm/mailnews/import/src/ImportOutFile.cpp b/comm/mailnews/import/src/ImportOutFile.cpp new file mode 100644 index 0000000000..3622b56ad7 --- /dev/null +++ b/comm/mailnews/import/src/ImportOutFile.cpp @@ -0,0 +1,257 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsString.h" +#include "prio.h" +#include "nsNetUtil.h" +#include "nsISeekableStream.h" +#include "nsMsgUtils.h" +#include "ImportOutFile.h" +#include "ImportCharSet.h" + +#include "ImportDebug.h" + +/* +#ifdef _MAC +#define kMacNoCreator '????' +#define kMacTextFile 'TEXT' +#else +#define kMacNoCreator 0 +#define kMacTextFile 0 +#endif +*/ + +ImportOutFile::ImportOutFile() { + m_ownsFileAndBuffer = false; + m_pos = 0; + m_pBuf = nullptr; + m_bufSz = 0; + m_pTrans = nullptr; + m_pTransOut = nullptr; + m_pTransBuf = nullptr; +} + +ImportOutFile::ImportOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz) { + m_pTransBuf = nullptr; + m_pTransOut = nullptr; + m_pTrans = nullptr; + m_ownsFileAndBuffer = false; + InitOutFile(pFile, pBuf, sz); +} + +ImportOutFile::~ImportOutFile() { + if (m_ownsFileAndBuffer) { + Flush(); + delete[] m_pBuf; + } + + delete m_pTrans; + delete m_pTransOut; + delete[] m_pTransBuf; +} + +bool ImportOutFile::Set8bitTranslator(nsImportTranslator* pTrans) { + if (!Flush()) return false; + + m_engaged = false; + m_pTrans = pTrans; + m_supports8to7 = pTrans->Supports8bitEncoding(); + + return true; +} + +bool ImportOutFile::End8bitTranslation(bool* pEngaged, nsCString& useCharset, + nsCString& encoding) { + if (!m_pTrans) return false; + + bool bResult = Flush(); + if (m_supports8to7 && m_pTransOut) { + if (bResult) bResult = m_pTrans->FinishConvertToFile(m_pTransOut); + if (bResult) bResult = Flush(); + } + + if (m_supports8to7) { + m_pTrans->GetCharset(useCharset); + m_pTrans->GetEncoding(encoding); + } else + useCharset.Truncate(); + *pEngaged = m_engaged; + delete m_pTrans; + m_pTrans = nullptr; + delete m_pTransOut; + m_pTransOut = nullptr; + delete[] m_pTransBuf; + m_pTransBuf = nullptr; + + return bResult; +} + +bool ImportOutFile::InitOutFile(nsIFile* pFile, uint32_t bufSz) { + if (!bufSz) bufSz = 32 * 1024; + if (!m_pBuf) m_pBuf = new uint8_t[bufSz]; + + if (!m_outputStream) { + nsresult rv; + rv = MsgNewBufferedFileOutputStream( + getter_AddRefs(m_outputStream), pFile, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0644); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("Couldn't create outfile\n"); + delete[] m_pBuf; + m_pBuf = nullptr; + return false; + } + } + m_pFile = pFile; + m_ownsFileAndBuffer = true; + m_pos = 0; + m_bufSz = bufSz; + return true; +} + +void ImportOutFile::InitOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz) { + m_ownsFileAndBuffer = false; + m_pFile = pFile; + m_pBuf = pBuf; + m_bufSz = sz; + m_pos = 0; +} + +bool ImportOutFile::Flush(void) { + if (!m_pos) return true; + + uint32_t transLen; + bool duddleyDoWrite = false; + + // handle translations if appropriate + if (m_pTrans) { + if (m_engaged && m_supports8to7) { + // Markers can get confused by this crap!!! + // TLR: FIXME: Need to update the markers based on + // the difference between the translated len and untranslated len + + if (!m_pTrans->ConvertToFile(m_pBuf, m_pos, m_pTransOut, &transLen)) + return false; + if (!m_pTransOut->Flush()) return false; + // now update our buffer... + if (transLen < m_pos) { + memcpy(m_pBuf, m_pBuf + transLen, m_pos - transLen); + } + m_pos -= transLen; + } else if (m_engaged) { + // does not actually support translation! + duddleyDoWrite = true; + } else { + // should we engage? + uint8_t* pChar = m_pBuf; + uint32_t len = m_pos; + while (len) { + if (!ImportCharSet::IsUSAscii(*pChar)) break; + pChar++; + len--; + } + if (len) { + m_engaged = true; + if (m_supports8to7) { + // allocate our translation output buffer and file... + m_pTransBuf = new uint8_t[m_bufSz]; + m_pTransOut = new ImportOutFile(m_pFile, m_pTransBuf, m_bufSz); + return Flush(); + } else + duddleyDoWrite = true; + } else { + duddleyDoWrite = true; + } + } + } else + duddleyDoWrite = true; + + if (duddleyDoWrite) { + uint32_t written = 0; + nsresult rv = + m_outputStream->Write((const char*)m_pBuf, (int32_t)m_pos, &written); + if (NS_FAILED(rv) || ((uint32_t)written != m_pos)) return false; + m_pos = 0; + } + + return true; +} + +bool ImportOutFile::WriteU8NullTerm(const uint8_t* pSrc, bool includeNull) { + while (*pSrc) { + if (m_pos >= m_bufSz) { + if (!Flush()) return false; + } + *(m_pBuf + m_pos) = *pSrc; + m_pos++; + pSrc++; + } + if (includeNull) { + if (m_pos >= m_bufSz) { + if (!Flush()) return false; + } + *(m_pBuf + m_pos) = 0; + m_pos++; + } + + return true; +} + +bool ImportOutFile::SetMarker(int markerID) { + if (!Flush()) { + return false; + } + + if (markerID < kMaxMarkers) { + int64_t pos = 0; + if (m_outputStream) { + // do we need to flush for the seek to give us the right pos? + m_outputStream->Flush(); + nsresult rv; + nsCOMPtr seekStream = + do_QueryInterface(m_outputStream, &rv); + NS_ENSURE_SUCCESS(rv, false); + rv = seekStream->Tell(&pos); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error, Tell failed on output stream\n"); + return false; + } + } + m_markers[markerID] = (uint32_t)pos + m_pos; + } + + return true; +} + +void ImportOutFile::ClearMarker(int markerID) { + if (markerID < kMaxMarkers) m_markers[markerID] = 0; +} + +bool ImportOutFile::WriteStrAtMarker(int markerID, const char* pStr) { + if (markerID >= kMaxMarkers) return false; + + if (!Flush()) return false; + int64_t pos; + m_outputStream->Flush(); + nsresult rv; + nsCOMPtr seekStream = + do_QueryInterface(m_outputStream, &rv); + NS_ENSURE_SUCCESS(rv, false); + rv = seekStream->Tell(&pos); + if (NS_FAILED(rv)) return false; + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, + (int32_t)m_markers[markerID]); + if (NS_FAILED(rv)) return false; + uint32_t written; + rv = m_outputStream->Write(pStr, strlen(pStr), &written); + if (NS_FAILED(rv)) return false; + + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, pos); + if (NS_FAILED(rv)) return false; + + return true; +} diff --git a/comm/mailnews/import/src/ImportOutFile.h b/comm/mailnews/import/src/ImportOutFile.h new file mode 100644 index 0000000000..f682c4dc4f --- /dev/null +++ b/comm/mailnews/import/src/ImportOutFile.h @@ -0,0 +1,94 @@ +/* -*- 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 ImportOutFile_h___ +#define ImportOutFile_h___ + +#include "nsImportTranslator.h" +#include "nsIOutputStream.h" +#include "nsIFile.h" + +#define kMaxMarkers 10 + +class ImportOutFile; + +class ImportOutFile { + public: + ImportOutFile(); + ImportOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz); + ~ImportOutFile(); + + bool InitOutFile(nsIFile* pFile, uint32_t bufSz = 4096); + void InitOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz); + inline bool WriteData(const uint8_t* pSrc, uint32_t len); + inline bool WriteByte(uint8_t byte); + bool WriteStr(const char* pStr) { + return WriteU8NullTerm((const uint8_t*)pStr, false); + } + bool WriteU8NullTerm(const uint8_t* pSrc, bool includeNull); + bool WriteEol(void) { return WriteStr("\x0D\x0A"); } + bool Done(void) { return Flush(); } + + // Marker support + bool SetMarker(int markerID); + void ClearMarker(int markerID); + bool WriteStrAtMarker(int markerID, const char* pStr); + + // 8-bit to 7-bit translation + bool Set8bitTranslator(nsImportTranslator* pTrans); + bool End8bitTranslation(bool* pEngaged, nsCString& useCharset, + nsCString& encoding); + + protected: + bool Flush(void); + + protected: + nsCOMPtr m_pFile; + nsCOMPtr m_outputStream; + uint8_t* m_pBuf; + uint32_t m_bufSz; + uint32_t m_pos; + bool m_ownsFileAndBuffer; + + // markers + uint32_t m_markers[kMaxMarkers]; + + // 8 bit to 7 bit translations + nsImportTranslator* m_pTrans; + bool m_engaged; + bool m_supports8to7; + ImportOutFile* m_pTransOut; + uint8_t* m_pTransBuf; +}; + +inline bool ImportOutFile::WriteData(const uint8_t* pSrc, uint32_t len) { + while ((len + m_pos) > m_bufSz) { + if ((m_bufSz - m_pos)) { + memcpy(m_pBuf + m_pos, pSrc, m_bufSz - m_pos); + len -= (m_bufSz - m_pos); + pSrc += (m_bufSz - m_pos); + m_pos = m_bufSz; + } + if (!Flush()) return false; + } + + if (len) { + memcpy(m_pBuf + m_pos, pSrc, len); + m_pos += len; + } + + return true; +} + +inline bool ImportOutFile::WriteByte(uint8_t byte) { + if (m_pos == m_bufSz) { + if (!Flush()) return false; + } + *(m_pBuf + m_pos) = byte; + m_pos++; + return true; +} + +#endif /* ImportOutFile_h__ */ diff --git a/comm/mailnews/import/src/ImportTranslate.cpp b/comm/mailnews/import/src/ImportTranslate.cpp new file mode 100644 index 0000000000..64fc09bce5 --- /dev/null +++ b/comm/mailnews/import/src/ImportTranslate.cpp @@ -0,0 +1,100 @@ +/* -*- 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/. */ + +#include "ImportTranslate.h" + +int ImportTranslate::m_useTranslator = -1; + +bool ImportTranslate::ConvertString(const nsCString& inStr, nsCString& outStr, + bool mimeHeader) { + if (inStr.IsEmpty()) { + outStr = inStr; + return true; + } + + nsImportTranslator* pTrans = GetTranslator(); + // int maxLen = (int) pTrans->GetMaxBufferSize(inStr.Length()); + // int hLen = 0; + nsCString set; + nsCString lang; + + if (mimeHeader) { + // add the charset and language + pTrans->GetCharset(set); + pTrans->GetLanguage(lang); + } + + // Unfortunately, we didn't implement ConvertBuffer for all translators, + // just ConvertToFile. This means that this data will not always + // be converted to the charset of pTrans. In that case... + // We don't always have the data in the same charset as the current + // translator... + // It is safer to leave the charset and language field blank + set.Truncate(); + lang.Truncate(); + + uint8_t* pBuf; + /* + pBuf = (P_U8) outStr.GetBuffer(maxLen); + if (!pBuf) { + delete pTrans; + return FALSE; + } + pTrans->ConvertBuffer((PC_U8)(PC_S8)inStr, inStr.GetLength(), pBuf); + outStr.ReleaseBuffer(); + */ + outStr = inStr; + delete pTrans; + + // Now I need to run the string through the mime-header special char + // encoder. + + pTrans = new CMHTranslator; + pBuf = new uint8_t[pTrans->GetMaxBufferSize(outStr.Length())]; + pTrans->ConvertBuffer((const uint8_t*)(outStr.get()), outStr.Length(), pBuf); + delete pTrans; + outStr.Truncate(); + if (mimeHeader) { + outStr = set; + outStr += "'"; + outStr += lang; + outStr += "'"; + } + outStr += (const char*)pBuf; + delete[] pBuf; + + return true; +} + +nsImportTranslator* ImportTranslate::GetTranslator(void) { + if (m_useTranslator == -1) { + // get the translator to use... + // CString trans; + // trans.LoadString(IDS_LANGUAGE_TRANSLATION); + m_useTranslator = 0; + // if (!trans.CompareNoCase("iso-2022-jp")) + // gWizData.m_useTranslator = 1; + } + + switch (m_useTranslator) { + case 0: + return new nsImportTranslator; + // case 1: + // return new CSJis2JisTranslator; + default: + return new nsImportTranslator; + } +} + +nsImportTranslator* ImportTranslate::GetMatchingTranslator( + const char* pCharSet) { + /* + CString jp = "iso-2022-jp"; + if (!jp.CompareNoCase(pCharSet)) + return new CSJis2JisTranslator; + */ + + return nullptr; +} diff --git a/comm/mailnews/import/src/ImportTranslate.h b/comm/mailnews/import/src/ImportTranslate.h new file mode 100644 index 0000000000..5e31207395 --- /dev/null +++ b/comm/mailnews/import/src/ImportTranslate.h @@ -0,0 +1,23 @@ +/* -*- 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 ImportTranslate_h___ +#define ImportTranslate_h___ + +#include "nsString.h" +#include "nsImportTranslator.h" + +class ImportTranslate { + public: + static bool ConvertString(const nsCString& inStr, nsCString& outStr, + bool mimeHeader); + static nsImportTranslator* GetTranslator(void); + static nsImportTranslator* GetMatchingTranslator(const char* pCharSet); + + protected: + static int m_useTranslator; +}; + +#endif /* ImportTranslate_h__ */ diff --git a/comm/mailnews/import/src/MapiApi.cpp b/comm/mailnews/import/src/MapiApi.cpp new file mode 100644 index 0000000000..5490b6bc11 --- /dev/null +++ b/comm/mailnews/import/src/MapiApi.cpp @@ -0,0 +1,1842 @@ +/* -*- 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/. */ + +#include "MapiDbgLog.h" +#include "MapiApi.h" + +#include +#include "rtfMailDecoder.h" + +#include "prprf.h" +#include "nsMemory.h" +#include "nsMsgUtils.h" +#include "nsUnicharUtils.h" +#include "nsNativeCharsetUtils.h" + +int CMapiApi::m_clients = 0; +BOOL CMapiApi::m_initialized = false; +nsTArray* CMapiApi::m_pStores = NULL; +LPMAPISESSION CMapiApi::m_lpSession = NULL; +LPMDB CMapiApi::m_lpMdb = NULL; +HRESULT CMapiApi::m_lastError; +/* +Type: 1, name: Calendar, class: IPF.Appointment +Type: 1, name: Contacts, class: IPF.Contact +Type: 1, name: Journal, class: IPF.Journal +Type: 1, name: Notes, class: IPF.StickyNote +Type: 1, name: Tasks, class: IPF.Task +Type: 1, name: Drafts, class: IPF.Note +*/ + +HINSTANCE CMapiApi::m_hMapi32 = NULL; + +LPMAPIUNINITIALIZE gpMapiUninitialize = NULL; +LPMAPIINITIALIZE gpMapiInitialize = NULL; +LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer = NULL; +LPMAPIFREEBUFFER gpMapiFreeBuffer = NULL; +LPMAPILOGONEX gpMapiLogonEx = NULL; +LPOPENSTREAMONFILE gpMapiOpenStreamOnFile = NULL; + +typedef HRESULT(STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAM)( + LPSTREAM lpCompressedRTFStream, ULONG ulFlags, + LPSTREAM FAR* lpUncompressedRTFStream); +typedef WRAPCOMPRESSEDRTFSTREAM* LPWRAPCOMPRESSEDRTFSTREAM; +LPWRAPCOMPRESSEDRTFSTREAM gpWrapCompressedRTFStream = NULL; + +// WrapCompressedRTFStreamEx related stuff - see +// http://support.microsoft.com/kb/839560 +typedef struct { + ULONG size; + ULONG ulFlags; + ULONG ulInCodePage; + ULONG ulOutCodePage; +} RTF_WCSINFO; +typedef struct { + ULONG size; + ULONG ulStreamFlags; +} RTF_WCSRETINFO; + +typedef HRESULT(STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAMEX)( + LPSTREAM lpCompressedRTFStream, CONST RTF_WCSINFO* pWCSInfo, + LPSTREAM* lppUncompressedRTFStream, RTF_WCSRETINFO* pRetInfo); +typedef WRAPCOMPRESSEDRTFSTREAMEX* LPWRAPCOMPRESSEDRTFSTREAMEX; +LPWRAPCOMPRESSEDRTFSTREAMEX gpWrapCompressedRTFStreamEx = NULL; + +BOOL CMapiApi::LoadMapiEntryPoints(void) { + if (!(gpMapiUninitialize = + (LPMAPIUNINITIALIZE)GetProcAddress(m_hMapi32, "MAPIUninitialize"))) + return FALSE; + if (!(gpMapiInitialize = + (LPMAPIINITIALIZE)GetProcAddress(m_hMapi32, "MAPIInitialize"))) + return FALSE; + if (!(gpMapiAllocateBuffer = (LPMAPIALLOCATEBUFFER)GetProcAddress( + m_hMapi32, "MAPIAllocateBuffer"))) + return FALSE; + if (!(gpMapiFreeBuffer = + (LPMAPIFREEBUFFER)GetProcAddress(m_hMapi32, "MAPIFreeBuffer"))) + return FALSE; + if (!(gpMapiLogonEx = + (LPMAPILOGONEX)GetProcAddress(m_hMapi32, "MAPILogonEx"))) + return FALSE; + if (!(gpMapiOpenStreamOnFile = + (LPOPENSTREAMONFILE)GetProcAddress(m_hMapi32, "OpenStreamOnFile"))) + return FALSE; + + // Available from the Outlook 2002 post-SP3 hotfix + // (http://support.microsoft.com/kb/883924/) Exported by msmapi32.dll; so it's + // unavailable to us using mapi32.dll + gpWrapCompressedRTFStreamEx = (LPWRAPCOMPRESSEDRTFSTREAMEX)GetProcAddress( + m_hMapi32, "WrapCompressedRTFStreamEx"); + // Available always + gpWrapCompressedRTFStream = (LPWRAPCOMPRESSEDRTFSTREAM)GetProcAddress( + m_hMapi32, "WrapCompressedRTFStream"); + + return TRUE; +} + +// Gets the PR_RTF_COMPRESSED tag property +// Codepage is used only if the WrapCompressedRTFStreamEx is available +BOOL CMapiApi::GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val, + unsigned long& nativeBodyType, + unsigned long codepage) { + if (!m_hMapi32 || !(gpWrapCompressedRTFStreamEx || gpWrapCompressedRTFStream)) + return FALSE; // Fallback to the default processing + + LPSTREAM icstream = 0; // for the compressed stream + LPSTREAM iunstream = 0; // for the uncompressed stream + HRESULT hr = + pProp->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, + STGM_READ | STGM_DIRECT, 0, (LPUNKNOWN*)&icstream); + if (HR_FAILED(hr)) return FALSE; + + if (gpWrapCompressedRTFStreamEx) { // Impossible - we use mapi32.dll! + RTF_WCSINFO wcsinfo = {0}; + RTF_WCSRETINFO retinfo = {0}; + + retinfo.size = sizeof(RTF_WCSRETINFO); + + wcsinfo.size = sizeof(RTF_WCSINFO); + wcsinfo.ulFlags = MAPI_NATIVE_BODY; + wcsinfo.ulInCodePage = codepage; + wcsinfo.ulOutCodePage = CP_UTF8; + + if (HR_SUCCEEDED(hr = gpWrapCompressedRTFStreamEx(icstream, &wcsinfo, + &iunstream, &retinfo))) + nativeBodyType = retinfo.ulStreamFlags; + } else { // mapi32.dll + gpWrapCompressedRTFStream(icstream, 0, &iunstream); + } + icstream->Release(); + + if (iunstream) { // Succeeded + std::string streamData; + // Stream.Stat doesn't work for this stream! + bool done = false; + while (!done) { + // I think 10K is a good guess to minimize the number of reads while + // keeping memory usage low + const int bufsize = 10240; + char buf[bufsize]; + ULONG read; + hr = iunstream->Read(buf, bufsize, &read); + done = (read < bufsize) || (hr != S_OK); + if (read) streamData.append(buf, read); + } + iunstream->Release(); + // if rtf -> convert to plain text. + if (!gpWrapCompressedRTFStreamEx || + (nativeBodyType == MAPI_NATIVE_BODY_TYPE_RTF)) { + std::stringstream s(streamData); + CRTFMailDecoder decoder; + DecodeRTF(s, decoder); + if (decoder.mode() == CRTFMailDecoder::mHTML) + nativeBodyType = MAPI_NATIVE_BODY_TYPE_HTML; + else if (decoder.mode() == CRTFMailDecoder::mText) + nativeBodyType = MAPI_NATIVE_BODY_TYPE_PLAINTEXT; + else + nativeBodyType = MAPI_NATIVE_BODY_TYPE_RTF; + val.Assign(decoder.text(), decoder.textSize()); + } else { // WrapCompressedRTFStreamEx available and original type is not + // rtf + CopyUTF8toUTF16(nsDependentCString(streamData.c_str()), val); + } + return TRUE; + } + return FALSE; +} + +void CMapiApi::MAPIUninitialize(void) { + if (m_hMapi32 && gpMapiUninitialize) (*gpMapiUninitialize)(); +} + +HRESULT CMapiApi::MAPIInitialize(LPVOID lpInit) { + return (m_hMapi32 && gpMapiInitialize) ? (*gpMapiInitialize)(lpInit) + : MAPI_E_NOT_INITIALIZED; +} + +SCODE CMapiApi::MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR* lppBuffer) { + return (m_hMapi32 && gpMapiAllocateBuffer) + ? (*gpMapiAllocateBuffer)(cbSize, lppBuffer) + : MAPI_E_NOT_INITIALIZED; +} + +ULONG CMapiApi::MAPIFreeBuffer(LPVOID lpBuff) { + return (m_hMapi32 && gpMapiFreeBuffer) ? (*gpMapiFreeBuffer)(lpBuff) + : MAPI_E_NOT_INITIALIZED; +} + +HRESULT CMapiApi::MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName, + LPTSTR lpszPassword, FLAGS flFlags, + LPMAPISESSION FAR* lppSession) { + return (m_hMapi32 && gpMapiLogonEx) + ? (*gpMapiLogonEx)(ulUIParam, lpszProfileName, lpszPassword, + flFlags, lppSession) + : MAPI_E_NOT_INITIALIZED; +} + +HRESULT CMapiApi::OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer, + LPFREEBUFFER lpFreeBuffer, ULONG ulFlags, + LPCTSTR lpszFileName, LPTSTR lpszPrefix, + LPSTREAM FAR* lppStream) { + return (m_hMapi32 && gpMapiOpenStreamOnFile) + ? (*gpMapiOpenStreamOnFile)(lpAllocateBuffer, lpFreeBuffer, + ulFlags, lpszFileName, lpszPrefix, + lppStream) + : MAPI_E_NOT_INITIALIZED; +} + +void CMapiApi::FreeProws(LPSRowSet prows) { + ULONG irow; + if (!prows) return; + for (irow = 0; irow < prows->cRows; ++irow) + MAPIFreeBuffer(prows->aRow[irow].lpProps); + MAPIFreeBuffer(prows); +} + +BOOL CMapiApi::LoadMapi(void) { + if (m_hMapi32) return TRUE; + + HINSTANCE hInst = ::LoadLibraryW(L"MAPI32.DLL"); + if (!hInst) return FALSE; + FARPROC pProc = GetProcAddress(hInst, "MAPIGetNetscapeVersion"); + if (pProc) { + ::FreeLibrary(hInst); + hInst = ::LoadLibraryW(L"MAPI32BAK.DLL"); + if (!hInst) return FALSE; + } + + m_hMapi32 = hInst; + return LoadMapiEntryPoints(); +} + +void CMapiApi::UnloadMapi(void) { + if (m_hMapi32) ::FreeLibrary(m_hMapi32); + m_hMapi32 = NULL; +} + +CMapiApi::CMapiApi() { + m_clients++; + LoadMapi(); + if (!m_pStores) m_pStores = new nsTArray(); +} + +CMapiApi::~CMapiApi() { + m_clients--; + if (!m_clients) { + HRESULT hr; + + ClearMessageStores(); + delete m_pStores; + m_pStores = NULL; + + m_lpMdb = NULL; + + if (m_lpSession) { + hr = m_lpSession->Logoff(NULL, 0, 0); + if (FAILED(hr)) { + MAPI_TRACE2("Logoff failed: 0x%lx, %d\n", (long)hr, (int)hr); + } + m_lpSession->Release(); + m_lpSession = NULL; + } + + if (m_initialized) { + MAPIUninitialize(); + m_initialized = FALSE; + } + + UnloadMapi(); + } +} + +void CMapiApi::CStrToUnicode(const char* pStr, nsString& result) { + NS_CopyNativeToUnicode(nsDependentCString(pStr), result); +} + +BOOL CMapiApi::Initialize(void) { + if (m_initialized) return TRUE; + + HRESULT hr; + + hr = MAPIInitialize(NULL); + + if (FAILED(hr)) { + MAPI_TRACE2("MAPI Initialize failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + m_initialized = TRUE; + MAPI_TRACE0("MAPI Initialized\n"); + + return TRUE; +} + +BOOL CMapiApi::LogOn(void) { + if (!m_initialized) { + MAPI_TRACE0("Tried to LogOn before initializing MAPI\n"); + return FALSE; + } + + if (m_lpSession) return TRUE; + + HRESULT hr; + + hr = MAPILogonEx( + 0, // might need to be passed in HWND + NULL, // profile name, 64 char max (LPTSTR) + NULL, // profile password, 64 char max (LPTSTR) + // MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI | + // MAPI_EXPLICIT_PROFILE, MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI, + // MAPI_NO_MAIL | MAPI_LOGON_UI, + MAPI_NO_MAIL | MAPI_USE_DEFAULT | MAPI_EXTENDED | MAPI_NEW_SESSION, + &m_lpSession); + + if (FAILED(hr)) { + m_lpSession = NULL; + MAPI_TRACE2("LogOn failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + MAPI_TRACE0("MAPI Logged on\n"); + return TRUE; +} + +class CGetStoreFoldersIter : public CMapiHierarchyIter { + public: + CGetStoreFoldersIter(CMapiApi* pApi, CMapiFolderList& folders, int depth, + BOOL isMail = TRUE); + + virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry); + + protected: + BOOL ExcludeFolderClass(const char16_t* pName); + + BOOL m_isMail; + CMapiApi* m_pApi; + CMapiFolderList* m_pList; + int m_depth; +}; + +CGetStoreFoldersIter::CGetStoreFoldersIter(CMapiApi* pApi, + CMapiFolderList& folders, int depth, + BOOL isMail) { + m_pApi = pApi; + m_pList = &folders; + m_depth = depth; + m_isMail = isMail; +} + +BOOL CGetStoreFoldersIter::ExcludeFolderClass(const char16_t* pName) { + BOOL bResult; + nsDependentString pNameStr(pName); + if (m_isMail) { + bResult = FALSE; + if (pNameStr.EqualsLiteral("IPF.Appointment")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.Contact")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.Journal")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.StickyNote")) + bResult = TRUE; + else if (pNameStr.EqualsLiteral("IPF.Task")) + bResult = TRUE; + // Skip IMAP folders + else if (pNameStr.EqualsLiteral("IPF.Imap")) + bResult = TRUE; + // else if (!stricmp(pName, "IPF.Note")) + // bResult = TRUE; + } else { + bResult = TRUE; + if (pNameStr.EqualsLiteral("IPF.Contact")) bResult = FALSE; + } + + return bResult; +} + +BOOL CGetStoreFoldersIter::HandleHierarchyItem(ULONG oType, ULONG cb, + LPENTRYID pEntry) { + if (oType == MAPI_FOLDER) { + LPMAPIFOLDER pFolder; + if (m_pApi->OpenEntry(cb, pEntry, (LPUNKNOWN*)&pFolder)) { + LPSPropValue pVal; + nsString name; + + pVal = m_pApi->GetMapiProperty(pFolder, PR_CONTAINER_CLASS); + if (pVal) + m_pApi->GetStringFromProp(pVal, name); + else + name.Truncate(); + + if ((name.IsEmpty() && m_isMail) || (!ExcludeFolderClass(name.get()))) { + pVal = m_pApi->GetMapiProperty(pFolder, PR_DISPLAY_NAME); + m_pApi->GetStringFromProp(pVal, name); + CMapiFolder* pNewFolder = + new CMapiFolder(name.get(), cb, pEntry, m_depth); + m_pList->AddItem(pNewFolder); + + pVal = m_pApi->GetMapiProperty(pFolder, PR_FOLDER_TYPE); + MAPI_TRACE2("Type: %d, name: %s\n", m_pApi->GetLongFromProp(pVal), + name.get()); + // m_pApi->ListProperties(pFolder); + + CGetStoreFoldersIter nextIter(m_pApi, *m_pList, m_depth + 1, m_isMail); + m_pApi->IterateHierarchy(&nextIter, pFolder); + } + pFolder->Release(); + } else { + MAPI_TRACE0( + "GetStoreFolders - HandleHierarchyItem: Error opening folder " + "entry.\n"); + return FALSE; + } + } else + MAPI_TRACE1( + "GetStoreFolders - HandleHierarchyItem: Unhandled ObjectType: %ld\n", + oType); + return TRUE; +} + +BOOL CMapiApi::GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, + CMapiFolderList& folders, int startDepth) { + // Fill in the array with the folders in the given store + if (!m_initialized || !m_lpSession) { + MAPI_TRACE0("MAPI not initialized for GetStoreFolders\n"); + return FALSE; + } + + m_lpMdb = NULL; + + CMsgStore* pStore = FindMessageStore(cbEid, lpEid); + BOOL bResult = FALSE; + LPSPropValue pVal; + + if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) { + // Successful open, do the iteration of the store + pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID); + if (pVal) { + ULONG cbEntry; + LPENTRYID pEntry; + LPMAPIFOLDER lpSubTree = NULL; + + if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) { + // Open up the folder! + bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN*)&lpSubTree); + MAPIFreeBuffer(pEntry); + if (bResult && lpSubTree) { + // Iterate the subtree with the results going into the folder list + CGetStoreFoldersIter iterHandler(this, folders, startDepth); + bResult = IterateHierarchy(&iterHandler, lpSubTree); + lpSubTree->Release(); + } else { + MAPI_TRACE0("GetStoreFolders: Error opening sub tree.\n"); + } + } else { + MAPI_TRACE0( + "GetStoreFolders: Error getting entryID from sub tree property " + "val.\n"); + } + } else { + MAPI_TRACE0("GetStoreFolders: Error getting sub tree property.\n"); + } + } else { + MAPI_TRACE0("GetStoreFolders: Error opening message store.\n"); + } + + return bResult; +} + +BOOL CMapiApi::GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid, + CMapiFolderList& folders) { + // Fill in the array with the folders in the given store + if (!m_initialized || !m_lpSession) { + MAPI_TRACE0("MAPI not initialized for GetStoreAddressFolders\n"); + return FALSE; + } + + m_lpMdb = NULL; + + CMsgStore* pStore = FindMessageStore(cbEid, lpEid); + BOOL bResult = FALSE; + LPSPropValue pVal; + + if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) { + // Successful open, do the iteration of the store + pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID); + if (pVal) { + ULONG cbEntry; + LPENTRYID pEntry; + LPMAPIFOLDER lpSubTree = NULL; + + if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) { + // Open up the folder! + bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN*)&lpSubTree); + MAPIFreeBuffer(pEntry); + if (bResult && lpSubTree) { + // Iterate the subtree with the results going into the folder list + CGetStoreFoldersIter iterHandler(this, folders, 1, FALSE); + bResult = IterateHierarchy(&iterHandler, lpSubTree); + lpSubTree->Release(); + } else { + MAPI_TRACE0("GetStoreAddressFolders: Error opening sub tree.\n"); + } + } else { + MAPI_TRACE0( + "GetStoreAddressFolders: Error getting entryID from sub tree " + "property val.\n"); + } + } else { + MAPI_TRACE0("GetStoreAddressFolders: Error getting sub tree property.\n"); + } + } else + MAPI_TRACE0("GetStoreAddressFolders: Error opening message store.\n"); + + return bResult; +} + +BOOL CMapiApi::OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB* ppMdb) { + if (!m_lpSession) { + MAPI_TRACE0("OpenStore called before a session was opened\n"); + return FALSE; + } + + CMsgStore* pStore = FindMessageStore(cbEid, lpEid); + if (pStore && pStore->Open(m_lpSession, ppMdb)) return TRUE; + return FALSE; +} + +BOOL CMapiApi::OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN* ppOpen) { + if (!m_lpMdb) { + MAPI_TRACE0("OpenEntry called before the message store is open\n"); + return FALSE; + } + + return OpenMdbEntry(m_lpMdb, cbEntry, pEntryId, ppOpen); +} + +BOOL CMapiApi::OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId, + LPUNKNOWN* ppOpen) { + ULONG ulObjType; + HRESULT hr; + hr = m_lpSession->OpenEntry(cbEntry, pEntryId, NULL, 0, &ulObjType, + (LPUNKNOWN*)ppOpen); + if (FAILED(hr)) { + MAPI_TRACE2("OpenMdbEntry failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + return TRUE; +} + +enum { ieidPR_ENTRYID = 0, ieidPR_OBJECT_TYPE, ieidMax }; + +static const SizedSPropTagArray(ieidMax, ptaEid) = {ieidMax, + { + PR_ENTRYID, + PR_OBJECT_TYPE, + }}; + +BOOL CMapiApi::IterateContents(CMapiContentIter* pIter, LPMAPIFOLDER pFolder, + ULONG flags) { + // flags can be 0 or MAPI_ASSOCIATED + // MAPI_ASSOCIATED is usually used for forms and views + + HRESULT hr; + LPMAPITABLE lpTable; + hr = pFolder->GetContentsTable(flags, &lpTable); + if (FAILED(hr)) { + MAPI_TRACE2("GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + if (!rowCount) { + MAPI_TRACE0(" Empty Table\n"); + } + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr); + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRow; + BOOL keepGoing = TRUE; + BOOL bResult = TRUE; + do { + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + if (HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = FALSE; + break; + } + + if (lpRow) { + cNumRows = lpRow->cRows; + if (cNumRows) { + LPENTRYID lpEID = + (LPENTRYID)lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul; + keepGoing = HandleContentsItem(oType, cbEID, lpEID); + MAPI_TRACE1(" ObjectType: %ld\n", oType); + } + FreeProws(lpRow); + } + + } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing); + + lpTable->Release(); + return bResult; +} + +BOOL CMapiApi::HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry) { + if (oType == MAPI_MESSAGE) { + LPMESSAGE pMsg; + if (OpenEntry(cb, pEntry, (LPUNKNOWN*)&pMsg)) { + LPSPropValue pVal; + pVal = GetMapiProperty(pMsg, PR_SUBJECT); + ReportStringProp("PR_SUBJECT:", pVal); + pVal = GetMapiProperty(pMsg, PR_DISPLAY_BCC); + ReportStringProp("PR_DISPLAY_BCC:", pVal); + pVal = GetMapiProperty(pMsg, PR_DISPLAY_CC); + ReportStringProp("PR_DISPLAY_CC:", pVal); + pVal = GetMapiProperty(pMsg, PR_DISPLAY_TO); + ReportStringProp("PR_DISPLAY_TO:", pVal); + pVal = GetMapiProperty(pMsg, PR_MESSAGE_CLASS); + ReportStringProp("PR_MESSAGE_CLASS:", pVal); + ListProperties(pMsg); + pMsg->Release(); + } else { + MAPI_TRACE0(" Folder type - error opening\n"); + } + } else + MAPI_TRACE1(" ObjectType: %ld\n", oType); + + return TRUE; +} + +void CMapiApi::ListProperties(LPMAPIPROP lpProp, BOOL getValues) { + LPSPropTagArray pArray; + HRESULT hr = lpProp->GetPropList(0, &pArray); + if (FAILED(hr)) { + MAPI_TRACE0(" Unable to retrieve property list\n"); + return; + } + ULONG count = 0; + LPMAPINAMEID FAR* lppPropNames; + SPropTagArray tagArray; + LPSPropTagArray lpTagArray = &tagArray; + tagArray.cValues = (ULONG)1; + nsCString desc; + for (ULONG i = 0; i < pArray->cValues; i++) { + GetPropTagName(pArray->aulPropTag[i], desc); + if (getValues) { + tagArray.aulPropTag[0] = pArray->aulPropTag[i]; + hr = lpProp->GetNamesFromIDs(&lpTagArray, nullptr, 0, &count, + &lppPropNames); + if (hr == S_OK) MAPIFreeBuffer(lppPropNames); + + LPSPropValue pVal = GetMapiProperty(lpProp, pArray->aulPropTag[i]); + if (pVal) { + desc += ", "; + ListPropertyValue(pVal, desc); + MAPIFreeBuffer(pVal); + } + } + MAPI_TRACE2(" Tag #%d: %s\n", (int)i, desc.get()); + } + + MAPIFreeBuffer(pArray); +} + +ULONG CMapiApi::GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID) { + static GUID emailGUID = {0x00062004, + 0x0000, + 0x0000, + {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; + + MAPINAMEID mapiNameID; + mapiNameID.lpguid = &emailGUID; + mapiNameID.ulKind = MNID_ID; + mapiNameID.Kind.lID = nameID; + + LPMAPINAMEID lpMapiNames = &mapiNameID; + LPSPropTagArray lpMailTagArray = nullptr; + + HRESULT result = + lpProp->GetIDsFromNames(1L, &lpMapiNames, 0, &lpMailTagArray); + if (result == S_OK) { + ULONG lTag = lpMailTagArray->aulPropTag[0]; + MAPIFreeBuffer(lpMailTagArray); + return lTag; + } else + return 0L; +} + +BOOL CMapiApi::HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) { + if (oType == MAPI_FOLDER) { + LPMAPIFOLDER pFolder; + if (OpenEntry(cb, pEntry, (LPUNKNOWN*)&pFolder)) { + LPSPropValue pVal; + pVal = GetMapiProperty(pFolder, PR_DISPLAY_NAME); + ReportStringProp("Folder name:", pVal); + IterateContents(NULL, pFolder); + IterateHierarchy(NULL, pFolder); + pFolder->Release(); + } else { + MAPI_TRACE0(" Folder type - error opening\n"); + } + } else + MAPI_TRACE1(" ObjectType: %ld\n", oType); + + return TRUE; +} + +BOOL CMapiApi::IterateHierarchy(CMapiHierarchyIter* pIter, LPMAPIFOLDER pFolder, + ULONG flags) { + // flags can be CONVENIENT_DEPTH or 0 + // CONVENIENT_DEPTH will return all depths I believe instead + // of just children + HRESULT hr; + LPMAPITABLE lpTable; + hr = pFolder->GetHierarchyTable(flags, &lpTable); + if (HR_FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE2("IterateHierarchy: GetContentsTable failed: 0x%lx, %d\n", + (long)hr, (int)hr); + return FALSE; + } + + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + if (!rowCount) { + lpTable->Release(); + return TRUE; + } + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (HR_FAILED(hr)) { + m_lastError = hr; + lpTable->Release(); + MAPI_TRACE2("IterateHierarchy: SetColumns failed: 0x%lx, %d\n", (long)hr, + (int)hr); + return FALSE; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (HR_FAILED(hr)) { + m_lastError = hr; + lpTable->Release(); + MAPI_TRACE2("IterateHierarchy: SeekRow failed: 0x%lx, %d\n", (long)hr, + (int)hr); + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRow; + BOOL keepGoing = TRUE; + BOOL bResult = TRUE; + do { + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + + if (HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + bResult = FALSE; + break; + } + + if (lpRow) { + cNumRows = lpRow->cRows; + + if (cNumRows) { + LPENTRYID lpEntry = + (LPENTRYID)lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cb = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul; + + if (pIter) + keepGoing = pIter->HandleHierarchyItem(oType, cb, lpEntry); + else + keepGoing = HandleHierarchyItem(oType, cb, lpEntry); + } + FreeProws(lpRow); + } + } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing); + + lpTable->Release(); + + if (bResult && !keepGoing) bResult = FALSE; + + return bResult; +} + +enum { itblPR_DISPLAY_NAME, itblPR_ENTRYID, itblMax }; + +static const SizedSPropTagArray(itblMax, ptaTbl) = {itblMax, + { + PR_DISPLAY_NAME, + PR_ENTRYID, + }}; + +BOOL CMapiApi::IterateStores(CMapiFolderList& stores) { + stores.ClearAll(); + + if (!m_lpSession) { + MAPI_TRACE0("IterateStores called before session is open\n"); + m_lastError = E_UNEXPECTED; + return FALSE; + } + + HRESULT hr; + + /* -- Some Microsoft sample code just to see if things are working --- */ /* + + ULONG cbEIDStore; + LPENTRYID lpEIDStore; + + hr = HrMAPIFindDefaultMsgStore(m_lpSession, &cbEIDStore, &lpEIDStore); + if (HR_FAILED(hr)) { + MAPI_TRACE0("Default message store not found\n"); + // MessageBoxW(NULL, L"Message Store Not Found", NULL, MB_OK); + } + else { + LPMDB lpStore; + MAPI_TRACE0("Default Message store FOUND\n"); + hr = m_lpSession->OpenMsgStore(NULL, cbEIDStore, + lpEIDStore, NULL, + MDB_NO_MAIL | MDB_NO_DIALOG, &lpStore); + if (HR_FAILED(hr)) { + MAPI_TRACE1("Unable to open default message store: 0x%lx\n", hr); + } + else { + MAPI_TRACE0("Default message store OPENED\n"); + lpStore->Release(); + } + } + */ + + LPMAPITABLE lpTable; + + hr = m_lpSession->GetMsgStoresTable(0, &lpTable); + if (FAILED(hr)) { + MAPI_TRACE0("GetMsgStoresTable failed\n"); + m_lastError = hr; + return FALSE; + } + + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + MAPI_TRACE1("MsgStores Table rowCount: %ld\n", rowCount); + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaTbl, 0); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + return FALSE; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + lpTable->Release(); + MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + return FALSE; + } + + int cNumRows = 0; + LPSRowSet lpRow; + BOOL keepGoing = TRUE; + BOOL bResult = TRUE; + do { + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + + if (HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = FALSE; + m_lastError = hr; + break; + } + + if (lpRow) { + cNumRows = lpRow->cRows; + + if (cNumRows) { + LPCTSTR lpStr = + (LPCTSTR)lpRow->aRow[0].lpProps[itblPR_DISPLAY_NAME].Value.LPSZ; + LPENTRYID lpEID = + (LPENTRYID)lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.cb; + + // In the future, GetStoreInfo needs to somehow return + // whether or not the store is from an IMAP server. + // Currently, GetStoreInfo opens the store and attempts + // to get the hierarchy tree. If the tree is empty or + // does not exist, then szContents will be zero. We'll + // assume that any store that doesn't have anything in + // it's hierarchy tree is not a store we want to import - + // there would be nothing to import from anyway! + // Currently, this does exclude IMAP server accounts + // which is the desired behaviour. + + int strLen = strlen(lpStr); + char16_t* pwszStr = + (char16_t*)moz_xmalloc((strLen + 1) * sizeof(WCHAR)); + if (!pwszStr) { + // out of memory + FreeProws(lpRow); + lpTable->Release(); + return FALSE; + } + ::MultiByteToWideChar(CP_ACP, 0, lpStr, strlen(lpStr) + 1, + reinterpret_cast(pwszStr), + (strLen + 1) * sizeof(WCHAR)); + CMapiFolder* pFolder = + new CMapiFolder(pwszStr, cbEID, lpEID, 0, MAPI_STORE); + free(pwszStr); + + long szContents = 1; + GetStoreInfo(pFolder, &szContents); + + MAPI_TRACE1(" DisplayName: %s\n", lpStr); + if (szContents) + stores.AddItem(pFolder); + else { + delete pFolder; + MAPI_TRACE0(" ^^^^^ Not added to store list\n"); + } + + keepGoing = TRUE; + } + FreeProws(lpRow); + } + } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing); + + lpTable->Release(); + + return bResult; +} + +void CMapiApi::GetStoreInfo(CMapiFolder* pFolder, long* pSzContents) { + HRESULT hr; + LPMDB lpMdb; + + if (pSzContents) *pSzContents = 0; + + if (!OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), &lpMdb)) + return; + + LPSPropValue pVal; + /* + pVal = GetMapiProperty(lpMdb, PR_DISPLAY_NAME); + ReportStringProp(" Message store name:", pVal); + pVal = GetMapiProperty(lpMdb, PR_MDB_PROVIDER); + ReportUIDProp(" Message store provider:", pVal); + pVal = GetMapiProperty(lpMdb, PR_COMMENT); + ReportStringProp(" Message comment:", pVal); + pVal = GetMapiProperty(lpMdb, PR_ACCESS_LEVEL); + ReportLongProp(" Message store Access Level:", pVal); + pVal = GetMapiProperty(lpMdb, PR_STORE_SUPPORT_MASK); + ReportLongProp(" Message store support mask:", pVal); + pVal = GetMapiProperty(lpMdb, PR_STORE_STATE); + ReportLongProp(" Message store state:", pVal); + pVal = GetMapiProperty(lpMdb, PR_OBJECT_TYPE); + ReportLongProp(" Message store object type:", pVal); + pVal = GetMapiProperty(lpMdb, PR_VALID_FOLDER_MASK); + ReportLongProp(" Message store valid folder mask:", pVal); + + pVal = GetMapiProperty(lpMdb, 0x8001001e); + ReportStringProp(" Message prop 0x8001001e:", pVal); + + // This key appears to be the OMI Account Manager account that corresponds + // to this message store. This is important for IMAP accounts + // since we may not want to import messages from an IMAP store! + // Seems silly if you ask me! + // In order to test this, we'll need the registry key to look under to + determine + // if it contains the "IMAP Server" value, if it does then we are an + // IMAP store, if not, then we are a non-IMAP store - which may always mean + // a regular store that should be imported. + + pVal = GetMapiProperty(lpMdb, 0x80000003); + ReportLongProp(" Message prop 0x80000003:", pVal); + + // ListProperties(lpMdb); + */ + + pVal = GetMapiProperty(lpMdb, PR_IPM_SUBTREE_ENTRYID); + if (pVal) { + ULONG cbEntry; + LPENTRYID pEntry; + LPMAPIFOLDER lpSubTree = NULL; + + if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) { + // Open up the folder! + ULONG ulObjType; + hr = lpMdb->OpenEntry(cbEntry, pEntry, NULL, 0, &ulObjType, + (LPUNKNOWN*)&lpSubTree); + MAPIFreeBuffer(pEntry); + if (SUCCEEDED(hr) && lpSubTree) { + // Find out if there are any contents in the + // tree. + LPMAPITABLE lpTable; + hr = lpSubTree->GetHierarchyTable(0, &lpTable); + if (HR_FAILED(hr)) { + MAPI_TRACE2("GetStoreInfo: GetHierarchyTable failed: 0x%lx, %d\n", + (long)hr, (int)hr); + } else { + ULONG rowCount; + hr = lpTable->GetRowCount(0, &rowCount); + lpTable->Release(); + if (SUCCEEDED(hr) && pSzContents) *pSzContents = (long)rowCount; + } + + lpSubTree->Release(); + } + } + } +} + +void CMapiApi::ClearMessageStores(void) { + if (m_pStores) { + CMsgStore* pStore; + for (size_t i = 0; i < m_pStores->Length(); i++) { + pStore = m_pStores->ElementAt(i); + delete pStore; + } + m_pStores->Clear(); + } +} + +void CMapiApi::AddMessageStore(CMsgStore* pStore) { + if (m_pStores) m_pStores->AppendElement(pStore); +} + +CMsgStore* CMapiApi::FindMessageStore(ULONG cbEid, LPENTRYID lpEid) { + if (!m_lpSession) { + MAPI_TRACE0("FindMessageStore called before session is open\n"); + m_lastError = E_UNEXPECTED; + return NULL; + } + + ULONG result; + HRESULT hr; + CMsgStore* pStore; + for (size_t i = 0; i < m_pStores->Length(); i++) { + pStore = m_pStores->ElementAt(i); + hr = m_lpSession->CompareEntryIDs(cbEid, lpEid, pStore->GetCBEntryID(), + pStore->GetLPEntryID(), 0, &result); + if (HR_FAILED(hr)) { + MAPI_TRACE2("CompareEntryIDs failed: 0x%lx, %d\n", (long)hr, (int)hr); + m_lastError = hr; + return NULL; + } + if (result) { + return pStore; + } + } + + pStore = new CMsgStore(cbEid, lpEid); + AddMessageStore(pStore); + return pStore; +} + +// -------------------------------------------------------------------- +// Utility stuff +// -------------------------------------------------------------------- + +LPSPropValue CMapiApi::GetMapiProperty(LPMAPIPROP pProp, ULONG tag) { + if (!pProp) return NULL; + + int sz = CbNewSPropTagArray(1); + SPropTagArray* pTag = (SPropTagArray*)new char[sz]; + pTag->cValues = 1; + pTag->aulPropTag[0] = tag; + LPSPropValue lpProp = NULL; + ULONG cValues = 0; + HRESULT hr = pProp->GetProps(pTag, 0, &cValues, &lpProp); + delete[] pTag; + if (HR_FAILED(hr) || (cValues != 1)) { + if (lpProp) MAPIFreeBuffer(lpProp); + return NULL; + } else { + if (PROP_TYPE(lpProp->ulPropTag) == PT_ERROR) { + if (lpProp->Value.l == MAPI_E_NOT_FOUND) { + MAPIFreeBuffer(lpProp); + lpProp = NULL; + } + } + } + + return lpProp; +} + +BOOL CMapiApi::IsLargeProperty(LPSPropValue pVal) { + return ((PROP_TYPE(pVal->ulPropTag) == PT_ERROR) && + (pVal->Value.l == E_OUTOFMEMORY)); +} + +// The output buffer (result) must be freed with operator delete[] +BOOL CMapiApi::GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result) { + LPSTREAM lpStream; + HRESULT hr = + pProp->OpenProperty(tag, &IID_IStream, 0, 0, (LPUNKNOWN*)&lpStream); + if (HR_FAILED(hr)) return FALSE; + STATSTG st; + BOOL bResult = TRUE; + hr = lpStream->Stat(&st, STATFLAG_NONAME); + if (HR_FAILED(hr)) + bResult = FALSE; + else { + if (!st.cbSize.QuadPart) st.cbSize.QuadPart = 1; + char* pVal = new char[(int)st.cbSize.QuadPart + 2]; + if (pVal) { + ULONG sz; + hr = lpStream->Read(pVal, (ULONG)st.cbSize.QuadPart, &sz); + if (HR_FAILED(hr)) { + bResult = FALSE; + delete[] pVal; + } else { + // Just in case it's a UTF16 string + pVal[(int)st.cbSize.QuadPart] = pVal[(int)st.cbSize.QuadPart + 1] = 0; + *result = pVal; + } + } else + bResult = FALSE; + } + + lpStream->Release(); + + return bResult; +} + +BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, + nsCString& val) { + void* result; + if (!GetLargeProperty(pProp, tag, &result)) return FALSE; + if (PROP_TYPE(tag) == PT_UNICODE) // unicode string + LossyCopyUTF16toASCII(nsDependentString(static_cast(result)), + val); + else // either PT_STRING8 or some other binary - use as is + val.Assign(static_cast(result)); + // Despite being used as wchar_t*, result it allocated as "new char[]" in + // GetLargeProperty(). + delete[] static_cast(result); + return TRUE; +} + +BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, + nsString& val) { + void* result; + if (!GetLargeProperty(pProp, tag, &result)) return FALSE; + if (PROP_TYPE(tag) == PT_UNICODE) // We already get the unicode string + val.Assign(static_cast(result)); + else // either PT_STRING8 or some other binary + CStrToUnicode(static_cast(result), val); + // Despite being used as wchar_t*, result it allocated as "new char[]" in + // GetLargeProperty(). + delete[] static_cast(result); + return TRUE; +} +// If the value is a string, get it... +BOOL CMapiApi::GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId, + LPENTRYID& lpEntryId, BOOL delVal) { + if (!pVal) return FALSE; + + BOOL bResult = TRUE; + switch (PROP_TYPE(pVal->ulPropTag)) { + case PT_BINARY: + cbEntryId = pVal->Value.bin.cb; + MAPIAllocateBuffer(cbEntryId, (LPVOID*)&lpEntryId); + memcpy(lpEntryId, pVal->Value.bin.lpb, cbEntryId); + break; + + default: + MAPI_TRACE0("EntryId not in BINARY prop value\n"); + bResult = FALSE; + break; + } + + if (pVal && delVal) MAPIFreeBuffer(pVal); + + return bResult; +} + +BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsCString& val, + BOOL delVal) { + BOOL bResult = TRUE; + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8)) + val = pVal->Value.lpszA; + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE)) + LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), val); + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) + val.Truncate(); + else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + val.Truncate(); + bResult = FALSE; + } else { + if (pVal) { + MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n", + (int)PROP_TYPE(pVal->ulPropTag)); + } else { + MAPI_TRACE0( + "GetStringFromProp: invalid value, expecting string, got null " + "pointer\n"); + } + val.Truncate(); + bResult = FALSE; + } + if (pVal && delVal) MAPIFreeBuffer(pVal); + + return bResult; +} + +BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsString& val, + BOOL delVal) { + BOOL bResult = TRUE; + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8)) { + CStrToUnicode((const char*)pVal->Value.lpszA, val); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE)) { + val = (char16_t*)pVal->Value.lpszW; + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + val.Truncate(); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + val.Truncate(); + bResult = FALSE; + } else { + if (pVal) { + MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n", + (int)PROP_TYPE(pVal->ulPropTag)); + } else { + MAPI_TRACE0( + "GetStringFromProp: invalid value, expecting string, got null " + "pointer\n"); + } + val.Truncate(); + bResult = FALSE; + } + if (pVal && delVal) MAPIFreeBuffer(pVal); + + return bResult; +} + +LONG CMapiApi::GetLongFromProp(LPSPropValue pVal, BOOL delVal) { + LONG val = 0; + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) { + val = pVal->Value.l; + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + val = 0; + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + val = 0; + MAPI_TRACE0("GetLongFromProp: Error retrieving property\n"); + } else { + MAPI_TRACE0("GetLongFromProp: invalid value, expecting long\n"); + } + if (pVal && delVal) MAPIFreeBuffer(pVal); + + return val; +} + +void CMapiApi::ReportUIDProp(const char* pTag, LPSPropValue pVal) { + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_BINARY)) { + if (pVal->Value.bin.cb != 16) { + MAPI_TRACE1("%s - INVALID, expecting 16 bytes of binary data for UID\n", + pTag); + } else { + nsIID uid; + memcpy(&uid, pVal->Value.bin.lpb, 16); + const char* pStr = uid.ToString().get(); + if (pStr) { + MAPI_TRACE2("%s %s\n", pTag, pStr); + } + } + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + MAPI_TRACE1("%s {NULL}\n", pTag); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } else { + MAPI_TRACE1("%s invalid value, expecting binary\n", pTag); + } + if (pVal) MAPIFreeBuffer(pVal); +} + +void CMapiApi::ReportLongProp(const char* pTag, LPSPropValue pVal) { + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) { + nsCString num; + nsCString num2; + + num.AppendInt((int32_t)pVal->Value.l); + num2.AppendInt((int32_t)pVal->Value.l, 16); + MAPI_TRACE3("%s %s, 0x%s\n", pTag, num, num2); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + MAPI_TRACE1("%s {NULL}\n", pTag); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } else { + MAPI_TRACE1("%s invalid value, expecting long\n", pTag); + } + if (pVal) MAPIFreeBuffer(pVal); +} + +void CMapiApi::ReportStringProp(const char* pTag, LPSPropValue pVal) { + if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_TSTRING)) { + nsCString val((LPCTSTR)(pVal->Value.LPSZ)); + MAPI_TRACE2("%s %s\n", pTag, val.get()); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) { + MAPI_TRACE1("%s {NULL}\n", pTag); + } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) { + MAPI_TRACE1("%s {Error retrieving property}\n", pTag); + } else { + MAPI_TRACE1("%s invalid value, expecting string\n", pTag); + } + if (pVal) MAPIFreeBuffer(pVal); +} + +void CMapiApi::GetPropTagName(ULONG tag, nsCString& s) { + char numStr[256]; + PR_snprintf(numStr, 256, "0x%lx, %ld", tag, tag); + s = numStr; + switch (tag) { +#include "MapiTagStrs.cpp" + } + s += ", data: "; + switch (PROP_TYPE(tag)) { + case PT_UNSPECIFIED: + s += "PT_UNSPECIFIED"; + break; + case PT_NULL: + s += "PT_NULL"; + break; + case PT_I2: + s += "PT_I2"; + break; + case PT_LONG: + s += "PT_LONG"; + break; + case PT_R4: + s += "PT_R4"; + break; + case PT_DOUBLE: + s += "PT_DOUBLE"; + break; + case PT_CURRENCY: + s += "PT_CURRENCY"; + break; + case PT_APPTIME: + s += "PT_APPTIME"; + break; + case PT_ERROR: + s += "PT_ERROR"; + break; + case PT_BOOLEAN: + s += "PT_BOOLEAN"; + break; + case PT_OBJECT: + s += "PT_OBJECT"; + break; + case PT_I8: + s += "PT_I8"; + break; + case PT_STRING8: + s += "PT_STRING8"; + break; + case PT_UNICODE: + s += "PT_UNICODE"; + break; + case PT_SYSTIME: + s += "PT_SYSTIME"; + break; + case PT_CLSID: + s += "PT_CLSID"; + break; + case PT_BINARY: + s += "PT_BINARY"; + break; + case PT_MV_I2: + s += "PT_MV_I2"; + break; + case PT_MV_LONG: + s += "PT_MV_LONG"; + break; + case PT_MV_R4: + s += "PT_MV_R4"; + break; + case PT_MV_DOUBLE: + s += "PT_MV_DOUBLE"; + break; + case PT_MV_CURRENCY: + s += "PT_MV_CURRENCY"; + break; + case PT_MV_APPTIME: + s += "PT_MV_APPTIME"; + break; + case PT_MV_SYSTIME: + s += "PT_MV_SYSTIME"; + break; + case PT_MV_STRING8: + s += "PT_MV_STRING8"; + break; + case PT_MV_BINARY: + s += "PT_MV_BINARY"; + break; + case PT_MV_UNICODE: + s += "PT_MV_UNICODE"; + break; + case PT_MV_CLSID: + s += "PT_MV_CLSID"; + break; + case PT_MV_I8: + s += "PT_MV_I8"; + break; + default: + s += "Unknown"; + } +} + +void CMapiApi::ListPropertyValue(LPSPropValue pVal, nsCString& s) { + nsCString strVal; + char nBuff[64]; + + s += "value: "; + switch (PROP_TYPE(pVal->ulPropTag)) { + case PT_STRING8: + GetStringFromProp(pVal, strVal, FALSE); + if (strVal.Length() > 60) { + strVal.SetLength(60); + strVal += "..."; + } + strVal.ReplaceSubstring("\r", "\\r"); + strVal.ReplaceSubstring("\n", "\\n"); + s += strVal; + break; + case PT_LONG: + s.AppendInt((int32_t)pVal->Value.l); + s += ", 0x"; + s.AppendInt((int32_t)pVal->Value.l, 16); + s += nBuff; + break; + case PT_BOOLEAN: + if (pVal->Value.b) + s += "True"; + else + s += "False"; + break; + case PT_NULL: + s += "--NULL--"; + break; + case PT_SYSTIME: { + /* + COleDateTime tm(pVal->Value.ft); + s += tm.Format(); + */ + s += "-- Figure out how to format time in mozilla, PT_SYSTIME --"; + } break; + default: + s += "?"; + } +} + +// ------------------------------------------------------------------- +// Folder list stuff +// ------------------------------------------------------------------- +CMapiFolderList::CMapiFolderList() {} + +CMapiFolderList::~CMapiFolderList() { ClearAll(); } + +void CMapiFolderList::AddItem(CMapiFolder* pFolder) { + EnsureUniqueName(pFolder); + GenerateFilePath(pFolder); + m_array.AppendElement(pFolder); +} + +void CMapiFolderList::ChangeName(nsString& name) { + if (name.IsEmpty()) { + name.Assign('1'); + return; + } + char16_t lastC = name.Last(); + if ((lastC >= '0') && (lastC <= '9')) { + lastC++; + if (lastC > '9') { + lastC = '1'; + name.SetCharAt(lastC, name.Length() - 1); + name.Append('0'); + } else { + name.SetCharAt(lastC, name.Length() - 1); + } + } else { + name.AppendLiteral(" 2"); + } +} + +void CMapiFolderList::EnsureUniqueName(CMapiFolder* pFolder) { + // For everybody in the array before me with the SAME + // depth, my name must be unique + CMapiFolder* pCurrent; + int i; + BOOL done; + nsString name; + nsString cName; + + pFolder->GetDisplayName(name); + do { + done = TRUE; + i = m_array.Length() - 1; + while (i >= 0) { + pCurrent = GetAt(i); + if (pCurrent->GetDepth() == pFolder->GetDepth()) { + pCurrent->GetDisplayName(cName); + if (cName.Equals(name, nsCaseInsensitiveStringComparator)) { + ChangeName(name); + pFolder->SetDisplayName(name.get()); + done = FALSE; + break; + } + } else if (pCurrent->GetDepth() < pFolder->GetDepth()) + break; + i--; + } + } while (!done); +} + +void CMapiFolderList::GenerateFilePath(CMapiFolder* pFolder) { + // A file path, includes all of my parent's path, plus mine + nsString name; + nsString path; + if (!pFolder->GetDepth()) { + pFolder->GetDisplayName(name); + pFolder->SetFilePath(name.get()); + return; + } + + CMapiFolder* pCurrent; + int i = m_array.Length() - 1; + while (i >= 0) { + pCurrent = GetAt(i); + if (pCurrent->GetDepth() == (pFolder->GetDepth() - 1)) { + pCurrent->GetFilePath(path); + path.AppendLiteral(".sbd\\"); + pFolder->GetDisplayName(name); + path += name; + pFolder->SetFilePath(path.get()); + return; + } + i--; + } + pFolder->GetDisplayName(name); + pFolder->SetFilePath(name.get()); +} + +void CMapiFolderList::ClearAll(void) { + CMapiFolder* pFolder; + for (size_t i = 0; i < m_array.Length(); i++) { + pFolder = GetAt(i); + delete pFolder; + } + m_array.Clear(); +} + +void CMapiFolderList::DumpList(void) { + CMapiFolder* pFolder; + nsString str; + int depth; + char prefix[256]; + + MAPI_TRACE0("Folder List ---------------------------------\n"); + for (size_t i = 0; i < m_array.Length(); i++) { + pFolder = GetAt(i); + depth = pFolder->GetDepth(); + pFolder->GetDisplayName(str); + depth *= 2; + if (depth > 255) depth = 255; + memset(prefix, ' ', depth); + prefix[depth] = 0; +#ifdef MAPI_DEBUG + char* ansiStr = ToNewCString(str); + MAPI_TRACE2("%s%s: ", prefix, ansiStr); + free(ansiStr); +#endif + pFolder->GetFilePath(str); +#ifdef MAPI_DEBUG + ansiStr = ToNewCString(str); + MAPI_TRACE2("depth=%d, filePath=%s\n", pFolder->GetDepth(), ansiStr); + free(ansiStr); +#endif + } + MAPI_TRACE0("---------------------------------------------\n"); +} + +CMapiFolder::CMapiFolder() { + m_objectType = MAPI_FOLDER; + m_cbEid = 0; + m_lpEid = NULL; + m_depth = 0; + m_doImport = TRUE; +} + +CMapiFolder::CMapiFolder(const char16_t* pDisplayName, ULONG cbEid, + LPENTRYID lpEid, int depth, LONG oType) { + m_cbEid = 0; + m_lpEid = NULL; + SetDisplayName(pDisplayName); + SetEntryID(cbEid, lpEid); + SetDepth(depth); + SetObjectType(oType); + SetDoImport(TRUE); +} + +CMapiFolder::CMapiFolder(const CMapiFolder* pCopyFrom) { + m_lpEid = NULL; + m_cbEid = 0; + SetDoImport(pCopyFrom->GetDoImport()); + SetDisplayName(pCopyFrom->m_displayName.get()); + SetObjectType(pCopyFrom->GetObjectType()); + SetEntryID(pCopyFrom->GetCBEntryID(), pCopyFrom->GetEntryID()); + SetDepth(pCopyFrom->GetDepth()); + SetFilePath(pCopyFrom->m_mailFilePath.get()); +} + +CMapiFolder::~CMapiFolder() { + if (m_lpEid) delete m_lpEid; +} + +void CMapiFolder::SetEntryID(ULONG cbEid, LPENTRYID lpEid) { + if (m_lpEid) delete m_lpEid; + m_lpEid = NULL; + m_cbEid = cbEid; + if (cbEid) { + m_lpEid = new BYTE[cbEid]; + memcpy(m_lpEid, lpEid, cbEid); + } +} + +// --------------------------------------------------------------------- +// Message store stuff +// --------------------------------------------------------------------- + +CMsgStore::CMsgStore(ULONG cbEid, LPENTRYID lpEid) { + m_lpEid = NULL; + m_lpMdb = NULL; + SetEntryID(cbEid, lpEid); +} + +CMsgStore::~CMsgStore() { + if (m_lpEid) delete m_lpEid; + + if (m_lpMdb) { + ULONG flags = LOGOFF_NO_WAIT; + m_lpMdb->StoreLogoff(&flags); + m_lpMdb->Release(); + m_lpMdb = NULL; + } +} + +void CMsgStore::SetEntryID(ULONG cbEid, LPENTRYID lpEid) { + if (m_lpEid) delete m_lpEid; + + m_lpEid = NULL; + if (cbEid) { + m_lpEid = new BYTE[cbEid]; + memcpy(m_lpEid, lpEid, cbEid); + } + m_cbEid = cbEid; + + if (m_lpMdb) { + ULONG flags = LOGOFF_NO_WAIT; + m_lpMdb->StoreLogoff(&flags); + m_lpMdb->Release(); + m_lpMdb = NULL; + } +} + +BOOL CMsgStore::Open(LPMAPISESSION pSession, LPMDB* ppMdb) { + if (m_lpMdb) { + if (ppMdb) *ppMdb = m_lpMdb; + return TRUE; + } + + BOOL bResult = TRUE; + HRESULT hr = pSession->OpenMsgStore(NULL, m_cbEid, (LPENTRYID)m_lpEid, NULL, + MDB_NO_MAIL, &m_lpMdb); // MDB pointer + if (HR_FAILED(hr)) { + m_lpMdb = NULL; + MAPI_TRACE2("OpenMsgStore failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = FALSE; + } + + if (ppMdb) *ppMdb = m_lpMdb; + return bResult; +} + +// ------------------------------------------------------------ +// Contents Iterator +// ----------------------------------------------------------- + +CMapiFolderContents::CMapiFolderContents(LPMDB lpMdb, ULONG cbEid, + LPENTRYID lpEid) { + m_lpMdb = lpMdb; + m_fCbEid = cbEid; + m_fLpEid = new BYTE[cbEid]; + memcpy(m_fLpEid, lpEid, cbEid); + m_count = 0; + m_iterCount = 0; + m_failure = FALSE; + m_lastError = 0; + m_lpFolder = NULL; + m_lpTable = NULL; + m_lastLpEid = NULL; + m_lastCbEid = 0; +} + +CMapiFolderContents::~CMapiFolderContents() { + if (m_lastLpEid) delete m_lastLpEid; + delete m_fLpEid; + if (m_lpTable) m_lpTable->Release(); + if (m_lpFolder) m_lpFolder->Release(); +} + +BOOL CMapiFolderContents::SetUpIter(void) { + // First, open up the MAPIFOLDER object + ULONG ulObjType; + HRESULT hr; + hr = m_lpMdb->OpenEntry(m_fCbEid, (LPENTRYID)m_fLpEid, NULL, 0, &ulObjType, + (LPUNKNOWN*)&m_lpFolder); + + if (FAILED(hr) || !m_lpFolder) { + m_lpFolder = NULL; + m_lastError = hr; + MAPI_TRACE2("CMapiFolderContents OpenEntry failed: 0x%lx, %d\n", (long)hr, + (int)hr); + return FALSE; + } + + if (ulObjType != MAPI_FOLDER) { + m_lastError = E_UNEXPECTED; + MAPI_TRACE0("CMapiFolderContents - bad object type, not a folder.\n"); + return FALSE; + } + + hr = m_lpFolder->GetContentsTable(0, &m_lpTable); + if (FAILED(hr) || !m_lpTable) { + m_lastError = hr; + m_lpTable = NULL; + MAPI_TRACE2("CMapiFolderContents - GetContentsTable failed: 0x%lx, %d\n", + (long)hr, (int)hr); + return FALSE; + } + + hr = m_lpTable->GetRowCount(0, &m_count); + if (FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE0("CMapiFolderContents - GetRowCount failed\n"); + return FALSE; + } + + hr = m_lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE2("CMapiFolderContents - SetColumns failed: 0x%lx, %d\n", + (long)hr, (int)hr); + return FALSE; + } + + hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + m_lastError = hr; + MAPI_TRACE2("CMapiFolderContents - SeekRow failed: 0x%lx, %d\n", (long)hr, + (int)hr); + return FALSE; + } + + return TRUE; +} + +BOOL CMapiFolderContents::GetNext(ULONG* pcbEid, LPENTRYID* ppEid, + ULONG* poType, BOOL* pDone) { + *pDone = FALSE; + if (m_failure) return FALSE; + if (!m_lpFolder) { + if (!SetUpIter()) { + m_failure = TRUE; + return FALSE; + } + if (!m_count) { + *pDone = TRUE; + return TRUE; + } + } + + int cNumRows = 0; + LPSRowSet lpRow = NULL; + HRESULT hr = m_lpTable->QueryRows(1, 0, &lpRow); + + if (HR_FAILED(hr)) { + m_lastError = hr; + m_failure = TRUE; + MAPI_TRACE2("CMapiFolderContents - QueryRows failed: 0x%lx, %d\n", (long)hr, + (int)hr); + return FALSE; + } + + if (lpRow) { + cNumRows = lpRow->cRows; + if (cNumRows) { + LPENTRYID lpEID = + (LPENTRYID)lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb; + ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb; + ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul; + + if (m_lastCbEid != cbEID) { + if (m_lastLpEid) delete m_lastLpEid; + m_lastLpEid = new BYTE[cbEID]; + m_lastCbEid = cbEID; + } + memcpy(m_lastLpEid, lpEID, cbEID); + + *ppEid = (LPENTRYID)m_lastLpEid; + *pcbEid = cbEID; + *poType = oType; + } else + *pDone = TRUE; + CMapiApi::FreeProws(lpRow); + } else + *pDone = TRUE; + + return TRUE; +} diff --git a/comm/mailnews/import/src/MapiApi.h b/comm/mailnews/import/src/MapiApi.h new file mode 100644 index 0000000000..4d9dc7be2a --- /dev/null +++ b/comm/mailnews/import/src/MapiApi.h @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef MapiApi_h___ +#define MapiApi_h___ + +#include "nscore.h" +#include "nsString.h" +#include "nsTArray.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +// wabutil.h expects mapiutil to define _MAPIUTIL_H but it actually +// defines _MAPIUTIL_H_ +#define _MAPIUTIL_H + +#ifndef PR_INTERNET_CPID +# define PR_INTERNET_CPID (PROP_TAG(PT_LONG, 0x3FDE)) +#endif +#ifndef MAPI_NATIVE_BODY +# define MAPI_NATIVE_BODY (0x00010000) +#endif +#ifndef MAPI_NATIVE_BODY_TYPE_RTF +# define MAPI_NATIVE_BODY_TYPE_RTF (0x00000001) +#endif +#ifndef MAPI_NATIVE_BODY_TYPE_HTML +# define MAPI_NATIVE_BODY_TYPE_HTML (0x00000002) +#endif +#ifndef MAPI_NATIVE_BODY_TYPE_PLAINTEXT +# define MAPI_NATIVE_BODY_TYPE_PLAINTEXT (0x00000004) +#endif +#ifndef PR_BODY_HTML_A +# define PR_BODY_HTML_A (PROP_TAG(PT_STRING8, 0x1013)) +#endif +#ifndef PR_BODY_HTML_W +# define PR_BODY_HTML_W (PROP_TAG(PT_UNICODE, 0x1013)) +#endif +#ifndef PR_BODY_HTML +# define PR_BODY_HTML (PROP_TAG(PT_TSTRING, 0x1013)) +#endif + +class CMapiFolderList; +class CMsgStore; +class CMapiFolder; + +class CMapiContentIter { + public: + virtual BOOL HandleContentItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0; +}; + +class CMapiHierarchyIter { + public: + virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0; +}; + +class CMapiApi { + public: + CMapiApi(); + ~CMapiApi(); + + static BOOL LoadMapi(void); + static BOOL LoadMapiEntryPoints(void); + static void UnloadMapi(void); + + static HINSTANCE m_hMapi32; + + static void MAPIUninitialize(void); + static HRESULT MAPIInitialize(LPVOID lpInit); + static SCODE MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR* lppBuffer); + static ULONG MAPIFreeBuffer(LPVOID lpBuff); + static HRESULT MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName, + LPTSTR lpszPassword, FLAGS flFlags, + LPMAPISESSION FAR* lppSession); + static HRESULT OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer, + LPFREEBUFFER lpFreeBuffer, ULONG ulFlags, + LPCTSTR lpszFileName, LPTSTR lpszPrefix, + LPSTREAM FAR* lppStream); + static void FreeProws(LPSRowSet prows); + + BOOL Initialize(void); + BOOL LogOn(void); + + void AddMessageStore(CMsgStore* pStore); + void SetCurrentMsgStore(LPMDB lpMdb) { m_lpMdb = lpMdb; } + + // Open any given entry from the current Message Store + BOOL OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN* ppOpen); + static BOOL OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId, + LPUNKNOWN* ppOpen); + + // Fill in the folders list with the hierarchy from the given + // message store. + BOOL GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders, + int startDepth); + BOOL GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid, + CMapiFolderList& folders); + BOOL OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB* ppMdb); + + // Iteration + BOOL IterateStores(CMapiFolderList& list); + BOOL IterateContents(CMapiContentIter* pIter, LPMAPIFOLDER pFolder, + ULONG flags = 0); + BOOL IterateHierarchy(CMapiHierarchyIter* pIter, LPMAPIFOLDER pFolder, + ULONG flags = 0); + + // Properties + static LPSPropValue GetMapiProperty(LPMAPIPROP pProp, ULONG tag); + // If delVal is true, functions will call CMapiApi::MAPIFreeBuffer on pVal. + static BOOL GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId, + LPENTRYID& lpEntryId, BOOL delVal = TRUE); + static BOOL GetStringFromProp(LPSPropValue pVal, nsCString& val, + BOOL delVal = TRUE); + static BOOL GetStringFromProp(LPSPropValue pVal, nsString& val, + BOOL delVal = TRUE); + static LONG GetLongFromProp(LPSPropValue pVal, BOOL delVal = TRUE); + static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, + nsCString& val); + static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag, + nsString& val); + static BOOL IsLargeProperty(LPSPropValue pVal); + static ULONG GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID); + + static BOOL GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val, + unsigned long& nativeBodyType, + unsigned long codepage = 0); + + // Debugging & reporting stuff + static void ListProperties(LPMAPIPROP lpProp, BOOL getValues = TRUE); + static void ListPropertyValue(LPSPropValue pVal, nsCString& s); + + protected: + BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry); + BOOL HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry); + void GetStoreInfo(CMapiFolder* pFolder, long* pSzContents); + + // array of available message stores, cached so that + // message stores are only opened once, preventing multiple + // logon's by the user if the store requires a logon. + CMsgStore* FindMessageStore(ULONG cbEid, LPENTRYID lpEid); + void ClearMessageStores(void); + + static void CStrToUnicode(const char* pStr, nsString& result); + + // Debugging & reporting stuff + static void GetPropTagName(ULONG tag, nsCString& s); + static void ReportStringProp(const char* pTag, LPSPropValue pVal); + static void ReportUIDProp(const char* pTag, LPSPropValue pVal); + static void ReportLongProp(const char* pTag, LPSPropValue pVal); + + private: + static int m_clients; + static BOOL m_initialized; + static nsTArray* m_pStores; + static LPMAPISESSION m_lpSession; + static LPMDB m_lpMdb; + static HRESULT m_lastError; + static char16_t* m_pUniBuff; + static int m_uniBuffLen; + + static BOOL GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result); +}; + +class CMapiFolder { + public: + CMapiFolder(); + explicit CMapiFolder(const CMapiFolder* pCopyFrom); + CMapiFolder(const char16_t* pDisplayName, ULONG cbEid, LPENTRYID lpEid, + int depth, LONG oType = MAPI_FOLDER); + ~CMapiFolder(); + + void SetDoImport(BOOL doIt) { m_doImport = doIt; } + void SetObjectType(long oType) { m_objectType = oType; } + void SetDisplayName(const char16_t* pDisplayName) { + m_displayName = pDisplayName; + } + void SetEntryID(ULONG cbEid, LPENTRYID lpEid); + void SetDepth(int depth) { m_depth = depth; } + void SetFilePath(const char16_t* pFilePath) { m_mailFilePath = pFilePath; } + + BOOL GetDoImport(void) const { return m_doImport; } + LONG GetObjectType(void) const { return m_objectType; } + void GetDisplayName(nsString& name) const { name = m_displayName; } + void GetFilePath(nsString& path) const { path = m_mailFilePath; } + BOOL IsStore(void) const { return m_objectType == MAPI_STORE; } + BOOL IsFolder(void) const { return m_objectType == MAPI_FOLDER; } + int GetDepth(void) const { return m_depth; } + + LPENTRYID GetEntryID(ULONG* pCb = NULL) const { + if (pCb) *pCb = m_cbEid; + return (LPENTRYID)m_lpEid; + } + ULONG GetCBEntryID(void) const { return m_cbEid; } + + private: + LONG m_objectType; + ULONG m_cbEid; + BYTE* m_lpEid; + nsString m_displayName; + int m_depth; + nsString m_mailFilePath; + BOOL m_doImport; +}; + +class CMapiFolderList { + public: + CMapiFolderList(); + ~CMapiFolderList(); + + void AddItem(CMapiFolder* pFolder); + CMapiFolder* GetItem(int index) { + if ((index >= 0) && (index < (int)m_array.Length())) + return GetAt(index); + else + return NULL; + } + void ClearAll(void); + + // Debugging and reporting + void DumpList(void); + + CMapiFolder* GetAt(int index) { return m_array.ElementAt(index); } + int GetSize(void) { return m_array.Length(); } + + protected: + void EnsureUniqueName(CMapiFolder* pFolder); + void GenerateFilePath(CMapiFolder* pFolder); + void ChangeName(nsString& name); + + private: + nsTArray m_array; +}; + +class CMsgStore { + public: + explicit CMsgStore(ULONG cbEid = 0, LPENTRYID lpEid = NULL); + ~CMsgStore(); + + void SetEntryID(ULONG cbEid, LPENTRYID lpEid); + BOOL Open(LPMAPISESSION pSession, LPMDB* ppMdb); + + ULONG GetCBEntryID(void) { return m_cbEid; } + LPENTRYID GetLPEntryID(void) { return (LPENTRYID)m_lpEid; } + + private: + ULONG m_cbEid; + BYTE* m_lpEid; + LPMDB m_lpMdb; +}; + +class CMapiFolderContents { + public: + CMapiFolderContents(LPMDB lpMdb, ULONG cbEID, LPENTRYID lpEid); + ~CMapiFolderContents(); + + BOOL GetNext(ULONG* pcbEid, LPENTRYID* ppEid, ULONG* poType, BOOL* pDone); + + ULONG GetCount(void) { return m_count; } + + protected: + BOOL SetUpIter(void); + + private: + HRESULT m_lastError; + BOOL m_failure; + LPMDB m_lpMdb; + LPMAPIFOLDER m_lpFolder; + LPMAPITABLE m_lpTable; + ULONG m_fCbEid; + BYTE* m_fLpEid; + ULONG m_count; + ULONG m_iterCount; + BYTE* m_lastLpEid; + ULONG m_lastCbEid; +}; + +#endif /* MapiApi_h__ */ diff --git a/comm/mailnews/import/src/MapiDbgLog.h b/comm/mailnews/import/src/MapiDbgLog.h new file mode 100644 index 0000000000..56580920ea --- /dev/null +++ b/comm/mailnews/import/src/MapiDbgLog.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MapiDbgLog_h___ +#define MapiDbgLog_h___ + +/* +#ifdef NS_DEBUG +#define MAPI_DEBUG 1 +#endif +*/ + +#ifdef MAPI_DEBUG +# include + +# define MAPI_DUMP_STRING(x) printf("%s", (const char*)x) +# define MAPI_TRACE0(x) printf(x) +# define MAPI_TRACE1(x, y) printf(x, y) +# define MAPI_TRACE2(x, y, z) printf(x, y, z) +# define MAPI_TRACE3(x, y, z, a) printf(x, y, z, a) +# define MAPI_TRACE4(x, y, z, a, b) printf(x, y, z, a, b) + +#else + +# define MAPI_DUMP_STRING(x) +# define MAPI_TRACE0(x) +# define MAPI_TRACE1(x, y) +# define MAPI_TRACE2(x, y, z) +# define MAPI_TRACE3(x, y, z, a) +# define MAPI_TRACE4(x, y, z, a, b) + +#endif + +#endif /* MapiDbgLog_h___ */ 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; +} diff --git a/comm/mailnews/import/src/MapiMessage.h b/comm/mailnews/import/src/MapiMessage.h new file mode 100644 index 0000000000..c06a84c57a --- /dev/null +++ b/comm/mailnews/import/src/MapiMessage.h @@ -0,0 +1,290 @@ +/* -*- 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 MapiMessage_h___ +#define MapiMessage_h___ + +#include "nsTArray.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsIMsgSend.h" +#include "MapiApi.h" + +#include + +#ifndef PR_LAST_VERB_EXECUTED +# define PR_LAST_VERB_EXECUTED PROP_TAG(PT_LONG, 0x1081) +#endif + +#define EXCHIVERB_REPLYTOSENDER (102) +#define EXCHIVERB_REPLYTOALL (103) +#define EXCHIVERB_FORWARD (104) + +#ifndef PR_ATTACH_CONTENT_ID +# define PR_ATTACH_CONTENT_ID PROP_TAG(PT_TSTRING, 0x3712) +#endif +#ifndef PR_ATTACH_CONTENT_ID_W +# define PR_ATTACH_CONTENT_ID_W PROP_TAG(PT_UNICODE, 0x3712) +#endif +#ifndef PR_ATTACH_CONTENT_ID_A +# define PR_ATTACH_CONTENT_ID_A PROP_TAG(PT_STRING8, 0x3712) +#endif + +#ifndef PR_ATTACH_FLAGS +# define PR_ATTACH_FLAGS PROP_TAG(PT_LONG, 0x3714) +#endif + +#ifndef ATT_INVISIBLE_IN_HTML +# define ATT_INVISIBLE_IN_HTML (0x1) +#endif +#ifndef ATT_INVISIBLE_IN_RTF +# define ATT_INVISIBLE_IN_RTF (0x2) +#endif +#ifndef ATT_MHTML_REF +# define ATT_MHTML_REF (0x4) +#endif + +////////////////////////////////////////////////////////////////////////////// + +class CMapiMessageHeaders { + public: + // Special headers that MUST appear at most once (see RFC822) + enum SpecialHeader { + hdrNone = -1, + hdrFirst = 0, // utility values + hdrDate = hdrFirst, + hdrFrom, + hdrSender, + hdrReplyTo, + hdrTo, + hdrCc, + hdrBcc, + hdrMessageID, + hdrSubject, + hdrMimeVersion, + hdrContentType, + hdrContentTransferEncoding, + hdrMax // utility value + }; + + explicit CMapiMessageHeaders(const char* headers = 0) { Assign(headers); } + ~CMapiMessageHeaders(); + void Assign(const char* headers); + + inline bool IsEmpty() const { return m_headerFields.empty(); } + // if no such header exists then 0 is returned, else the first value returned + const char* Value(const char* name) const; + // if no such header exists then 0 is returned + const char* Value(SpecialHeader special) const; + + void UnfoldValue(const char* name, nsString& dest, + const char* fallbackCharset) const; + void UnfoldValue(SpecialHeader special, nsString& dest, + const char* fallbackCharset) const; + + // value must be utf-8 or 7-bit; supposed that this function will be called + // when the charset of the value is known + // TODO: if replace is set, then all headers with this name will be removed + // and one with this value will be added, otherwise a new header is added + // (Unnecessary for now) + int SetValue(const char* name, const char* value, bool replace = true); + int SetValue(SpecialHeader special, const char* value); + + static const char* SpecialName(SpecialHeader special); + + nsresult ToStream(nsIOutputStream* pDst) const; + + private: + class CHeaderField { + public: + CHeaderField(const char* begin, int len); + CHeaderField(const char* name, const char* body, bool utf8 = false); + ~CHeaderField(); + inline bool Valid() const { return m_fname; } + inline const char* fname() const { return m_fname; } + inline const char* fbody() const { return m_fbody; } + + // txt must be utf-8 or 7-bit; supposed that this function will be called + // when the charset of the txt is known + void set_fbody(const char* txt); + + void GetUnfoldedString(nsString& dest, const char* fallbackCharset) const; + + private: + char* m_fname; + char* m_fbody; + bool m_fbody_utf8; + }; // class HeaderField + + class write_to_stream { + public: + explicit write_to_stream(nsIOutputStream* pDst) + : m_pDst(pDst), m_rv(NS_OK) {} + void operator()(const CHeaderField* f); + inline operator nsresult() const { return m_rv; } + + private: + nsIOutputStream* m_pDst; + nsresult m_rv; + }; + + // Search helper + class fname_equals { + public: + explicit fname_equals(const char* search) : m_search(search) {} + inline bool operator()(const CHeaderField* f) const { + return stricmp(f->fname(), m_search) == 0; + } + + private: + const char* m_search; + }; // class fname_equals + + // The common array of special headers' names + static const char* Specials[hdrMax]; + + std::vector m_headerFields; + CHeaderField* m_SpecialHeaders[hdrMax]; // Pointers into the m_headerFields + + void ClearHeaderFields(); + void Add(CHeaderField* f); + static void Delete(CHeaderField* p); + static SpecialHeader CheckSpecialHeader(const char* fname); + const CHeaderField* CFind(const char* name) const; + inline CHeaderField* Find(const char* name) { + return const_cast(CFind(name)); + } + +}; // class CMapiMessageHeaders + +////////////////////////////////////////////////////// + +class CMapiMessage { + public: + explicit CMapiMessage(LPMESSAGE lpMsg); + ~CMapiMessage(); + + // Attachments + // Ordinary (not embedded) attachments. + nsresult GetAttachments(nsTArray>& attachments); + // Embedded attachments + size_t EmbeddedAttachmentsCount() const { return m_embattachments.size(); } + bool GetEmbeddedAttachmentInfo(unsigned int i, nsIURI** uri, const char** cid, + const char** name) const; + // We don't check MSGFLAG_HASATTACH, since it returns true even if there are + // only embedded attachmentsin the message. TB only counts the ordinary + // attachments when shows the message status, so here we check only for the + // ordinary attachments. + inline bool HasAttach() const { return !m_stdattachments.empty(); } + + // Retrieve info for message + inline bool BodyIsHtml(void) const { return m_bodyIsHtml; } + const char* GetFromLine(int& len) const { + if (m_fromLine.IsEmpty()) + return NULL; + else { + len = m_fromLine.Length(); + return m_fromLine.get(); + } + } + inline CMapiMessageHeaders* GetHeaders() { return &m_headers; } + inline const wchar_t* GetBody(void) const { return m_body.get(); } + inline size_t GetBodyLen(void) const { return m_body.Length(); } + void GetBody(nsCString& dest) const; + inline const char* GetBodyCharset(void) const { return m_mimeCharset.get(); } + inline bool IsRead() const { return m_msgFlags & MSGFLAG_READ; } + inline bool IsReplied() const { + return (m_msgLastVerb == EXCHIVERB_REPLYTOSENDER) || + (m_msgLastVerb == EXCHIVERB_REPLYTOALL); + } + inline bool IsForvarded() const { return m_msgLastVerb == EXCHIVERB_FORWARD; } + + bool HasContentHeader(void) const { return !m_mimeContentType.IsEmpty(); } + bool HasMimeVersion(void) const { + return m_headers.Value(CMapiMessageHeaders::hdrMimeVersion); + } + const char* GetMimeContent(void) const { return m_mimeContentType.get(); } + int32_t GetMimeContentLen(void) const { return m_mimeContentType.Length(); } + const char* GetMimeBoundary(void) const { return m_mimeBoundary.get(); } + + // The only required part of a message is its header + inline bool ValidState() const { return !m_headers.IsEmpty(); } + inline bool FullMessageDownloaded() const { return !m_dldStateHeadersOnly; } + + private: + struct attach_data { + nsCOMPtr orig_url; + nsCOMPtr tmp_file; + char* type; + char* encoding; + char* real_name; + char* cid; + bool delete_file; + attach_data() + : type(0), encoding(0), real_name(0), cid(0), delete_file(false) {} + }; + + static const nsCString m_whitespace; + + LPMESSAGE m_lpMsg; + + bool m_dldStateHeadersOnly; // if the message has not been downloaded yet + CMapiMessageHeaders m_headers; + nsCString m_fromLine; // utf-8 + nsCString m_mimeContentType; // utf-8 + nsCString m_mimeBoundary; // utf-8 + nsCString m_mimeCharset; // utf-8 + + std::vector m_stdattachments; + std::vector m_embattachments; // Embedded + + nsString m_body; // to be converted from UTF-16 using m_mimeCharset + bool m_bodyIsHtml; + + uint32_t m_msgFlags; + uint32_t m_msgLastVerb; + + nsCOMPtr m_pIOService; + + void GetDownloadState(); + + // 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 FetchHeaders(void); + bool FetchBody(void); + void FetchFlags(void); + + static bool GetTmpFile(/*out*/ nsIFile** aResult); + static bool CopyMsgAttachToFile(LPATTACH lpAttach, + /*out*/ nsIFile** tmp_file); + static bool CopyBinAttachToFile(LPATTACH lpAttach, nsIFile** tmp_file); + + static void ClearAttachment(attach_data* data); + void ClearAttachments(); + bool AddAttachment(DWORD aNum); + bool IterateAttachTable(LPMAPITABLE tbl); + bool GetURL(nsIFile* aFile, nsIURI** url); + void ProcessAttachments(); + + bool EnsureHeader(CMapiMessageHeaders::SpecialHeader special, ULONG mapiTag); + bool EnsureDate(); + + void ProcessContentType(); + bool CheckBodyInCharsetRange(const char* charset); + void FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ = true); + void BuildFromLine(void); + + inline static bool IsSpace(char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\b' || c == '\t'; + } + inline static bool IsSpace(wchar_t c) { + return ((c & 0xFF) == c) && IsSpace(static_cast(c)); + } // Avoid false detections +}; + +#endif /* MapiMessage_h__ */ diff --git a/comm/mailnews/import/src/MapiMimeTypes.cpp b/comm/mailnews/import/src/MapiMimeTypes.cpp new file mode 100644 index 0000000000..554c5694d9 --- /dev/null +++ b/comm/mailnews/import/src/MapiMimeTypes.cpp @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsString.h" +#include "MapiMimeTypes.h" + +uint8_t CMimeTypes::m_mimeBuffer[kMaxMimeTypeSize]; + +BOOL CMimeTypes::GetKey(HKEY root, LPCWSTR pName, PHKEY pKey) { + LONG result = RegOpenKeyExW(root, pName, 0, + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, pKey); + return result == ERROR_SUCCESS; +} + +BOOL CMimeTypes::GetValueBytes(HKEY rootKey, LPCWSTR pValName, + LPBYTE* ppBytes) { + LONG err; + DWORD bufSz; + + *ppBytes = NULL; + // Get the installed directory + err = RegQueryValueExW(rootKey, pValName, NULL, NULL, NULL, &bufSz); + if (err == ERROR_SUCCESS) { + *ppBytes = new BYTE[bufSz]; + err = RegQueryValueExW(rootKey, pValName, NULL, NULL, *ppBytes, &bufSz); + if (err == ERROR_SUCCESS) { + return TRUE; + } + delete *ppBytes; + *ppBytes = NULL; + } + return FALSE; +} + +void CMimeTypes::ReleaseValueBytes(LPBYTE pBytes) { + if (pBytes) delete pBytes; +} + +BOOL CMimeTypes::GetMimeTypeFromReg(const nsString& ext, LPBYTE* ppBytes) { + HKEY extensionKey; + BOOL result = FALSE; + *ppBytes = NULL; + if (GetKey(HKEY_CLASSES_ROOT, ext.get(), &extensionKey)) { + result = GetValueBytes(extensionKey, L"Content Type", ppBytes); + RegCloseKey(extensionKey); + } + + return result; +} + +uint8_t* CMimeTypes::GetMimeType(const nsString& theExt) { + nsString ext = theExt; + if (ext.Length()) { + if (ext.First() != '.') { + ext = L"."; + ext += theExt; + } + } + + BOOL result = FALSE; + int len; + + if (!ext.Length()) return NULL; + LPBYTE pByte; + if (GetMimeTypeFromReg(ext, &pByte)) { + len = strlen((const char*)pByte); + if (len && (len < kMaxMimeTypeSize)) { + memcpy(m_mimeBuffer, pByte, len); + m_mimeBuffer[len] = 0; + result = TRUE; + } + ReleaseValueBytes(pByte); + } + + if (result) return m_mimeBuffer; + + return NULL; +} diff --git a/comm/mailnews/import/src/MapiMimeTypes.h b/comm/mailnews/import/src/MapiMimeTypes.h new file mode 100644 index 0000000000..d870893559 --- /dev/null +++ b/comm/mailnews/import/src/MapiMimeTypes.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MapiMimeTypes_h___ +#define MapiMimeTypes_h___ + +#include + +#define kMaxMimeTypeSize 256 + +class CMimeTypes { + public: + static uint8_t* GetMimeType(const nsString& theExt); + + protected: + // Registry stuff + static BOOL GetKey(HKEY root, LPCWSTR pName, PHKEY pKey); + static BOOL GetValueBytes(HKEY rootKey, LPCWSTR pValName, LPBYTE* ppBytes); + static void ReleaseValueBytes(LPBYTE pBytes); + static BOOL GetMimeTypeFromReg(const nsString& ext, LPBYTE* ppBytes); + + static uint8_t m_mimeBuffer[kMaxMimeTypeSize]; +}; + +#endif /* MapiMimeTypes_h__ */ diff --git a/comm/mailnews/import/src/MapiTagStrs.cpp b/comm/mailnews/import/src/MapiTagStrs.cpp new file mode 100644 index 0000000000..43a796ae24 --- /dev/null +++ b/comm/mailnews/import/src/MapiTagStrs.cpp @@ -0,0 +1,1473 @@ + /* -*- 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/. */ + + /* + * Message envelope properties + */ + +case PR_ACKNOWLEDGEMENT_MODE: + s = "PR_ACKNOWLEDGEMENT_MODE"; + break; +case PR_ALTERNATE_RECIPIENT_ALLOWED: + s = "PR_ALTERNATE_RECIPIENT_ALLOWED"; + break; +case PR_AUTHORIZING_USERS: + s = "PR_AUTHORIZING_USERS"; + break; +case PR_AUTO_FORWARD_COMMENT: + s = "PR_AUTO_FORWARD_COMMENT"; + break; +case PR_AUTO_FORWARDED: + s = "PR_AUTO_FORWARDED"; + break; +case PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID: + s = "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID"; + break; +case PR_CONTENT_CORRELATOR: + s = "PR_CONTENT_CORRELATOR"; + break; +case PR_CONTENT_IDENTIFIER: + s = "PR_CONTENT_IDENTIFIER"; + break; +case PR_CONTENT_LENGTH: + s = "PR_CONTENT_LENGTH"; + break; +case PR_CONTENT_RETURN_REQUESTED: + s = "PR_CONTENT_RETURN_REQUESTED"; + break; + +case PR_CONVERSATION_KEY: + s = "PR_CONVERSATION_KEY"; + break; + +case PR_CONVERSION_EITS: + s = "PR_CONVERSION_EITS"; + break; +case PR_CONVERSION_WITH_LOSS_PROHIBITED: + s = "PR_CONVERSION_WITH_LOSS_PROHIBITED"; + break; +case PR_CONVERTED_EITS: + s = "PR_CONVERTED_EITS"; + break; +case PR_DEFERRED_DELIVERY_TIME: + s = "PR_DEFERRED_DELIVERY_TIME"; + break; +case PR_DELIVER_TIME: + s = "PR_DELIVER_TIME"; + break; +case PR_DISCARD_REASON: + s = "PR_DISCARD_REASON"; + break; +case PR_DISCLOSURE_OF_RECIPIENTS: + s = "PR_DISCLOSURE_OF_RECIPIENTS"; + break; +case PR_DL_EXPANSION_HISTORY: + s = "PR_DL_EXPANSION_HISTORY"; + break; +case PR_DL_EXPANSION_PROHIBITED: + s = "PR_DL_EXPANSION_PROHIBITED"; + break; +case PR_EXPIRY_TIME: + s = "PR_EXPIRY_TIME"; + break; +case PR_IMPLICIT_CONVERSION_PROHIBITED: + s = "PR_IMPLICIT_CONVERSION_PROHIBITED"; + break; +case PR_IMPORTANCE: + s = "PR_IMPORTANCE"; + break; +case PR_IPM_ID: + s = "PR_IPM_ID"; + break; +case PR_LATEST_DELIVERY_TIME: + s = "PR_LATEST_DELIVERY_TIME"; + break; +case PR_MESSAGE_CLASS: + s = "PR_MESSAGE_CLASS"; + break; +case PR_MESSAGE_DELIVERY_ID: + s = "PR_MESSAGE_DELIVERY_ID"; + break; + +case PR_MESSAGE_SECURITY_LABEL: + s = "PR_MESSAGE_SECURITY_LABEL"; + break; +case PR_OBSOLETED_IPMS: + s = "PR_OBSOLETED_IPMS"; + break; +case PR_ORIGINALLY_INTENDED_RECIPIENT_NAME: + s = "PR_ORIGINALLY_INTENDED_RECIPIENT_NAME"; + break; +case PR_ORIGINAL_EITS: + s = "PR_ORIGINAL_EITS"; + break; +case PR_ORIGINATOR_CERTIFICATE: + s = "PR_ORIGINATOR_CERTIFICATE"; + break; +case PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED: + s = "PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED"; + break; +case PR_ORIGINATOR_RETURN_ADDRESS: + s = "PR_ORIGINATOR_RETURN_ADDRESS"; + break; + +case PR_PARENT_KEY: + s = "PR_PARENT_KEY"; + break; +case PR_PRIORITY: + s = "PR_PRIORITY"; + break; + +case PR_ORIGIN_CHECK: + s = "PR_ORIGIN_CHECK"; + break; +case PR_PROOF_OF_SUBMISSION_REQUESTED: + s = "PR_PROOF_OF_SUBMISSION_REQUESTED"; + break; +case PR_READ_RECEIPT_REQUESTED: + s = "PR_READ_RECEIPT_REQUESTED"; + break; +case PR_RECEIPT_TIME: + s = "PR_RECEIPT_TIME"; + break; +case PR_RECIPIENT_REASSIGNMENT_PROHIBITED: + s = "PR_RECIPIENT_REASSIGNMENT_PROHIBITED"; + break; +case PR_REDIRECTION_HISTORY: + s = "PR_REDIRECTION_HISTORY"; + break; +case PR_RELATED_IPMS: + s = "PR_RELATED_IPMS"; + break; +case PR_ORIGINAL_SENSITIVITY: + s = "PR_ORIGINAL_SENSITIVITY"; + break; +case PR_LANGUAGES: + s = "PR_LANGUAGES"; + break; +case PR_REPLY_TIME: + s = "PR_REPLY_TIME"; + break; +case PR_REPORT_TAG: + s = "PR_REPORT_TAG"; + break; +case PR_REPORT_TIME: + s = "PR_REPORT_TIME"; + break; +case PR_RETURNED_IPM: + s = "PR_RETURNED_IPM"; + break; +case PR_SECURITY: + s = "PR_SECURITY"; + break; +case PR_INCOMPLETE_COPY: + s = "PR_INCOMPLETE_COPY"; + break; +case PR_SENSITIVITY: + s = "PR_SENSITIVITY"; + break; +case PR_SUBJECT: + s = "PR_SUBJECT"; + break; +case PR_SUBJECT_IPM: + s = "PR_SUBJECT_IPM"; + break; +case PR_CLIENT_SUBMIT_TIME: + s = "PR_CLIENT_SUBMIT_TIME"; + break; +case PR_REPORT_NAME: + s = "PR_REPORT_NAME"; + break; +case PR_SENT_REPRESENTING_SEARCH_KEY: + s = "PR_SENT_REPRESENTING_SEARCH_KEY"; + break; +case PR_X400_CONTENT_TYPE: + s = "PR_X400_CONTENT_TYPE"; + break; +case PR_SUBJECT_PREFIX: + s = "PR_SUBJECT_PREFIX"; + break; +case PR_NON_RECEIPT_REASON: + s = "PR_NON_RECEIPT_REASON"; + break; +case PR_RECEIVED_BY_ENTRYID: + s = "PR_RECEIVED_BY_ENTRYID"; + break; +case PR_RECEIVED_BY_NAME: + s = "PR_RECEIVED_BY_NAME"; + break; +case PR_SENT_REPRESENTING_ENTRYID: + s = "PR_SENT_REPRESENTING_ENTRYID"; + break; +case PR_SENT_REPRESENTING_NAME: + s = "PR_SENT_REPRESENTING_NAME"; + break; +case PR_RCVD_REPRESENTING_ENTRYID: + s = "PR_RCVD_REPRESENTING_ENTRYID"; + break; +case PR_RCVD_REPRESENTING_NAME: + s = "PR_RCVD_REPRESENTING_NAME"; + break; +case PR_REPORT_ENTRYID: + s = "PR_REPORT_ENTRYID"; + break; +case PR_READ_RECEIPT_ENTRYID: + s = "PR_READ_RECEIPT_ENTRYID"; + break; +case PR_MESSAGE_SUBMISSION_ID: + s = "PR_MESSAGE_SUBMISSION_ID"; + break; +case PR_PROVIDER_SUBMIT_TIME: + s = "PR_PROVIDER_SUBMIT_TIME"; + break; +case PR_ORIGINAL_SUBJECT: + s = "PR_ORIGINAL_SUBJECT"; + break; +case PR_DISC_VAL: + s = "PR_DISC_VAL"; + break; +case PR_ORIG_MESSAGE_CLASS: + s = "PR_ORIG_MESSAGE_CLASS"; + break; +case PR_ORIGINAL_AUTHOR_ENTRYID: + s = "PR_ORIGINAL_AUTHOR_ENTRYID"; + break; +case PR_ORIGINAL_AUTHOR_NAME: + s = "PR_ORIGINAL_AUTHOR_NAME"; + break; +case PR_ORIGINAL_SUBMIT_TIME: + s = "PR_ORIGINAL_SUBMIT_TIME"; + break; +case PR_REPLY_RECIPIENT_ENTRIES: + s = "PR_REPLY_RECIPIENT_ENTRIES"; + break; +case PR_REPLY_RECIPIENT_NAMES: + s = "PR_REPLY_RECIPIENT_NAMES"; + break; + +case PR_RECEIVED_BY_SEARCH_KEY: + s = "PR_RECEIVED_BY_SEARCH_KEY"; + break; +case PR_RCVD_REPRESENTING_SEARCH_KEY: + s = "PR_RCVD_REPRESENTING_SEARCH_KEY"; + break; +case PR_READ_RECEIPT_SEARCH_KEY: + s = "PR_READ_RECEIPT_SEARCH_KEY"; + break; +case PR_REPORT_SEARCH_KEY: + s = "PR_REPORT_SEARCH_KEY"; + break; +case PR_ORIGINAL_DELIVERY_TIME: + s = "PR_ORIGINAL_DELIVERY_TIME"; + break; +case PR_ORIGINAL_AUTHOR_SEARCH_KEY: + s = "PR_ORIGINAL_AUTHOR_SEARCH_KEY"; + break; + +case PR_MESSAGE_TO_ME: + s = "PR_MESSAGE_TO_ME"; + break; +case PR_MESSAGE_CC_ME: + s = "PR_MESSAGE_CC_ME"; + break; +case PR_MESSAGE_RECIP_ME: + s = "PR_MESSAGE_RECIP_ME"; + break; + +case PR_ORIGINAL_SENDER_NAME: + s = "PR_ORIGINAL_SENDER_NAME"; + break; +case PR_ORIGINAL_SENDER_ENTRYID: + s = "PR_ORIGINAL_SENDER_ENTRYID"; + break; +case PR_ORIGINAL_SENDER_SEARCH_KEY: + s = "PR_ORIGINAL_SENDER_SEARCH_KEY"; + break; +case PR_ORIGINAL_SENT_REPRESENTING_NAME: + s = "PR_ORIGINAL_SENT_REPRESENTING_NAME"; + break; +case PR_ORIGINAL_SENT_REPRESENTING_ENTRYID: + s = "PR_ORIGINAL_SENT_REPRESENTING_ENTRYID"; + break; +case PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY: + s = "PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY"; + break; + +case PR_START_DATE: + s = "PR_START_DATE"; + break; +case PR_END_DATE: + s = "PR_END_DATE"; + break; +case PR_OWNER_APPT_ID: + s = "PR_OWNER_APPT_ID"; + break; +case PR_RESPONSE_REQUESTED: + s = "PR_RESPONSE_REQUESTED"; + break; + +case PR_SENT_REPRESENTING_ADDRTYPE: + s = "PR_SENT_REPRESENTING_ADDRTYPE"; + break; +case PR_SENT_REPRESENTING_EMAIL_ADDRESS: + s = "PR_SENT_REPRESENTING_EMAIL_ADDRESS"; + break; + +case PR_ORIGINAL_SENDER_ADDRTYPE: + s = "PR_ORIGINAL_SENDER_ADDRTYPE"; + break; +case PR_ORIGINAL_SENDER_EMAIL_ADDRESS: + s = "PR_ORIGINAL_SENDER_EMAIL_ADDRESS"; + break; + +case PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE: + s = "PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE"; + break; +case PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS: + s = "PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS"; + break; + +case PR_CONVERSATION_TOPIC: + s = "PR_CONVERSATION_TOPIC"; + break; +case PR_CONVERSATION_INDEX: + s = "PR_CONVERSATION_INDEX"; + break; + +case PR_ORIGINAL_DISPLAY_BCC: + s = "PR_ORIGINAL_DISPLAY_BCC"; + break; +case PR_ORIGINAL_DISPLAY_CC: + s = "PR_ORIGINAL_DISPLAY_CC"; + break; +case PR_ORIGINAL_DISPLAY_TO: + s = "PR_ORIGINAL_DISPLAY_TO"; + break; + +case PR_RECEIVED_BY_ADDRTYPE: + s = "PR_RECEIVED_BY_ADDRTYPE"; + break; +case PR_RECEIVED_BY_EMAIL_ADDRESS: + s = "PR_RECEIVED_BY_EMAIL_ADDRESS"; + break; + +case PR_RCVD_REPRESENTING_ADDRTYPE: + s = "PR_RCVD_REPRESENTING_ADDRTYPE"; + break; +case PR_RCVD_REPRESENTING_EMAIL_ADDRESS: + s = "PR_RCVD_REPRESENTING_EMAIL_ADDRESS"; + break; + +case PR_ORIGINAL_AUTHOR_ADDRTYPE: + s = "PR_ORIGINAL_AUTHOR_ADDRTYPE"; + break; +case PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS: + s = "PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS"; + break; + +case PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE: + s = "PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE"; + break; +case PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS: + s = "PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS"; + break; + +case PR_TRANSPORT_MESSAGE_HEADERS: + s = "PR_TRANSPORT_MESSAGE_HEADERS"; + break; + +case PR_DELEGATION: + s = "PR_DELEGATION"; + break; + +case PR_TNEF_CORRELATION_KEY: + s = "PR_TNEF_CORRELATION_KEY"; + break; + + /* + * Message content properties + */ + +case PR_BODY: + s = "PR_BODY"; + break; +case PR_REPORT_TEXT: + s = "PR_REPORT_TEXT"; + break; +case PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY: + s = "PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY"; + break; +case PR_REPORTING_DL_NAME: + s = "PR_REPORTING_DL_NAME"; + break; +case PR_REPORTING_MTA_CERTIFICATE: + s = "PR_REPORTING_MTA_CERTIFICATE"; + break; + + /* Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use + * PR_ORIGIN_CHECK */ + +case PR_RTF_SYNC_BODY_CRC: + s = "PR_RTF_SYNC_BODY_CRC"; + break; +case PR_RTF_SYNC_BODY_COUNT: + s = "PR_RTF_SYNC_BODY_COUNT"; + break; +case PR_RTF_SYNC_BODY_TAG: + s = "PR_RTF_SYNC_BODY_TAG"; + break; +case PR_RTF_COMPRESSED: + s = "PR_RTF_COMPRESSED"; + break; +case PR_RTF_SYNC_PREFIX_COUNT: + s = "PR_RTF_SYNC_PREFIX_COUNT"; + break; +case PR_RTF_SYNC_TRAILING_COUNT: + s = "PR_RTF_SYNC_TRAILING_COUNT"; + break; +case PR_ORIGINALLY_INTENDED_RECIP_ENTRYID: + s = "PR_ORIGINALLY_INTENDED_RECIP_ENTRYID"; + break; + + /* + * Reserved 0x1100-0x1200 + */ + + /* + * Message recipient properties + */ + +case PR_CONTENT_INTEGRITY_CHECK: + s = "PR_CONTENT_INTEGRITY_CHECK"; + break; +case PR_EXPLICIT_CONVERSION: + s = "PR_EXPLICIT_CONVERSION"; + break; +case PR_IPM_RETURN_REQUESTED: + s = "PR_IPM_RETURN_REQUESTED"; + break; +case PR_MESSAGE_TOKEN: + s = "PR_MESSAGE_TOKEN"; + break; +case PR_NDR_REASON_CODE: + s = "PR_NDR_REASON_CODE"; + break; +case PR_NDR_DIAG_CODE: + s = "PR_NDR_DIAG_CODE"; + break; +case PR_NON_RECEIPT_NOTIFICATION_REQUESTED: + s = "PR_NON_RECEIPT_NOTIFICATION_REQUESTED"; + break; +case PR_DELIVERY_POINT: + s = "PR_DELIVERY_POINT"; + break; + +case PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED: + s = "PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED"; + break; +case PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT: + s = "PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT"; + break; +case PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY: + s = "PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY"; + break; +case PR_PHYSICAL_DELIVERY_MODE: + s = "PR_PHYSICAL_DELIVERY_MODE"; + break; +case PR_PHYSICAL_DELIVERY_REPORT_REQUEST: + s = "PR_PHYSICAL_DELIVERY_REPORT_REQUEST"; + break; +case PR_PHYSICAL_FORWARDING_ADDRESS: + s = "PR_PHYSICAL_FORWARDING_ADDRESS"; + break; +case PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED: + s = "PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED"; + break; +case PR_PHYSICAL_FORWARDING_PROHIBITED: + s = "PR_PHYSICAL_FORWARDING_PROHIBITED"; + break; +case PR_PHYSICAL_RENDITION_ATTRIBUTES: + s = "PR_PHYSICAL_RENDITION_ATTRIBUTES"; + break; +case PR_PROOF_OF_DELIVERY: + s = "PR_PROOF_OF_DELIVERY"; + break; +case PR_PROOF_OF_DELIVERY_REQUESTED: + s = "PR_PROOF_OF_DELIVERY_REQUESTED"; + break; +case PR_RECIPIENT_CERTIFICATE: + s = "PR_RECIPIENT_CERTIFICATE"; + break; +case PR_RECIPIENT_NUMBER_FOR_ADVICE: + s = "PR_RECIPIENT_NUMBER_FOR_ADVICE"; + break; +case PR_RECIPIENT_TYPE: + s = "PR_RECIPIENT_TYPE"; + break; +case PR_REGISTERED_MAIL_TYPE: + s = "PR_REGISTERED_MAIL_TYPE"; + break; +case PR_REPLY_REQUESTED: + s = "PR_REPLY_REQUESTED"; + break; +case PR_REQUESTED_DELIVERY_METHOD: + s = "PR_REQUESTED_DELIVERY_METHOD"; + break; +case PR_SENDER_ENTRYID: + s = "PR_SENDER_ENTRYID"; + break; +case PR_SENDER_NAME: + s = "PR_SENDER_NAME"; + break; +case PR_SUPPLEMENTARY_INFO: + s = "PR_SUPPLEMENTARY_INFO"; + break; +case PR_TYPE_OF_MTS_USER: + s = "PR_TYPE_OF_MTS_USER"; + break; +case PR_SENDER_SEARCH_KEY: + s = "PR_SENDER_SEARCH_KEY"; + break; +case PR_SENDER_ADDRTYPE: + s = "PR_SENDER_ADDRTYPE"; + break; +case PR_SENDER_EMAIL_ADDRESS: + s = "PR_SENDER_EMAIL_ADDRESS"; + break; + + /* + * Message non-transmittable properties + */ + + /* + * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS, + * are to be used in the exclude list passed to + * IMessage::CopyTo when the caller wants either the recipients or attachments + * of the message to not get copied. It is also used in the ProblemArray + * return from IMessage::CopyTo when an error is encountered copying them + */ + +case PR_CURRENT_VERSION: + s = "PR_CURRENT_VERSION"; + break; +case PR_DELETE_AFTER_SUBMIT: + s = "PR_DELETE_AFTER_SUBMIT"; + break; +case PR_DISPLAY_BCC: + s = "PR_DISPLAY_BCC"; + break; +case PR_DISPLAY_CC: + s = "PR_DISPLAY_CC"; + break; +case PR_DISPLAY_TO: + s = "PR_DISPLAY_TO"; + break; +case PR_PARENT_DISPLAY: + s = "PR_PARENT_DISPLAY"; + break; +case PR_MESSAGE_DELIVERY_TIME: + s = "PR_MESSAGE_DELIVERY_TIME"; + break; +case PR_MESSAGE_FLAGS: + s = "PR_MESSAGE_FLAGS"; + break; +case PR_MESSAGE_SIZE: + s = "PR_MESSAGE_SIZE"; + break; +case PR_PARENT_ENTRYID: + s = "PR_PARENT_ENTRYID"; + break; +case PR_SENTMAIL_ENTRYID: + s = "PR_SENTMAIL_ENTRYID"; + break; +case PR_CORRELATE: + s = "PR_CORRELATE"; + break; +case PR_CORRELATE_MTSID: + s = "PR_CORRELATE_MTSID"; + break; +case PR_DISCRETE_VALUES: + s = "PR_DISCRETE_VALUES"; + break; +case PR_RESPONSIBILITY: + s = "PR_RESPONSIBILITY"; + break; +case PR_SPOOLER_STATUS: + s = "PR_SPOOLER_STATUS"; + break; +case PR_TRANSPORT_STATUS: + s = "PR_TRANSPORT_STATUS"; + break; +case PR_MESSAGE_RECIPIENTS: + s = "PR_MESSAGE_RECIPIENTS"; + break; +case PR_MESSAGE_ATTACHMENTS: + s = "PR_MESSAGE_ATTACHMENTS"; + break; +case PR_SUBMIT_FLAGS: + s = "PR_SUBMIT_FLAGS"; + break; +case PR_RECIPIENT_STATUS: + s = "PR_RECIPIENT_STATUS"; + break; +case PR_TRANSPORT_KEY: + s = "PR_TRANSPORT_KEY"; + break; +case PR_MSG_STATUS: + s = "PR_MSG_STATUS"; + break; +case PR_MESSAGE_DOWNLOAD_TIME: + s = "PR_MESSAGE_DOWNLOAD_TIME"; + break; +case PR_CREATION_VERSION: + s = "PR_CREATION_VERSION"; + break; +case PR_MODIFY_VERSION: + s = "PR_MODIFY_VERSION"; + break; +case PR_HASATTACH: + s = "PR_HASATTACH"; + break; +case PR_BODY_CRC: + s = "PR_BODY_CRC"; + break; +case PR_NORMALIZED_SUBJECT: + s = "PR_NORMALIZED_SUBJECT"; + break; +case PR_RTF_IN_SYNC: + s = "PR_RTF_IN_SYNC"; + break; +case PR_ATTACH_SIZE: + s = "PR_ATTACH_SIZE"; + break; +case PR_ATTACH_NUM: + s = "PR_ATTACH_NUM"; + break; +case PR_PREPROCESS: + s = "PR_PREPROCESS"; + break; + + /* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95 + */ + +case PR_ORIGINATING_MTA_CERTIFICATE: + s = "PR_ORIGINATING_MTA_CERTIFICATE"; + break; +case PR_PROOF_OF_SUBMISSION: + s = "PR_PROOF_OF_SUBMISSION"; + break; + + /* + * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF) + * is further broken down into ranges to make assigning new property IDs + * easier. + * + * From To Kind of property + * -------------------------------- + * 3000 32FF MAPI_defined common property + * 3200 33FF MAPI_defined form property + * 3400 35FF MAPI_defined message store property + * 3600 36FF MAPI_defined Folder or AB Container property + * 3700 38FF MAPI_defined attachment property + * 3900 39FF MAPI_defined address book property + * 3A00 3BFF MAPI_defined mailuser property + * 3C00 3CFF MAPI_defined DistList property + * 3D00 3DFF MAPI_defined Profile Section property + * 3E00 3EFF MAPI_defined Status property + * 3F00 3FFF MAPI_defined display table property + */ + + /* + * Properties common to numerous MAPI objects. + * + * Those properties that can appear on messages are in the + * non-transmittable range for messages. They start at the high + * end of that range and work down. + * + * Properties that never appear on messages are defined in the common + * property range (see above). + */ + + /* + * properties that are common to multiple objects (including message objects) + * -- these ids are in the non-transmittable range + */ + +case PR_ENTRYID: + s = "PR_ENTRYID"; + break; +case PR_OBJECT_TYPE: + s = "PR_OBJECT_TYPE"; + break; +case PR_ICON: + s = "PR_ICON"; + break; +case PR_MINI_ICON: + s = "PR_MINI_ICON"; + break; +case PR_STORE_ENTRYID: + s = "PR_STORE_ENTRYID"; + break; +case PR_STORE_RECORD_KEY: + s = "PR_STORE_RECORD_KEY"; + break; +case PR_RECORD_KEY: + s = "PR_RECORD_KEY"; + break; +case PR_MAPPING_SIGNATURE: + s = "PR_MAPPING_SIGNATURE"; + break; +case PR_ACCESS_LEVEL: + s = "PR_ACCESS_LEVEL"; + break; +case PR_INSTANCE_KEY: + s = "PR_INSTANCE_KEY"; + break; +case PR_ROW_TYPE: + s = "PR_ROW_TYPE"; + break; +case PR_ACCESS: + s = "PR_ACCESS"; + break; + + /* + * properties that are common to multiple objects (usually not including + * message objects) + * -- these ids are in the transmittable range + */ + +case PR_ROWID: + s = "PR_ROWID"; + break; +case PR_DISPLAY_NAME: + s = "PR_DISPLAY_NAME"; + break; +case PR_ADDRTYPE: + s = "PR_ADDRTYPE"; + break; +case PR_EMAIL_ADDRESS: + s = "PR_EMAIL_ADDRESS"; + break; +case PR_COMMENT: + s = "PR_COMMENT"; + break; +case PR_DEPTH: + s = "PR_DEPTH"; + break; +case PR_PROVIDER_DISPLAY: + s = "PR_PROVIDER_DISPLAY"; + break; +case PR_CREATION_TIME: + s = "PR_CREATION_TIME"; + break; +case PR_LAST_MODIFICATION_TIME: + s = "PR_LAST_MODIFICATION_TIME"; + break; +case PR_RESOURCE_FLAGS: + s = "PR_RESOURCE_FLAGS"; + break; +case PR_PROVIDER_DLL_NAME: + s = "PR_PROVIDER_DLL_NAME"; + break; +case PR_SEARCH_KEY: + s = "PR_SEARCH_KEY"; + break; +case PR_PROVIDER_UID: + s = "PR_PROVIDER_UID"; + break; +case PR_PROVIDER_ORDINAL: + s = "PR_PROVIDER_ORDINAL"; + break; + +/* + * MAPI Form properties + */ +case PR_FORM_VERSION: + s = "PR_FORM_VERSION"; + break; +case PR_FORM_CLSID: + s = "PR_FORM_CLSID"; + break; +case PR_FORM_CONTACT_NAME: + s = "PR_FORM_CONTACT_NAME"; + break; +case PR_FORM_CATEGORY: + s = "PR_FORM_CATEGORY"; + break; +case PR_FORM_CATEGORY_SUB: + s = "PR_FORM_CATEGORY_SUB"; + break; +case PR_FORM_HOST_MAP: + s = "PR_FORM_HOST_MAP"; + break; +case PR_FORM_HIDDEN: + s = "PR_FORM_HIDDEN"; + break; +case PR_FORM_DESIGNER_NAME: + s = "PR_FORM_DESIGNER_NAME"; + break; +case PR_FORM_DESIGNER_GUID: + s = "PR_FORM_DESIGNER_GUID"; + break; +case PR_FORM_MESSAGE_BEHAVIOR: + s = "PR_FORM_MESSAGE_BEHAVIOR"; + break; + + /* + * Message store properties + */ + +case PR_DEFAULT_STORE: + s = "PR_DEFAULT_STORE"; + break; +case PR_STORE_SUPPORT_MASK: + s = "PR_STORE_SUPPORT_MASK"; + break; +case PR_STORE_STATE: + s = "PR_STORE_STATE"; + break; + +case PR_IPM_SUBTREE_SEARCH_KEY: + s = "PR_IPM_SUBTREE_SEARCH_KEY"; + break; +case PR_IPM_OUTBOX_SEARCH_KEY: + s = "PR_IPM_OUTBOX_SEARCH_KEY"; + break; +case PR_IPM_WASTEBASKET_SEARCH_KEY: + s = "PR_IPM_WASTEBASKET_SEARCH_KEY"; + break; +case PR_IPM_SENTMAIL_SEARCH_KEY: + s = "PR_IPM_SENTMAIL_SEARCH_KEY"; + break; +case PR_MDB_PROVIDER: + s = "PR_MDB_PROVIDER"; + break; +case PR_RECEIVE_FOLDER_SETTINGS: + s = "PR_RECEIVE_FOLDER_SETTINGS"; + break; + +case PR_VALID_FOLDER_MASK: + s = "PR_VALID_FOLDER_MASK"; + break; +case PR_IPM_SUBTREE_ENTRYID: + s = "PR_IPM_SUBTREE_ENTRYID"; + break; + +case PR_IPM_OUTBOX_ENTRYID: + s = "PR_IPM_OUTBOX_ENTRYID"; + break; +case PR_IPM_WASTEBASKET_ENTRYID: + s = "PR_IPM_WASTEBASKET_ENTRYID"; + break; +case PR_IPM_SENTMAIL_ENTRYID: + s = "PR_IPM_SENTMAIL_ENTRYID"; + break; +case PR_VIEWS_ENTRYID: + s = "PR_VIEWS_ENTRYID"; + break; +case PR_COMMON_VIEWS_ENTRYID: + s = "PR_COMMON_VIEWS_ENTRYID"; + break; +case PR_FINDER_ENTRYID: + s = "PR_FINDER_ENTRYID"; + break; + + /* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by + * PR_VALID_FOLDER_MASK */ + + /* + * Folder and AB Container properties + */ + +case PR_CONTAINER_FLAGS: + s = "PR_CONTAINER_FLAGS"; + break; +case PR_FOLDER_TYPE: + s = "PR_FOLDER_TYPE"; + break; +case PR_CONTENT_COUNT: + s = "PR_CONTENT_COUNT"; + break; +case PR_CONTENT_UNREAD: + s = "PR_CONTENT_UNREAD"; + break; +case PR_CREATE_TEMPLATES: + s = "PR_CREATE_TEMPLATES"; + break; +case PR_DETAILS_TABLE: + s = "PR_DETAILS_TABLE"; + break; +case PR_SEARCH: + s = "PR_SEARCH"; + break; +case PR_SELECTABLE: + s = "PR_SELECTABLE"; + break; +case PR_SUBFOLDERS: + s = "PR_SUBFOLDERS"; + break; +case PR_STATUS: + s = "PR_STATUS"; + break; +case PR_ANR: + s = "PR_ANR"; + break; +case PR_CONTENTS_SORT_ORDER: + s = "PR_CONTENTS_SORT_ORDER"; + break; +case PR_CONTAINER_HIERARCHY: + s = "PR_CONTAINER_HIERARCHY"; + break; +case PR_CONTAINER_CONTENTS: + s = "PR_CONTAINER_CONTENTS"; + break; +case PR_FOLDER_ASSOCIATED_CONTENTS: + s = "PR_FOLDER_ASSOCIATED_CONTENTS"; + break; +case PR_DEF_CREATE_DL: + s = "PR_DEF_CREATE_DL"; + break; +case PR_DEF_CREATE_MAILUSER: + s = "PR_DEF_CREATE_MAILUSER"; + break; +case PR_CONTAINER_CLASS: + s = "PR_CONTAINER_CLASS"; + break; +case PR_CONTAINER_MODIFY_VERSION: + s = "PR_CONTAINER_MODIFY_VERSION"; + break; +case PR_AB_PROVIDER_ID: + s = "PR_AB_PROVIDER_ID"; + break; +case PR_DEFAULT_VIEW_ENTRYID: + s = "PR_DEFAULT_VIEW_ENTRYID"; + break; +case PR_ASSOC_CONTENT_COUNT: + s = "PR_ASSOC_CONTENT_COUNT"; + break; + + /* Reserved 0x36C0-0x36FF */ + + /* + * Attachment properties + */ + +case PR_ATTACHMENT_X400_PARAMETERS: + s = "PR_ATTACHMENT_X400_PARAMETERS"; + break; +case PR_ATTACH_DATA_OBJ: + s = "PR_ATTACH_DATA_OBJ"; + break; +case PR_ATTACH_DATA_BIN: + s = "PR_ATTACH_DATA_BIN"; + break; +case PR_ATTACH_ENCODING: + s = "PR_ATTACH_ENCODING"; + break; +case PR_ATTACH_EXTENSION: + s = "PR_ATTACH_EXTENSION"; + break; +case PR_ATTACH_FILENAME: + s = "PR_ATTACH_FILENAME"; + break; +case PR_ATTACH_METHOD: + s = "PR_ATTACH_METHOD"; + break; +case PR_ATTACH_LONG_FILENAME: + s = "PR_ATTACH_LONG_FILENAME"; + break; +case PR_ATTACH_PATHNAME: + s = "PR_ATTACH_PATHNAME"; + break; +case PR_ATTACH_RENDERING: + s = "PR_ATTACH_RENDERING"; + break; +case PR_ATTACH_TAG: + s = "PR_ATTACH_TAG"; + break; +case PR_RENDERING_POSITION: + s = "PR_RENDERING_POSITION"; + break; +case PR_ATTACH_TRANSPORT_NAME: + s = "PR_ATTACH_TRANSPORT_NAME"; + break; +case PR_ATTACH_LONG_PATHNAME: + s = "PR_ATTACH_LONG_PATHNAME"; + break; +case PR_ATTACH_MIME_TAG: + s = "PR_ATTACH_MIME_TAG"; + break; +case PR_ATTACH_ADDITIONAL_INFO: + s = "PR_ATTACH_ADDITIONAL_INFO"; + break; + + /* + * AB Object properties + */ + +case PR_DISPLAY_TYPE: + s = "PR_DISPLAY_TYPE"; + break; +case PR_TEMPLATEID: + s = "PR_TEMPLATEID"; + break; +case PR_PRIMARY_CAPABILITY: + s = "PR_PRIMARY_CAPABILITY"; + break; + +/* + * Mail user properties + */ +case PR_7BIT_DISPLAY_NAME: + s = "PR_7BIT_DISPLAY_NAME"; + break; +case PR_ACCOUNT: + s = "PR_ACCOUNT"; + break; +case PR_ALTERNATE_RECIPIENT: + s = "PR_ALTERNATE_RECIPIENT"; + break; +case PR_CALLBACK_TELEPHONE_NUMBER: + s = "PR_CALLBACK_TELEPHONE_NUMBER"; + break; +case PR_CONVERSION_PROHIBITED: + s = "PR_CONVERSION_PROHIBITED"; + break; +case PR_DISCLOSE_RECIPIENTS: + s = "PR_DISCLOSE_RECIPIENTS"; + break; +case PR_GENERATION: + s = "PR_GENERATION"; + break; +case PR_GIVEN_NAME: + s = "PR_GIVEN_NAME"; + break; +case PR_GOVERNMENT_ID_NUMBER: + s = "PR_GOVERNMENT_ID_NUMBER"; + break; +case PR_BUSINESS_TELEPHONE_NUMBER: + s = "PR_BUSINESS_TELEPHONE_NUMBER or PR_OFFICE_TELEPHONE_NUMBER"; + break; +case PR_HOME_TELEPHONE_NUMBER: + s = "PR_HOME_TELEPHONE_NUMBER"; + break; +case PR_INITIALS: + s = "PR_INITIALS"; + break; +case PR_KEYWORD: + s = "PR_KEYWORD"; + break; +case PR_LANGUAGE: + s = "PR_LANGUAGE"; + break; +case PR_LOCATION: + s = "PR_LOCATION"; + break; +case PR_MAIL_PERMISSION: + s = "PR_MAIL_PERMISSION"; + break; +case PR_MHS_COMMON_NAME: + s = "PR_MHS_COMMON_NAME"; + break; +case PR_ORGANIZATIONAL_ID_NUMBER: + s = "PR_ORGANIZATIONAL_ID_NUMBER"; + break; +case PR_SURNAME: + s = "PR_SURNAME"; + break; +case PR_ORIGINAL_ENTRYID: + s = "PR_ORIGINAL_ENTRYID"; + break; +case PR_ORIGINAL_DISPLAY_NAME: + s = "PR_ORIGINAL_DISPLAY_NAME"; + break; +case PR_ORIGINAL_SEARCH_KEY: + s = "PR_ORIGINAL_SEARCH_KEY"; + break; +case PR_POSTAL_ADDRESS: + s = "PR_POSTAL_ADDRESS"; + break; +case PR_COMPANY_NAME: + s = "PR_COMPANY_NAME"; + break; +case PR_TITLE: + s = "PR_TITLE"; + break; +case PR_DEPARTMENT_NAME: + s = "PR_DEPARTMENT_NAME"; + break; +case PR_OFFICE_LOCATION: + s = "PR_OFFICE_LOCATION"; + break; +case PR_PRIMARY_TELEPHONE_NUMBER: + s = "PR_PRIMARY_TELEPHONE_NUMBER"; + break; +case PR_BUSINESS2_TELEPHONE_NUMBER: + s = "PR_BUSINESS2_TELEPHONE_NUMBER or PR_OFFICE2_TELEPHONE_NUMBER"; + break; +case PR_MOBILE_TELEPHONE_NUMBER: + s = "PR_MOBILE_TELEPHONE_NUMBER or PR_CELLULAR_TELEPHONE_NUMBER"; + break; +case PR_RADIO_TELEPHONE_NUMBER: + s = "PR_RADIO_TELEPHONE_NUMBER"; + break; +case PR_CAR_TELEPHONE_NUMBER: + s = "PR_CAR_TELEPHONE_NUMBER"; + break; +case PR_OTHER_TELEPHONE_NUMBER: + s = "PR_OTHER_TELEPHONE_NUMBER"; + break; +case PR_TRANSMITABLE_DISPLAY_NAME: + s = "PR_TRANSMITABLE_DISPLAY_NAME"; + break; +case PR_PAGER_TELEPHONE_NUMBER: + s = "PR_PAGER_TELEPHONE_NUMBER or PR_BEEPER_TELEPHONE_NUMBER"; + break; +case PR_USER_CERTIFICATE: + s = "PR_USER_CERTIFICATE"; + break; +case PR_PRIMARY_FAX_NUMBER: + s = "PR_PRIMARY_FAX_NUMBER"; + break; +case PR_BUSINESS_FAX_NUMBER: + s = "PR_BUSINESS_FAX_NUMBER"; + break; +case PR_HOME_FAX_NUMBER: + s = "PR_HOME_FAX_NUMBER"; + break; +case PR_COUNTRY: + s = "PR_COUNTRY or PR_BUSINESS_ADDRESS_COUNTRY"; + break; + +case PR_LOCALITY: + s = "PR_LOCALITY or PR_BUSINESS_ADDRESS_CITY"; + break; + +case PR_STATE_OR_PROVINCE: + s = "PR_STATE_OR_PROVINCE or PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE"; + break; + +case PR_STREET_ADDRESS: + s = "PR_STREET_ADDRESS or PR_BUSINESS_ADDRESS_STREET"; + break; + +case PR_POSTAL_CODE: + s = "PR_POSTAL_CODE or PR_BUSINESS_ADDRESS_POSTAL_CODE"; + break; + +case PR_POST_OFFICE_BOX: + s = "PR_POST_OFFICE_BOX or PR_BUSINESS_ADDRESS_POST_OFFICE_BOX"; + break; + +case PR_TELEX_NUMBER: + s = "PR_TELEX_NUMBER"; + break; +case PR_ISDN_NUMBER: + s = "PR_ISDN_NUMBER"; + break; +case PR_ASSISTANT_TELEPHONE_NUMBER: + s = "PR_ASSISTANT_TELEPHONE_NUMBER"; + break; +case PR_HOME2_TELEPHONE_NUMBER: + s = "PR_HOME2_TELEPHONE_NUMBER"; + break; +case PR_ASSISTANT: + s = "PR_ASSISTANT"; + break; +case PR_SEND_RICH_INFO: + s = "PR_SEND_RICH_INFO"; + break; + +case PR_WEDDING_ANNIVERSARY: + s = "PR_WEDDING_ANNIVERSARY"; + break; +case PR_BIRTHDAY: + s = "PR_BIRTHDAY"; + break; + +case PR_HOBBIES: + s = "PR_HOBBIES"; + break; + +case PR_MIDDLE_NAME: + s = "PR_MIDDLE_NAME"; + break; + +case PR_DISPLAY_NAME_PREFIX: + s = "PR_DISPLAY_NAME_PREFIX"; + break; + +case PR_PROFESSION: + s = "PR_PROFESSION"; + break; + +case PR_PREFERRED_BY_NAME: + s = "PR_PREFERRED_BY_NAME"; + break; + +case PR_SPOUSE_NAME: + s = "PR_SPOUSE_NAME"; + break; + +case PR_COMPUTER_NETWORK_NAME: + s = "PR_COMPUTER_NETWORK_NAME"; + break; + +case PR_CUSTOMER_ID: + s = "PR_CUSTOMER_ID"; + break; + +case PR_TTYTDD_PHONE_NUMBER: + s = "PR_TTYTDD_PHONE_NUMBER"; + break; + +case PR_FTP_SITE: + s = "PR_FTP_SITE"; + break; + +case PR_GENDER: + s = "PR_GENDER"; + break; + +case PR_MANAGER_NAME: + s = "PR_MANAGER_NAME"; + break; + +case PR_NICKNAME: + s = "PR_NICKNAME"; + break; + +case PR_PERSONAL_HOME_PAGE: + s = "PR_PERSONAL_HOME_PAGE"; + break; + +case PR_BUSINESS_HOME_PAGE: + s = "PR_BUSINESS_HOME_PAGE"; + break; + +case PR_CONTACT_VERSION: + s = "PR_CONTACT_VERSION"; + break; +case PR_CONTACT_ENTRYIDS: + s = "PR_CONTACT_ENTRYIDS"; + break; + +case PR_CONTACT_ADDRTYPES: + s = "PR_CONTACT_ADDRTYPES"; + break; + +case PR_CONTACT_DEFAULT_ADDRESS_INDEX: + s = "PR_CONTACT_DEFAULT_ADDRESS_INDEX"; + break; + +case PR_CONTACT_EMAIL_ADDRESSES: + s = "PR_CONTACT_EMAIL_ADDRESSES"; + break; + +case PR_COMPANY_MAIN_PHONE_NUMBER: + s = "PR_COMPANY_MAIN_PHONE_NUMBER"; + break; + +case PR_CHILDRENS_NAMES: + s = "PR_CHILDRENS_NAMES"; + break; + +case PR_HOME_ADDRESS_CITY: + s = "PR_HOME_ADDRESS_CITY"; + break; + +case PR_HOME_ADDRESS_COUNTRY: + s = "PR_HOME_ADDRESS_COUNTRY"; + break; + +case PR_HOME_ADDRESS_POSTAL_CODE: + s = "PR_HOME_ADDRESS_POSTAL_CODE"; + break; + +case PR_HOME_ADDRESS_STATE_OR_PROVINCE: + s = "PR_HOME_ADDRESS_STATE_OR_PROVINCE"; + break; + +case PR_HOME_ADDRESS_STREET: + s = "PR_HOME_ADDRESS_STREET"; + break; + +case PR_HOME_ADDRESS_POST_OFFICE_BOX: + s = "PR_HOME_ADDRESS_POST_OFFICE_BOX"; + break; + +case PR_OTHER_ADDRESS_CITY: + s = "PR_OTHER_ADDRESS_CITY"; + break; + +case PR_OTHER_ADDRESS_COUNTRY: + s = "PR_OTHER_ADDRESS_COUNTRY"; + break; + +case PR_OTHER_ADDRESS_POSTAL_CODE: + s = "PR_OTHER_ADDRESS_POSTAL_CODE"; + break; + +case PR_OTHER_ADDRESS_STATE_OR_PROVINCE: + s = "PR_OTHER_ADDRESS_STATE_OR_PROVINCE"; + break; + +case PR_OTHER_ADDRESS_STREET: + s = "PR_OTHER_ADDRESS_STREET"; + break; + +case PR_OTHER_ADDRESS_POST_OFFICE_BOX: + s = "PR_OTHER_ADDRESS_POST_OFFICE_BOX"; + break; + + /* + * Profile section properties + */ + +case PR_STORE_PROVIDERS: + s = "PR_STORE_PROVIDERS"; + break; +case PR_AB_PROVIDERS: + s = "PR_AB_PROVIDERS"; + break; +case PR_TRANSPORT_PROVIDERS: + s = "PR_TRANSPORT_PROVIDERS"; + break; + +case PR_DEFAULT_PROFILE: + s = "PR_DEFAULT_PROFILE"; + break; +case PR_AB_SEARCH_PATH: + s = "PR_AB_SEARCH_PATH"; + break; +case PR_AB_DEFAULT_DIR: + s = "PR_AB_DEFAULT_DIR"; + break; +case PR_AB_DEFAULT_PAB: + s = "PR_AB_DEFAULT_PAB"; + break; + +case PR_FILTERING_HOOKS: + s = "PR_FILTERING_HOOKS"; + break; +case PR_SERVICE_NAME: + s = "PR_SERVICE_NAME"; + break; +case PR_SERVICE_DLL_NAME: + s = "PR_SERVICE_DLL_NAME"; + break; +case PR_SERVICE_ENTRY_NAME: + s = "PR_SERVICE_ENTRY_NAME"; + break; +case PR_SERVICE_UID: + s = "PR_SERVICE_UID"; + break; +case PR_SERVICE_EXTRA_UIDS: + s = "PR_SERVICE_EXTRA_UIDS"; + break; +case PR_SERVICES: + s = "PR_SERVICES"; + break; +case PR_SERVICE_SUPPORT_FILES: + s = "PR_SERVICE_SUPPORT_FILES"; + break; +case PR_SERVICE_DELETE_FILES: + s = "PR_SERVICE_DELETE_FILES"; + break; +case PR_AB_SEARCH_PATH_UPDATE: + s = "PR_AB_SEARCH_PATH_UPDATE"; + break; +case PR_PROFILE_NAME: + s = "PR_PROFILE_NAME"; + break; + + /* + * Status object properties + */ + +case PR_IDENTITY_DISPLAY: + s = "PR_IDENTITY_DISPLAY"; + break; +case PR_IDENTITY_ENTRYID: + s = "PR_IDENTITY_ENTRYID"; + break; +case PR_RESOURCE_METHODS: + s = "PR_RESOURCE_METHODS"; + break; +case PR_RESOURCE_TYPE: + s = "PR_RESOURCE_TYPE"; + break; +case PR_STATUS_CODE: + s = "PR_STATUS_CODE"; + break; +case PR_IDENTITY_SEARCH_KEY: + s = "PR_IDENTITY_SEARCH_KEY"; + break; +case PR_OWN_STORE_ENTRYID: + s = "PR_OWN_STORE_ENTRYID"; + break; +case PR_RESOURCE_PATH: + s = "PR_RESOURCE_PATH"; + break; +case PR_STATUS_STRING: + s = "PR_STATUS_STRING"; + break; +case PR_X400_DEFERRED_DELIVERY_CANCEL: + s = "PR_X400_DEFERRED_DELIVERY_CANCEL"; + break; +case PR_HEADER_FOLDER_ENTRYID: + s = "PR_HEADER_FOLDER_ENTRYID"; + break; +case PR_REMOTE_PROGRESS: + s = "PR_REMOTE_PROGRESS"; + break; +case PR_REMOTE_PROGRESS_TEXT: + s = "PR_REMOTE_PROGRESS_TEXT"; + break; +case PR_REMOTE_VALIDATE_OK: + s = "PR_REMOTE_VALIDATE_OK"; + break; + + /* + * Display table properties + */ + +case PR_CONTROL_FLAGS: + s = "PR_CONTROL_FLAGS"; + break; +case PR_CONTROL_STRUCTURE: + s = "PR_CONTROL_STRUCTURE"; + break; +case PR_CONTROL_TYPE: + s = "PR_CONTROL_TYPE"; + break; +case PR_DELTAX: + s = "PR_DELTAX"; + break; +case PR_DELTAY: + s = "PR_DELTAY"; + break; +case PR_XPOS: + s = "PR_XPOS"; + break; +case PR_YPOS: + s = "PR_YPOS"; + break; +case PR_CONTROL_ID: + s = "PR_CONTROL_ID"; + break; +case PR_INITIAL_DETAILS_PANE: + s = "PR_INITIAL_DETAILS_PANE"; + break; +/* + * Secure property id range + */ +case PROP_ID_SECURE_MIN: + s = "PROP_ID_SECURE_MIN"; + break; +case PROP_ID_SECURE_MAX: + s = "PROP_ID_SECURE_MAX"; + break; diff --git a/comm/mailnews/import/src/MorkImport.cpp b/comm/mailnews/import/src/MorkImport.cpp new file mode 100644 index 0000000000..4cdecb8067 --- /dev/null +++ b/comm/mailnews/import/src/MorkImport.cpp @@ -0,0 +1,343 @@ +/* -*- 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/. */ + +/* + * Mork import addressbook interfaces + */ + +#include "MorkImport.h" + +#include "nsCOMPtr.h" +#include "nsIImportService.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsImportStringBundle.h" +#include "nsIComponentManager.h" +#include "nsIAbDirectory.h" +#include "nsAddrDatabase.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" + +static const char kRowIDProperty[] = "DbRowID"; + +class MorkImportAddressImpl final : public nsIImportAddressBooks { + public: + explicit MorkImportAddressImpl(nsIStringBundle* aStringBundle); + + static nsresult Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTADDRESSBOOKS + + private: + ~MorkImportAddressImpl() {} + nsCOMPtr mFileLocation; + nsCOMPtr mStringBundle; +}; + +MorkImport::MorkImport() { + nsImportStringBundle::GetStringBundle( + "chrome://messenger/locale/morkImportMsgs.properties", + getter_AddRefs(mStringBundle)); +} + +MorkImport::~MorkImport() {} + +NS_IMPL_ISUPPORTS(MorkImport, nsIImportModule) + +NS_IMETHODIMP MorkImport::GetName(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + *name = + nsImportStringBundle::GetStringByName("morkImportName", mStringBundle); + return NS_OK; +} + +NS_IMETHODIMP MorkImport::GetDescription(char16_t** description) { + NS_ENSURE_ARG_POINTER(description); + *description = nsImportStringBundle::GetStringByName("morkImportDescription", + mStringBundle); + return NS_OK; +} + +NS_IMETHODIMP MorkImport::GetSupports(char** supports) { + NS_ENSURE_ARG_POINTER(supports); + *supports = strdup(NS_IMPORT_ADDRESS_STR); + return NS_OK; +} + +NS_IMETHODIMP MorkImport::GetSupportsUpgrade(bool* upgrade) { + NS_ENSURE_ARG_POINTER(upgrade); + *upgrade = false; + return NS_OK; +} + +NS_IMETHODIMP MorkImport::GetImportInterface(const char* importType, + nsISupports** interface) { + NS_ENSURE_ARG_POINTER(importType); + NS_ENSURE_ARG_POINTER(interface); + + *interface = nullptr; + nsresult rv; + + if (strcmp(importType, "addressbook")) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr pAddress; + nsCOMPtr pGeneric; + rv = MorkImportAddressImpl::Create(getter_AddRefs(pAddress), mStringBundle); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric)); + NS_ENSURE_SUCCESS(rv, rv); + pGeneric->SetData("addressInterface", pAddress); + nsCOMPtr pInterface(do_QueryInterface(pGeneric)); + pInterface.forget(interface); + + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////// + +nsresult MorkImportAddressImpl::Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new MorkImportAddressImpl(aStringBundle)); + return NS_OK; +} + +MorkImportAddressImpl::MorkImportAddressImpl(nsIStringBundle* aStringBundle) + : mStringBundle(aStringBundle) {} + +NS_IMPL_ISUPPORTS(MorkImportAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP MorkImportAddressImpl::GetSupportsMultiple(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::GetAutoFind(char16_t** addrDescription, + bool* _retval) { + NS_ENSURE_ARG_POINTER(addrDescription); + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::GetNeedsFieldMap(nsIFile* aLocation, + bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::GetDefaultLocation(nsIFile** ppLoc, + bool* found, + bool* userVerify) { + NS_ENSURE_ARG_POINTER(ppLoc); + NS_ENSURE_ARG_POINTER(found); + NS_ENSURE_ARG_POINTER(userVerify); + + *ppLoc = nullptr; + *found = false; + *userVerify = true; + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::FindAddressBooks( + nsIFile* pLoc, nsTArray>& books) { + NS_ENSURE_ARG_POINTER(pLoc); + + books.Clear(); + bool exists = false; + nsresult rv = pLoc->Exists(&exists); + if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE; + + bool isFile = false; + rv = pLoc->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) return NS_ERROR_FAILURE; + + mFileLocation = pLoc; + + /* Build an address book descriptor based on the file passed in! */ + nsString name; + rv = mFileLocation->GetLeafName(name); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t idx = name.RFindChar('.'); + if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) { + name.SetLength(idx); + } + + nsCOMPtr desc; + + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc)); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t sz = 0; + pLoc->GetFileSize(&sz); + desc->SetPreferredName(name); + desc->SetSize((uint32_t)sz); + desc->SetAbFile(mFileLocation); + books.AppendElement(desc); + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::InitFieldMap(nsIImportFieldMap* fieldMap) { + return NS_OK; +} + +NS_IMETHODIMP +MorkImportAddressImpl::ImportAddressBook( + nsIImportABDescriptor* pSource, nsIAbDirectory* pDestination, + nsIImportFieldMap* fieldMap, nsISupports* aSupportService, + char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) { + NS_ENSURE_ARG_POINTER(pSource); + NS_ENSURE_ARG_POINTER(pDestination); + NS_ENSURE_ARG_POINTER(fatalError); + + nsCOMPtr oldFile; + pSource->GetAbFile(getter_AddRefs(oldFile)); + + nsresult rv = ReadMABToDirectory(oldFile, pDestination); + + *pSuccessLog = + nsImportStringBundle::GetStringByName("morkImportSuccess", mStringBundle); + return rv; +} + +nsresult ReadMABToDirectory(nsIFile* oldFile, nsIAbDirectory* newDirectory) { + nsresult rv; + + nsAddrDatabase database = nsAddrDatabase(); + database.SetDbPath(oldFile); + database.OpenMDB(oldFile, false); + + nsInterfaceHashtable cardMap; + + nsCOMPtr enumerator; + database.EnumerateCards(getter_AddRefs(enumerator)); + + nsCOMPtr supports; + nsCOMPtr card; + bool isMailList; + while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(supports))) && + supports) { + card = do_QueryInterface(supports); + + card->GetIsMailList(&isMailList); + if (isMailList) { + continue; + } + + uint32_t rowId; + card->GetPropertyAsUint32(kRowIDProperty, &rowId); + cardMap.InsertOrUpdate(rowId, card); + + nsIAbCard* outCard; + newDirectory->AddCard(card, &outCard); + } + + database.EnumerateCards(getter_AddRefs(enumerator)); + + while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(supports))) && + supports) { + card = do_QueryInterface(supports); + card->GetIsMailList(&isMailList); + + if (!isMailList) { + continue; + } + + nsCOMPtr mailList = + do_CreateInstance("@mozilla.org/addressbook/directoryproperty;1"); + mailList->SetIsMailList(true); + + nsAutoString listName; + card->GetDisplayName(listName); + mailList->SetDirName(listName); + + nsAutoString nickName; + rv = card->GetPropertyAsAString("NickName", nickName); + if (NS_SUCCEEDED(rv)) { + mailList->SetListNickName(nickName); + } + + nsAutoString description; + rv = card->GetPropertyAsAString("Notes", description); + if (NS_SUCCEEDED(rv)) { + mailList->SetDescription(description); + } + + nsIAbDirectory* outList; + rv = newDirectory->AddMailList(mailList, &outList); + if (NS_FAILED(rv)) { + continue; + } + + uint32_t listRowId; + card->GetPropertyAsUint32(kRowIDProperty, &listRowId); + + nsCOMPtr listEnumerator; + database.EnumerateListAddresses(listRowId, getter_AddRefs(listEnumerator)); + + nsCOMPtr listSupports; + nsCOMPtr listCard; + while ( + NS_SUCCEEDED(listEnumerator->GetNext(getter_AddRefs(listSupports))) && + listSupports) { + listCard = do_QueryInterface(listSupports); + + uint32_t rowId; + listCard->GetPropertyAsUint32(kRowIDProperty, &rowId); + cardMap.Get(rowId, getter_AddRefs(listCard)); + + nsIAbCard* outCard; + outList->AddCard(listCard, &outCard); + } + } + + database.ForceClosed(); + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::GetImportProgress(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = 0; + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::SetSampleLocation(nsIFile* pLocation) { + NS_ENSURE_ARG_POINTER(pLocation); + return NS_OK; +} + +NS_IMETHODIMP MorkImportAddressImpl::GetSampleData(int32_t index, bool* pFound, + char16_t** pStr) { + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsImportABFromMab, nsIImportABFile) + +nsImportABFromMab::nsImportABFromMab() {} + +NS_IMETHODIMP +nsImportABFromMab::ReadFileToDirectory(nsIFile* sourceFile, + nsIAbDirectory* targetDirectory) { + return ReadMABToDirectory(sourceFile, targetDirectory); +} diff --git a/comm/mailnews/import/src/MorkImport.h b/comm/mailnews/import/src/MorkImport.h new file mode 100644 index 0000000000..80a0d25bec --- /dev/null +++ b/comm/mailnews/import/src/MorkImport.h @@ -0,0 +1,50 @@ +/* -*- 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 MorkImport_h___ +#define MorkImport_h___ + +#include "nsIImportABFile.h" +#include "nsIImportModule.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" + +#include "nsIFile.h" +#include "nsIAbDirectory.h" + +#define MORKIMPORT_CID \ + { /* 54d48d9f-1bac-47be-9190-c4dc74e837e2 */ \ + 0x54d48d9f, 0x1bac, 0x47be, { \ + 0x91, 0x90, 0xc4, 0xdc, 0x74, 0xe8, 0x37, 0xe2 \ + } \ + } + +nsresult ReadMABToDirectory(nsIFile* oldFile, nsIAbDirectory* newDirectory); + +class nsImportABFromMab : public nsIImportABFile { + public: + nsImportABFromMab(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTABFILE + + protected: + virtual ~nsImportABFromMab(){}; +}; + +class MorkImport : public nsIImportModule { + public: + MorkImport(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTMODULE + + protected: + virtual ~MorkImport(); + nsCOMPtr mStringBundle; +}; + +#endif /* MorkImport_h___ */ diff --git a/comm/mailnews/import/src/SeamonkeyImport.jsm b/comm/mailnews/import/src/SeamonkeyImport.jsm new file mode 100644 index 0000000000..c88f1830d7 --- /dev/null +++ b/comm/mailnews/import/src/SeamonkeyImport.jsm @@ -0,0 +1,253 @@ +/* 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/. */ + +let { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +let seamonkeyImportMsgs = Services.strings.createBundle( + "chrome://messenger/locale/seamonkeyImportMsgs.properties" +); + +var EXPORTED_SYMBOLS = ["SeamonkeyImport"]; + +/** + * Implements nsIImportGeneric instead of nsIImportAddressBook. The actual + * importing is delegated to nsSeamonkeyProfileMigrator. + */ +function SeamonkeyImportAddressbook() { + this.migrator = Cc[ + "@mozilla.org/profile/migrator;1?app=mail&type=seamonkey" + ].createInstance(Ci.nsIMailProfileMigrator); + this.sourceProfileName = null; + this.sourceProfileLocation = null; +} + +SeamonkeyImportAddressbook.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIImportGeneric"]), + + /** + * Return the location of addressbook. + */ + GetData() { + if (!this.sourceProfileName || !this.sourceProfileLocation) { + try { + this.sourceProfileName = this.migrator.sourceProfiles[0]; + this.sourceProfileLocation = this.migrator.sourceProfileLocations[0]; + } catch (e) { + return null; + } + } + + return this.sourceProfileLocation; + }, + + SetData() { + return 0; + }, + + WantsProgress() { + return false; + }, + + GetProgress() { + return 0; + }, + + GetStatus() { + return 0; + }, + + CancelImport() { + return 0; + }, + + ContinueImport() { + return 0; + }, + + BeginImport(successLog, errorLog) { + this.migrator.migrate( + Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA, + null, + this.sourceProfileName + ); + successLog.data = seamonkeyImportMsgs.GetStringFromName( + "SeamonkeyImportAddressSuccess" + ); + return true; + }, +}; + +/** + * Implements nsIImportMail. The importing process is managed by nsImportMail. + */ +function SeamonkeyImportMail() {} + +SeamonkeyImportMail.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIImportMail"]), + + GetDefaultLocation(location, found, userVerify) { + let migrator = Cc[ + "@mozilla.org/profile/migrator;1?app=mail&type=seamonkey" + ].createInstance(Ci.nsIMailProfileMigrator); + + try { + let sourceProfile = migrator.sourceProfileLocations[0]; + location.value = sourceProfile; + found.value = true; + } catch (e) { + found.value = false; + } + userVerify.value = false; + }, + + _createMailboxDescriptor(path, name, depth) { + let importService = Cc[ + "@mozilla.org/import/import-service;1" + ].createInstance(Ci.nsIImportService); + let descriptor = importService.CreateNewMailboxDescriptor(); + descriptor.size = 100; + descriptor.depth = depth; + descriptor.SetDisplayName(name); + descriptor.file.initWithPath(path); + + return descriptor; + }, + + _collectMailboxesInDirectory(directory, depth) { + let result = []; + let name = directory.leafName; + if (depth > 0 && !name.endsWith(".msf") && !name.endsWith(".dat")) { + if (name.endsWith(".sbd")) { + name = name.slice(0, name.lastIndexOf(".")); + } + let descriptor = this._createMailboxDescriptor( + directory.path, + name, + depth + ); + result.push(descriptor); + } + if (directory.isDirectory()) { + for (let entry of directory.directoryEntries) { + if ( + (depth == 0 && + entry.leafName != "ImapMail" && + entry.leafName != "Mail") || + (depth == 1 && entry.leafName == "Feeds") + ) { + continue; + } + result.push(...this._collectMailboxesInDirectory(entry, depth + 1)); + } + } + return result; + }, + + // Collect mailboxes in a Seamonkey profile. + findMailboxes(location) { + return this._collectMailboxesInDirectory(location, 0); + }, + + // Copy mailboxes a Seamonkey profile to Thunderbird profile. + ImportMailbox(source, dstFolder, errorLog, successLog, fatalError) { + if (source.file.isFile()) { + source.file.copyTo( + dstFolder.filePath.parent, + dstFolder.filePath.leafName + ); + successLog.value = `Import ${source.file.leafName} succeeded.\n`; + } + }, +}; + +/** + * Implements nsIImportSettings. The actual importing is delegated to + * nsSeamonkeyProfileMigrator. + */ +function SeamonkeyImportSettings() { + this.migrator = Cc[ + "@mozilla.org/profile/migrator;1?app=mail&type=seamonkey" + ].createInstance(Ci.nsIMailProfileMigrator); + this.sourceProfileName = null; + this.sourceProfileLocation = null; +} + +SeamonkeyImportSettings.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIImportSettings"]), + + AutoLocate(desc, loc) { + if (!this.sourceProfileName || !this.sourceProfileLocation) { + try { + this.sourceProfileName = this.migrator.sourceProfiles[0]; + this.sourceProfileLocation = this.migrator.sourceProfileLocations[0]; + } catch (e) { + return false; + } + } + loc = this.sourceProfileLocation; + return true; + }, + + Import() { + this.migrator.migrate( + Ci.nsIMailProfileMigrator.SETTINGS, + null, + this.sourceProfileName + ); + + // Reload accounts so that `CheckIfLocalFolderExists` in importDialog works + MailServices.accounts.unloadAccounts(); + MailServices.accounts.loadAccounts(); + return true; + }, +}; + +/** + * Implements nsIImportModule so that Seamonkey is shown as an option in the + * importDialog.xhtml. Currently supports importing addressbook and mail, see + * the GetImportInterface function. + */ +function SeamonkeyImport() {} + +SeamonkeyImport.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIImportModule"]), + + get name() { + return seamonkeyImportMsgs.GetStringFromName("SeamonkeyImportName"); + }, + + get description() { + return seamonkeyImportMsgs.GetStringFromName("SeamonkeyImportDescription"); + }, + + get supports() { + return "addressbook,mail,settings"; + }, + + get supportsUpgrade() { + return false; + }, + + GetImportInterface(type) { + if (type == "addressbook") { + return new SeamonkeyImportAddressbook(); + } else if (type == "mail") { + let importService = Cc[ + "@mozilla.org/import/import-service;1" + ].createInstance(Ci.nsIImportService); + let genericInterface = importService.CreateNewGenericMail(); + genericInterface.SetData("mailInterface", new SeamonkeyImportMail()); + let name = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + name.data = "SeaMonkey"; + genericInterface.SetData("name", name); + return genericInterface; + } else if (type == "settings") { + return new SeamonkeyImportSettings(); + } + return null; + }, +}; diff --git a/comm/mailnews/import/src/ThunderbirdImport.jsm b/comm/mailnews/import/src/ThunderbirdImport.jsm new file mode 100644 index 0000000000..8263fa7098 --- /dev/null +++ b/comm/mailnews/import/src/ThunderbirdImport.jsm @@ -0,0 +1,145 @@ +/* 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/. */ + +const EXPORTED_SYMBOLS = ["ThunderbirdImport"]; + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; + +XPCOMUtils.defineLazyGetter( + lazy, + "l10n", + () => new Localization(["messenger/importDialog.ftl"], true) +); + +/** + * The importing process is managed by importDialog.js and nsImportMail.cpp. + * + * @implements {nsIImportMail} + */ +class ThunderbirdImportMail { + QueryInterface = ChromeUtils.generateQI(["nsIImportMail"]); + + GetDefaultLocation(location, found, userVerify) { + userVerify.value = true; + } + + /** + * Create a nsIImportMailboxDescriptor instance. + * + * @param {string} path - The mailbox path. + * @param {string} name - The mailbox name. + * @param {number} depth - The depth of the mailbox folder. + * @returns {nsIImportMailboxDescriptor} + */ + _createMailboxDescriptor(path, name, depth) { + let importService = Cc[ + "@mozilla.org/import/import-service;1" + ].createInstance(Ci.nsIImportService); + let descriptor = importService.CreateNewMailboxDescriptor(); + descriptor.size = 100; + descriptor.depth = depth; + descriptor.SetDisplayName(name); + descriptor.file.initWithPath(path); + + return descriptor; + } + + /** + * Recursively find mailboxes in a directory. + * + * @param {nsIFile} directory - The directory to find mailboxes. + * @param {number} depth - The depth of the current directory. + * @returns {nsIImportMailboxDescriptor[]} - All mailboxes found. + */ + _collectMailboxesInDirectory(directory, depth) { + let result = []; + let name = directory.leafName; + if (depth > 0 && !name.endsWith(".msf") && !name.endsWith(".dat")) { + if (name.endsWith(".sbd")) { + name = name.slice(0, name.lastIndexOf(".")); + } + let descriptor = this._createMailboxDescriptor( + directory.path, + name, + depth + ); + result.push(descriptor); + } + if (directory.isDirectory()) { + for (let entry of directory.directoryEntries) { + if ( + (depth == 0 && + entry.leafName != "ImapMail" && + entry.leafName != "Mail") || + (depth == 1 && entry.leafName == "Feeds") + ) { + continue; + } + result.push(...this._collectMailboxesInDirectory(entry, depth + 1)); + } + } + return result; + } + + findMailboxes(location) { + return this._collectMailboxesInDirectory(location, 0); + } + + ImportMailbox(source, dstFolder, errorLog, successLog, fatalError) { + if (source.file.isFile()) { + source.file.copyTo( + dstFolder.filePath.parent, + dstFolder.filePath.leafName + ); + successLog.value = `Import ${source.file.leafName} succeeded.\n`; + } + } +} + +/** + * With this class, Thunderbird is shown as an option in the importDialog.xhtml. + * Currently supports importing mail, see the GetImportInterface function. + * + * @implements {nsIImportModule} + */ +class ThunderbirdImport { + QueryInterface = ChromeUtils.generateQI(["nsIImportModule"]); + + get name() { + return lazy.l10n.formatValueSync("thunderbird-import-name"); + } + + get description() { + return lazy.l10n.formatValueSync("thunderbird-import-description"); + } + + get supports() { + return "mail"; + } + + get supportsUpgrade() { + return false; + } + + GetImportInterface(type) { + if (type == "mail") { + let importService = Cc[ + "@mozilla.org/import/import-service;1" + ].createInstance(Ci.nsIImportService); + let genericInterface = importService.CreateNewGenericMail(); + genericInterface.SetData("mailInterface", new ThunderbirdImportMail()); + let name = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + name.data = "Thunderbird"; + genericInterface.SetData("name", name); + return genericInterface; + } + return null; + } +} diff --git a/comm/mailnews/import/src/components.conf b/comm/mailnews/import/src/components.conf new file mode 100644 index 0000000000..e8891fca57 --- /dev/null +++ b/comm/mailnews/import/src/components.conf @@ -0,0 +1,104 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + "cid": "{991f078e-a6d5-44f2-b91e-c52efcfa3360}", + "contract_ids": ["@mozilla.org/import/import-seamonkey;1"], + "jsm": "resource:///modules/SeamonkeyImport.jsm", + "constructor": "SeamonkeyImport", + "categories": {"mailnewsimport": "seamonkey"}, + }, + { + "cid": "{c6988841-d916-44a3-bb4d-f0838a98e95a}", + "contract_ids": ["@mozilla.org/import/import-thunderbird;1"], + "jsm": "resource:///modules/ThunderbirdImport.jsm", + "constructor": "ThunderbirdImport", + "categories": {"mailnewsimport": "thunderbird"}, + }, + { + "cid": "{a6629718-9a97-4073-ab48-442fcceaea5d}", + "contract_ids": ["@mozilla.org/import/import-ab-file;1?type=mab"], + "type": "nsImportABFromMab", + "headers": ["/comm/mailnews/import/src/MorkImport.h"], + }, + { + "cid": "{5df96d60-1726-11d3-a206-00a0cc26da63}", + "contract_ids": ["@mozilla.org/import/import-service;1"], + "type": "nsImportService", + "headers": ["/comm/mailnews/import/src/nsImportService.h"], + "name": "Import", + "interfaces": ["nsIImportService"], + }, + { + "cid": "{a5991d01-ada7-11d3-a9c2-00a0cc26da63}", + "contract_ids": ["@mozilla.org/import/import-text;1"], + "type": "nsTextImport", + "headers": ["/comm/mailnews/import/src/nsTextImport.h"], + "categories": {"mailnewsimport": "text"}, + }, + { + "cid": "{0eb034a3-964a-4e2f-92ebcc55d9ae9dd2}", + "contract_ids": ["@mozilla.org/import/import-vcard;1"], + "type": "nsVCardImport", + "headers": ["/comm/mailnews/import/src/nsVCardImport.h"], + "categories": {"mailnewsimport": "vcard"}, + }, + { + "cid": "{54d48d9f-1bac-47be-9190-c4dc74e837e2}", + "contract_ids": ["@mozilla.org/import/import-mork;1"], + "type": "MorkImport", + "headers": ["/comm/mailnews/import/src/MorkImport.h"], + "categories": {"mailnewsimport": "mork"}, + }, +] + +if buildconfig.substs["OS_ARCH"] == "Darwin": + Classes += [ + { + "cid": "{6d3f101c-70ec-4e04-b68d-9908d1aeddf3}", + "contract_ids": ["@mozilla.org/import/import-applemail;1"], + "type": "nsAppleMailImportModule", + "headers": ["/comm/mailnews/import/src/nsAppleMailImport.h"], + "categories": {"mailnewsimport": "applemail"}, + }, + { + "cid": "{9117a1ea-e012-43b5-a020-cb8a66cc09e1}", + "contract_ids": ["@mozilla.org/import/import-appleMailImpl;1"], + "type": "nsAppleMailImportMail", + "init_method": "Initialize", + "headers": ["/comm/mailnews/import/src/nsAppleMailImport.h"], + }, + ] + +if buildconfig.substs["OS_ARCH"] == "WINNT": + Classes += [ + { + "cid": "{42bc82bc-8e9f-4597-8b6e-e529daaf3af1}", + "contract_ids": ["@mozilla.org/import/import-wm;1"], + "type": "nsWMImport", + "headers": ["/comm/mailnews/import/src/nsWMImport.h"], + "categories": {"mailnewsimport": "winlivemail"}, + }, + { + "cid": "{7952a6cf-2442-4c04-9f02-150b15a0a841}", + "contract_ids": ["@mozilla.org/import/import-becky;1"], + "type": "nsBeckyImport", + "headers": ["/comm/mailnews/import/src/nsBeckyImport.h"], + "categories": {"mailnewsimport": "becky"}, + }, + ] + + if buildconfig.substs["MOZ_MAPI_SUPPORT"]: + Classes += [ + { + "cid": "{1db469a0-8b00-11d3-a206-00a0cc26da63}", + "contract_ids": ["@mozilla.org/import/import-outlook;1"], + "type": "nsOutlookImport", + "headers": ["/comm/mailnews/import/src/nsOutlookImport.h"], + "categories": {"mailnewsimport": "outlook"}, + }, + ] diff --git a/comm/mailnews/import/src/moz.build b/comm/mailnews/import/src/moz.build new file mode 100644 index 0000000000..d587d87fa5 --- /dev/null +++ b/comm/mailnews/import/src/moz.build @@ -0,0 +1,86 @@ +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "ImportCharSet.cpp", + "ImportOutFile.cpp", + "ImportTranslate.cpp", + "MorkImport.cpp", + "nsAddrDatabase.cpp", + "nsImportABDescriptor.cpp", + "nsImportAddressBooks.cpp", + "nsImportEmbeddedImageData.cpp", + "nsImportEncodeScan.cpp", + "nsImportFieldMap.cpp", + "nsImportMail.cpp", + "nsImportMailboxDescriptor.cpp", + "nsImportScanFile.cpp", + "nsImportService.cpp", + "nsImportStringBundle.cpp", + "nsImportTranslator.cpp", + "nsTextAddress.cpp", + "nsTextImport.cpp", + "nsVCardAddress.cpp", + "nsVCardImport.cpp", +] + +if not CONFIG["MOZ_SUITE"]: + EXTRA_JS_MODULES += [ + "SeamonkeyImport.jsm", + "ThunderbirdImport.jsm", + ] + + XPCOM_MANIFESTS += [ + "components.conf", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsAppleMailImport.cpp", + "nsEmlxHelperUtils.mm", + ] + +if CONFIG["OS_ARCH"] == "WINNT": + UNIFIED_SOURCES += [ + "nsBeckyAddressBooks.cpp", + "nsBeckyFilters.cpp", + "nsBeckyImport.cpp", + "nsBeckyMail.cpp", + "nsBeckySettings.cpp", + "nsBeckyStringBundle.cpp", + "nsBeckyUtils.cpp", + ] + + if CONFIG["MOZ_MAPI_SUPPORT"]: + SOURCES += [ + "MapiApi.cpp", + "MapiMessage.cpp", + "MapiMimeTypes.cpp", + "nsOutlookCompose.cpp", + "nsOutlookImport.cpp", + "nsOutlookMail.cpp", + "nsOutlookSettings.cpp", + "nsOutlookStringBundle.cpp", + "rtfDecoder.cpp", + "rtfMailDecoder.cpp", + ] + + SOURCES["rtfDecoder.cpp"].flags += ["-Wno-switch"] + LOCAL_INCLUDES += ["/comm/mailnews/mapi/include"] + + if CONFIG["CC_TYPE"] in ("msvc", "clang-cl"): + SOURCES += [ + "nsWMImport.cpp", + "nsWMSettings.cpp", + "nsWMStringBundle.cpp", + "nsWMUtils.cpp", + ] + +EXPORTS += [ + "ImportDebug.h", + "nsVCardAddress.h", +] + +FINAL_LIBRARY = "import" diff --git a/comm/mailnews/import/src/nsAddrDatabase.cpp b/comm/mailnews/import/src/nsAddrDatabase.cpp new file mode 100644 index 0000000000..1bc0df1795 --- /dev/null +++ b/comm/mailnews/import/src/nsAddrDatabase.cpp @@ -0,0 +1,864 @@ +/* -*- 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/. */ + +// this file implements the nsAddrDatabase interface using the MDB Interface. + +#include "nsAddrDatabase.h" +#include "nsMsgUtils.h" +#include "nsIMdbFactoryFactory.h" +#include "nsSimpleEnumerator.h" + +#define kAddressCharSetColumn "AddrCharSet" +#define kMailListName "ListName" +#define kMailListNickName "ListNickName" +#define kMailListDescription "ListDescription" +#define kMailListTotalAddresses "ListTotalAddresses" +// not shown in the UI +#define kLowerPriEmailColumn "LowercasePrimaryEmail" +#define kLower2ndEmailColumn "LowercaseSecondEmail" + +#define ID_PAB_TABLE 1 + +static const char kPabTableKind[] = "ns:addrbk:db:table:kind:pab"; +static const char kDeletedCardsTableKind[] = + "ns:addrbk:db:table:kind:deleted"; // this table is used to keep the + // deleted cards + +static const char kCardRowScope[] = "ns:addrbk:db:row:scope:card:all"; +static const char kListRowScope[] = "ns:addrbk:db:row:scope:list:all"; +static const char kDataRowScope[] = "ns:addrbk:db:row:scope:data:all"; + +#define COLUMN_STR_MAX 16 + +static const char kRecordKeyColumn[] = "RecordKey"; +static const char kLastRecordKeyColumn[] = "LastRecordKey"; +static const char kRowIDProperty[] = "DbRowID"; + +static const char kLowerListNameColumn[] = "LowercaseListName"; + +struct mdbOid gAddressBookTableOID; + +static const char kMailListAddressFormat[] = "Address%d"; + +nsAddrDatabase::nsAddrDatabase() + : m_mdbEnv(nullptr), + m_mdbStore(nullptr), + m_mdbPabTable(nullptr), + m_mdbTokensInitialized(false), + m_PabTableKind(0), + m_DeletedCardsTableKind(0), + m_CardRowScopeToken(0), + m_UIDColumnToken(0), + m_FirstNameColumnToken(0), + m_LastNameColumnToken(0), + m_PhoneticFirstNameColumnToken(0), + m_PhoneticLastNameColumnToken(0), + m_DisplayNameColumnToken(0), + m_NickNameColumnToken(0), + m_PriEmailColumnToken(0), + m_2ndEmailColumnToken(0), + m_WorkPhoneColumnToken(0), + m_HomePhoneColumnToken(0), + m_FaxColumnToken(0), + m_PagerColumnToken(0), + m_CellularColumnToken(0), + m_WorkPhoneTypeColumnToken(0), + m_HomePhoneTypeColumnToken(0), + m_FaxTypeColumnToken(0), + m_PagerTypeColumnToken(0), + m_CellularTypeColumnToken(0), + m_HomeAddressColumnToken(0), + m_HomeAddress2ColumnToken(0), + m_HomeCityColumnToken(0), + m_HomeStateColumnToken(0), + m_HomeZipCodeColumnToken(0), + m_HomeCountryColumnToken(0), + m_WorkAddressColumnToken(0), + m_WorkAddress2ColumnToken(0), + m_WorkCityColumnToken(0), + m_WorkStateColumnToken(0), + m_WorkZipCodeColumnToken(0), + m_WorkCountryColumnToken(0), + m_CompanyColumnToken(0), + m_AimScreenNameColumnToken(0), + m_AnniversaryYearColumnToken(0), + m_AnniversaryMonthColumnToken(0), + m_AnniversaryDayColumnToken(0), + m_SpouseNameColumnToken(0), + m_FamilyNameColumnToken(0), + m_DefaultAddressColumnToken(0), + m_CategoryColumnToken(0), + m_WebPage1ColumnToken(0), + m_WebPage2ColumnToken(0), + m_BirthYearColumnToken(0), + m_BirthMonthColumnToken(0), + m_BirthDayColumnToken(0), + m_Custom1ColumnToken(0), + m_Custom2ColumnToken(0), + m_Custom3ColumnToken(0), + m_Custom4ColumnToken(0), + m_NotesColumnToken(0), + m_LastModDateColumnToken(0), + m_PopularityIndexColumnToken(0), + m_AddressCharSetColumnToken(0) {} + +nsAddrDatabase::~nsAddrDatabase() { + Close(false); // better have already been closed. + + // RemoveFromCache(this); + // clean up after ourself! + if (m_mdbPabTable) m_mdbPabTable->Release(); + NS_IF_RELEASE(m_mdbStore); + NS_IF_RELEASE(m_mdbEnv); +} + +nsresult nsAddrDatabase::GetMDBFactory(nsIMdbFactory** aMdbFactory) { + if (!mMdbFactory) { + nsresult rv; + nsCOMPtr mdbFactoryService = + do_GetService("@mozilla.org/db/mork;1", &rv); + if (NS_SUCCEEDED(rv) && mdbFactoryService) { + rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory)); + NS_ENSURE_SUCCESS(rv, rv); + if (!mMdbFactory) return NS_ERROR_FAILURE; + } + } + NS_ADDREF(*aMdbFactory = mMdbFactory); + return NS_OK; +} + +nsresult nsAddrDatabase::SetDbPath(nsIFile* aDbPath) { + return aDbPath->Clone(getter_AddRefs(m_dbName)); +} + +// Open the MDB database synchronously. If successful, this routine +// will set up the m_mdbStore and m_mdbEnv of the database object +// so other database calls can work. +nsresult nsAddrDatabase::OpenMDB(nsIFile* dbName, bool create) { + nsCOMPtr mdbFactory; + nsresult ret = GetMDBFactory(getter_AddRefs(mdbFactory)); + NS_ENSURE_SUCCESS(ret, ret); + + ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv); + if (NS_SUCCEEDED(ret)) { + nsIMdbThumb* thumb = nullptr; + + PathString filePath = dbName->NativePath(); + + nsIMdbHeap* dbHeap = nullptr; + + if (m_mdbEnv) m_mdbEnv->SetAutoClear(true); + + bool dbNameExists = false; + ret = dbName->Exists(&dbNameExists); + NS_ENSURE_SUCCESS(ret, ret); + + if (!dbNameExists) + ret = NS_ERROR_FILE_NOT_FOUND; + else { + mdbOpenPolicy inOpenPolicy; + mdb_bool canOpen; + mdbYarn outFormatVersion; + nsIMdbFile* oldFile = nullptr; + int64_t fileSize; + ret = dbName->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(ret, ret); + + ret = mdbFactory->OpenOldFile( + m_mdbEnv, dbHeap, filePath.get(), + mdbBool_kFalse, // not readonly, we want modifiable + &oldFile); + if (oldFile) { + if (NS_SUCCEEDED(ret)) { + ret = mdbFactory->CanOpenFilePort(m_mdbEnv, + oldFile, // the file to investigate + &canOpen, &outFormatVersion); + if (NS_SUCCEEDED(ret) && canOpen) { + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap, oldFile, + &inOpenPolicy, &thumb); + } else if (fileSize != 0) + ret = NS_ERROR_FILE_ACCESS_DENIED; + } + NS_RELEASE(oldFile); // always release our file ref, store has own + } + if (NS_FAILED(ret)) ret = NS_ERROR_FILE_ACCESS_DENIED; + } + + if (NS_SUCCEEDED(ret) && thumb) { + mdb_count outTotal; // total somethings to do in operation + mdb_count outCurrent; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken; // is operation irreparably dead and broken? + do { + ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, + &outBroken); + if (NS_FAILED(ret)) { + outDone = true; + break; + } + } while (NS_SUCCEEDED(ret) && !outBroken && !outDone); + if (NS_SUCCEEDED(ret) && outDone) { + ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore); + if (NS_SUCCEEDED(ret) && m_mdbStore) { + ret = InitExistingDB(); + create = false; + } + } + } else if (create && ret != NS_ERROR_FILE_ACCESS_DENIED) { + ret = NS_ERROR_NOT_IMPLEMENTED; + } + NS_IF_RELEASE(thumb); + } + return ret; +} + +nsresult nsAddrDatabase::CloseMDB(bool commit) { + if (commit) return NS_ERROR_NOT_IMPLEMENTED; + //??? RemoveFromCache(this); // if we've closed it, better not leave it in + // the cache. + return NS_OK; +} + +// force the database to close - this'll flush out anybody holding onto +// a database without having a listener! +// This is evil in the com world, but there are times we need to delete the +// file. +nsresult nsAddrDatabase::ForceClosed() { + nsresult err = NS_OK; + + // make sure someone has a reference so object won't get deleted out from + // under us. + // NS_ADDREF_THIS(); + // OK, remove from cache first and close the store. + // RemoveFromCache(this); + + err = CloseMDB(false); // since we're about to delete it, no need to commit. + NS_IF_RELEASE(m_mdbStore); + // NS_RELEASE_THIS(); + return err; +} + +nsresult nsAddrDatabase::Close(bool forceCommit /* = TRUE */) { + return CloseMDB(forceCommit); +} + +nsresult nsAddrDatabase::InitExistingDB() { + nsresult err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) { + if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->GetTable(m_mdbEnv, &gAddressBookTableOID, &m_mdbPabTable); + if (NS_SUCCEEDED(err) && m_mdbPabTable) { + // This code has always run here. Removing it fails an assertion in the + // Mork code which indicates a bad state. In the interest of saving + // effort, and since this whole file is doomed after the next release, + // I'm leaving it behind. + nsIMdbTableRowCursor* rowCursor = nullptr; + nsIMdbRow* findRow = nullptr; + mdb_pos rowPos = 0; + + err = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor); + if (NS_SUCCEEDED(err) && rowCursor) { + do { + err = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos); + } while (NS_SUCCEEDED(err) && findRow); + rowCursor->Release(); + } + } + } + return err; +} + +// initialize the various tokens and tables in our db's env +nsresult nsAddrDatabase::InitMDBInfo() { + nsresult err = NS_OK; + + if (!m_mdbTokensInitialized && m_mdbStore && m_mdbEnv) { + m_mdbTokensInitialized = true; + err = m_mdbStore->StringToToken(m_mdbEnv, kCardRowScope, + &m_CardRowScopeToken); + err = m_mdbStore->StringToToken(m_mdbEnv, kListRowScope, + &m_ListRowScopeToken); + err = m_mdbStore->StringToToken(m_mdbEnv, kDataRowScope, + &m_DataRowScopeToken); + gAddressBookTableOID.mOid_Scope = m_CardRowScopeToken; + gAddressBookTableOID.mOid_Id = ID_PAB_TABLE; + if (NS_SUCCEEDED(err)) { + m_mdbStore->StringToToken(m_mdbEnv, kUIDProperty, &m_UIDColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFirstNameProperty, + &m_FirstNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLastNameProperty, + &m_LastNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPhoneticFirstNameProperty, + &m_PhoneticFirstNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPhoneticLastNameProperty, + &m_PhoneticLastNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kDisplayNameProperty, + &m_DisplayNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kNicknameProperty, + &m_NickNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPriEmailProperty, + &m_PriEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLowerPriEmailColumn, + &m_LowerPriEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, k2ndEmailProperty, + &m_2ndEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLower2ndEmailColumn, + &m_Lower2ndEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPopularityIndexProperty, + &m_PopularityIndexColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneProperty, + &m_WorkPhoneColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneProperty, + &m_HomePhoneColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFaxProperty, &m_FaxColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPagerProperty, &m_PagerColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCellularProperty, + &m_CellularColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneTypeProperty, + &m_WorkPhoneTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneTypeProperty, + &m_HomePhoneTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFaxTypeProperty, + &m_FaxTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPagerTypeProperty, + &m_PagerTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCellularTypeProperty, + &m_CellularTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeAddressProperty, + &m_HomeAddressColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeAddress2Property, + &m_HomeAddress2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeCityProperty, + &m_HomeCityColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeStateProperty, + &m_HomeStateColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeZipCodeProperty, + &m_HomeZipCodeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeCountryProperty, + &m_HomeCountryColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkAddressProperty, + &m_WorkAddressColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkAddress2Property, + &m_WorkAddress2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkCityProperty, + &m_WorkCityColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkStateProperty, + &m_WorkStateColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkZipCodeProperty, + &m_WorkZipCodeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkCountryProperty, + &m_WorkCountryColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kJobTitleProperty, + &m_JobTitleColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kDepartmentProperty, + &m_DepartmentColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCompanyProperty, + &m_CompanyColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kScreenNameProperty, + &m_AimScreenNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryYearProperty, + &m_AnniversaryYearColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryMonthProperty, + &m_AnniversaryMonthColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryDayProperty, + &m_AnniversaryDayColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kSpouseNameProperty, + &m_SpouseNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFamilyNameProperty, + &m_FamilyNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkWebPageProperty, + &m_WebPage1ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeWebPageProperty, + &m_WebPage2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kBirthYearProperty, + &m_BirthYearColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kBirthMonthProperty, + &m_BirthMonthColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kBirthDayProperty, + &m_BirthDayColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom1Property, + &m_Custom1ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom2Property, + &m_Custom2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom3Property, + &m_Custom3ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom4Property, + &m_Custom4ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kNotesProperty, &m_NotesColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLastModifiedDateProperty, + &m_LastModDateColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kRecordKeyColumn, + &m_RecordKeyColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAddressCharSetColumn, + &m_AddressCharSetColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLastRecordKeyColumn, + &m_LastRecordKeyColumnToken); + + err = m_mdbStore->StringToToken(m_mdbEnv, kPabTableKind, &m_PabTableKind); + + m_mdbStore->StringToToken(m_mdbEnv, kMailListName, + &m_ListNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kMailListNickName, + &m_ListNickNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kMailListDescription, + &m_ListDescriptionColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kMailListTotalAddresses, + &m_ListTotalColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLowerListNameColumn, + &m_LowerListNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kDeletedCardsTableKind, + &m_DeletedCardsTableKind); + } + } + return err; +} + +//////////////////////////////////////////////////////////////////////////////// + +uint32_t nsAddrDatabase::GetListAddressTotal(nsIMdbRow* listRow) { + uint32_t count = 0; + GetIntColumn(listRow, m_ListTotalColumnToken, &count, 0); + return count; +} + +nsresult nsAddrDatabase::GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, + nsIMdbRow** cardRow) { + if (!m_mdbStore || !listRow || !cardRow || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdb_token listAddressColumnToken; + + char columnStr[COLUMN_STR_MAX]; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken); + + nsAutoString tempString; + mdb_id rowID; + nsresult err = + GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0); + NS_ENSURE_SUCCESS(err, err); + + return GetCardRowByRowID(rowID, cardRow); +} + +nsresult nsAddrDatabase::GetStringColumn(nsIMdbRow* cardRow, mdb_token outToken, + nsString& str) { + nsresult err = NS_ERROR_NULL_POINTER; + nsIMdbCell* cardCell; + + if (cardRow && m_mdbEnv) { + err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell); + if (NS_SUCCEEDED(err) && cardCell) { + struct mdbYarn yarn; + cardCell->AliasYarn(m_mdbEnv, &yarn); + NS_ConvertUTF8toUTF16 uniStr((const char*)yarn.mYarn_Buf, + yarn.mYarn_Fill); + if (!uniStr.IsEmpty()) + str.Assign(uniStr); + else + err = NS_ERROR_FAILURE; + cardCell->Release(); // always release ref + } else + err = NS_ERROR_FAILURE; + } + return err; +} + +void nsAddrDatabase::YarnToUInt32(struct mdbYarn* yarn, uint32_t* pResult) { + uint8_t numChars = std::min(8, yarn->mYarn_Fill); + *pResult = MsgUnhex((char*)yarn->mYarn_Buf, numChars); +} + +nsresult nsAddrDatabase::GetIntColumn(nsIMdbRow* cardRow, mdb_token outToken, + uint32_t* pValue, uint32_t defaultValue) { + nsresult err = NS_ERROR_NULL_POINTER; + nsIMdbCell* cardCell; + + if (pValue) *pValue = defaultValue; + if (cardRow && m_mdbEnv) { + err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell); + if (NS_SUCCEEDED(err) && cardCell) { + struct mdbYarn yarn; + cardCell->AliasYarn(m_mdbEnv, &yarn); + YarnToUInt32(&yarn, pValue); + cardCell->Release(); + } else + err = NS_ERROR_FAILURE; + } + return err; +} + +nsresult nsAddrDatabase::InitCardFromRow(nsIAbCard* newCard, + nsIMdbRow* cardRow) { + nsresult rv = NS_OK; + if (!newCard || !cardRow || !m_mdbEnv) return NS_ERROR_NULL_POINTER; + + nsCOMPtr cursor; + nsCOMPtr cell; + + rv = cardRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(cursor)); + NS_ENSURE_SUCCESS(rv, rv); + + mdb_column columnNumber; + char columnName[100]; + struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr}; + struct mdbYarn cellYarn; + + do { + rv = cursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &columnNumber, + nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (!cell) break; + + // Get the value of the cell + cell->AliasYarn(m_mdbEnv, &cellYarn); + NS_ConvertUTF8toUTF16 value(static_cast(cellYarn.mYarn_Buf), + cellYarn.mYarn_Fill); + + if (!value.IsEmpty()) { + // Get the column of the cell + // Mork makes this so hard... + rv = m_mdbStore->TokenToString(m_mdbEnv, columnNumber, &colYarn); + NS_ENSURE_SUCCESS(rv, rv); + + char* name = + PL_strndup(static_cast(colYarn.mYarn_Buf), colYarn.mYarn_Fill); + newCard->SetPropertyAsAString(name, value); + PL_strfree(name); + } + } while (true); + + uint32_t key = 0; + rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0); + if (NS_SUCCEEDED(rv)) newCard->SetPropertyAsUint32(kRecordKeyColumn, key); + + return NS_OK; +} + +nsresult nsAddrDatabase::GetListCardFromDB(nsIAbCard* listCard, + nsIMdbRow* listRow) { + nsresult err = NS_OK; + if (!listCard || !listRow) return NS_ERROR_NULL_POINTER; + + nsAutoString tempString; + + err = GetStringColumn(listRow, m_UIDColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) { + listCard->SetPropertyAsAString(kUIDProperty, tempString); + } + err = GetStringColumn(listRow, m_ListNameColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) { + listCard->SetDisplayName(tempString); + listCard->SetLastName(tempString); + } + err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) { + listCard->SetPropertyAsAString(kNicknameProperty, tempString); + } + err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) { + listCard->SetPropertyAsAString(kNotesProperty, tempString); + } + uint32_t key = 0; + err = GetIntColumn(listRow, m_RecordKeyColumnToken, &key, 0); + if (NS_SUCCEEDED(err)) listCard->SetPropertyAsUint32(kRecordKeyColumn, key); + return err; +} + +class nsAddrDBEnumerator : public nsSimpleEnumerator { + public: + const nsID& DefaultInterface() override { return NS_GET_IID(nsIAbCard); } + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + + // nsAddrDBEnumerator methods: + explicit nsAddrDBEnumerator(nsAddrDatabase* aDb); + void Clear(); + + protected: + nsAddrDatabase* mDb; + nsIMdbTable* mDbTable; + nsCOMPtr mRowCursor; + nsCOMPtr mCurrentRow; + mdb_pos mRowPos; +}; + +nsAddrDBEnumerator::nsAddrDBEnumerator(nsAddrDatabase* aDb) + : mDb(aDb), mDbTable(aDb->GetPabTable()), mRowPos(-1) {} + +NS_IMETHODIMP +nsAddrDBEnumerator::HasMoreElements(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + if (!mDbTable || !mDb->GetEnv()) { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr rowCursor; + mDbTable->GetTableRowCursor(mDb->GetEnv(), mRowPos, + getter_AddRefs(rowCursor)); + NS_ENSURE_TRUE(rowCursor, NS_ERROR_FAILURE); + + mdbOid rowOid; + rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr); + while (rowOid.mOid_Id != (mdb_id)-1) { + if (mDb->IsListRowScopeToken(rowOid.mOid_Scope) || + mDb->IsCardRowScopeToken(rowOid.mOid_Scope)) { + *aResult = true; + + return NS_OK; + } + + if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope)) { + return NS_ERROR_FAILURE; + } + + rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAddrDBEnumerator::GetNext(nsISupports** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (!mDbTable || !mDb->GetEnv()) { + return NS_ERROR_NULL_POINTER; + } + + if (!mRowCursor) { + mDbTable->GetTableRowCursor(mDb->GetEnv(), -1, getter_AddRefs(mRowCursor)); + NS_ENSURE_TRUE(mRowCursor, NS_ERROR_FAILURE); + } + + nsCOMPtr resultCard; + mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos); + while (mCurrentRow) { + mdbOid rowOid; + if (NS_SUCCEEDED(mCurrentRow->GetOid(mDb->GetEnv(), &rowOid))) { + nsresult rv; + if (mDb->IsListRowScopeToken(rowOid.mOid_Scope)) { + rv = mDb->CreateABListCard(mCurrentRow, getter_AddRefs(resultCard)); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mDb->IsCardRowScopeToken(rowOid.mOid_Scope)) { + rv = mDb->CreateABCard(mCurrentRow, 0, getter_AddRefs(resultCard)); + NS_ENSURE_SUCCESS(rv, rv); + } else if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope)) { + return NS_ERROR_FAILURE; + } + + if (resultCard) { + return CallQueryInterface(resultCard, aResult); + } + } + + mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos); + } + + return NS_ERROR_FAILURE; +} + +class nsListAddressEnumerator final : public nsSimpleEnumerator { + public: + const nsID& DefaultInterface() override { return NS_GET_IID(nsIAbCard); } + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + + // nsListAddressEnumerator methods: + nsListAddressEnumerator(nsAddrDatabase* aDb, mdb_id aRowID); + + protected: + ~nsListAddressEnumerator() override = default; + nsAddrDatabase* mDb; + nsIMdbTable* mDbTable; + nsCOMPtr mListRow; + mdb_id mListRowID; + uint32_t mAddressTotal; + uint16_t mAddressPos; +}; + +nsListAddressEnumerator::nsListAddressEnumerator(nsAddrDatabase* aDb, + mdb_id aRowID) + : mDb(aDb), + mDbTable(aDb->GetPabTable()), + mListRowID(aRowID), + mAddressPos(0) { + mDb->GetListRowByRowID(mListRowID, getter_AddRefs(mListRow)); + mAddressTotal = aDb->GetListAddressTotal(mListRow); +} + +NS_IMETHODIMP +nsListAddressEnumerator::HasMoreElements(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + + if (!mDbTable || !mDb->GetEnv()) { + return NS_ERROR_NULL_POINTER; + } + + // In some cases it is possible that GetAddressRowByPos returns success, + // but currentRow is null. This is typically due to the fact that a card + // has been deleted from the parent and not the list. Whilst we have fixed + // that there are still a few dbs around there that we need to support + // correctly. Therefore, whilst processing lists ensure that we don't return + // false if the only thing stopping us is a blank row, just skip it and try + // the next one. + while (mAddressPos < mAddressTotal) { + nsCOMPtr currentRow; + nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos + 1, + getter_AddRefs(currentRow)); + + if (NS_SUCCEEDED(rv) && currentRow) { + *aResult = true; + break; + } + + ++mAddressPos; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsListAddressEnumerator::GetNext(nsISupports** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (!mDbTable || !mDb->GetEnv()) { + return NS_ERROR_NULL_POINTER; + } + + while (++mAddressPos <= mAddressTotal) { + nsCOMPtr currentRow; + nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos, + getter_AddRefs(currentRow)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr resultCard; + rv = + mDb->CreateABCard(currentRow, mListRowID, getter_AddRefs(resultCard)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(resultCard, aResult); + } + } + + return NS_ERROR_FAILURE; +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsAddrDatabase::EnumerateCards(nsISimpleEnumerator** result) { + NS_ADDREF(*result = new nsAddrDBEnumerator(this)); + return NS_OK; +} + +nsresult nsAddrDatabase::EnumerateListAddresses(uint32_t listRowID, + nsISimpleEnumerator** result) { + NS_ADDREF(*result = new nsListAddressEnumerator(this, listRowID)); + return NS_OK; +} + +nsresult nsAddrDatabase::CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, + nsIAbCard** result) { + if (!cardRow || !m_mdbEnv || !result) return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + mdbOid outOid; + mdb_id rowID = 0; + + if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid))) rowID = outOid.mOid_Id; + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr personCard; + personCard = + do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + InitCardFromRow(personCard, cardRow); + personCard->SetPropertyAsUint32(kRowIDProperty, rowID); + + personCard.forget(result); + } + + return rv; +} + +nsresult nsAddrDatabase::CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, + nsIAbCard** result) { + return CreateCard(cardRow, listRowID, result); +} + +/* create a card for mailing list in the address book */ +nsresult nsAddrDatabase::CreateABListCard(nsIMdbRow* listRow, + nsIAbCard** result) { + if (!listRow || !m_mdbEnv || !result) return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + mdbOid outOid; + mdb_id rowID = 0; + + if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid))) rowID = outOid.mOid_Id; + + char* listURI = nullptr; + + nsAutoString fileName; + rv = m_dbName->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + listURI = PR_smprintf("MailList%ld", rowID); + + nsCOMPtr personCard; + personCard = + do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (personCard) { + GetListCardFromDB(personCard, listRow); + + personCard->SetPropertyAsUint32(kRowIDProperty, rowID); + personCard->SetIsMailList(true); + personCard->SetMailListURI(listURI); + } + + personCard.forget(result); + if (listURI) PR_smprintf_free(listURI); + + return rv; +} + +nsresult nsAddrDatabase::GetCardRowByRowID(mdb_id rowID, nsIMdbRow** dbRow) { + if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NULL_POINTER; + + mdbOid rowOid; + rowOid.mOid_Scope = m_CardRowScopeToken; + rowOid.mOid_Id = rowID; + + return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow); +} + +nsresult nsAddrDatabase::GetListRowByRowID(mdb_id rowID, nsIMdbRow** dbRow) { + if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NULL_POINTER; + + mdbOid rowOid; + rowOid.mOid_Scope = m_ListRowScopeToken; + rowOid.mOid_Id = rowID; + + return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow); +} diff --git a/comm/mailnews/import/src/nsAddrDatabase.h b/comm/mailnews/import/src/nsAddrDatabase.h new file mode 100644 index 0000000000..d667c92e47 --- /dev/null +++ b/comm/mailnews/import/src/nsAddrDatabase.h @@ -0,0 +1,158 @@ +/* -*- 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 _nsAddrDatabase_H_ +#define _nsAddrDatabase_H_ + +#include "nsIAbCard.h" +#include "nsIFile.h" +#include "mdb.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +class nsAddrDatabase { + using PathString = mozilla::PathString; + + public: + nsresult SetDbPath(nsIFile* aDbPath); + nsresult Close(bool forceCommit); + nsresult OpenMDB(nsIFile* dbName, bool create); + nsresult CloseMDB(bool commit); + nsresult ForceClosed(void); + nsresult EnumerateCards(nsISimpleEnumerator** _retval); + nsresult EnumerateListAddresses(uint32_t listRowID, + nsISimpleEnumerator** _retval); + + nsAddrDatabase(); + virtual ~nsAddrDatabase(); + + nsresult GetMDBFactory(nsIMdbFactory** aMdbFactory); + nsIMdbEnv* GetEnv() { return m_mdbEnv; } + uint32_t GetCurVersion(); + nsIMdbTableRowCursor* GetTableRowCursor(); + nsIMdbTable* GetPabTable() { return m_mdbPabTable; } + + nsresult CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, + nsIAbCard** result); + nsresult CreateABListCard(nsIMdbRow* listRow, nsIAbCard** result); + + bool IsListRowScopeToken(mdb_scope scope) { + return (scope == m_ListRowScopeToken) ? true : false; + } + bool IsCardRowScopeToken(mdb_scope scope) { + return (scope == m_CardRowScopeToken) ? true : false; + } + bool IsDataRowScopeToken(mdb_scope scope) { + return (scope == m_DataRowScopeToken) ? true : false; + } + nsresult GetCardRowByRowID(mdb_id rowID, nsIMdbRow** dbRow); + nsresult GetListRowByRowID(mdb_id rowID, nsIMdbRow** dbRow); + + uint32_t GetListAddressTotal(nsIMdbRow* listRow); + nsresult GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, + nsIMdbRow** cardRow); + + nsresult InitCardFromRow(nsIAbCard* aNewCard, nsIMdbRow* aCardRow); + + protected: + void YarnToUInt32(struct mdbYarn* yarn, uint32_t* pResult); + nsresult GetStringColumn(nsIMdbRow* cardRow, mdb_token outToken, + nsString& str); + nsresult GetIntColumn(nsIMdbRow* cardRow, mdb_token outToken, + uint32_t* pValue, uint32_t defaultValue); + nsresult GetListCardFromDB(nsIAbCard* listCard, nsIMdbRow* listRow); + nsresult CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard** result); + + // mdb bookkeeping stuff + nsresult InitExistingDB(); + nsresult InitMDBInfo(); + + nsIMdbEnv* m_mdbEnv; // to be used in all the db calls. + nsIMdbStore* m_mdbStore; + nsIMdbTable* m_mdbPabTable; + nsCOMPtr m_dbName; + bool m_mdbTokensInitialized; + + mdb_kind m_PabTableKind; + mdb_kind m_DeletedCardsTableKind; + + mdb_scope m_CardRowScopeToken; + mdb_scope m_ListRowScopeToken; + mdb_scope m_DataRowScopeToken; + + mdb_token m_UIDColumnToken; + mdb_token m_FirstNameColumnToken; + mdb_token m_LastNameColumnToken; + mdb_token m_PhoneticFirstNameColumnToken; + mdb_token m_PhoneticLastNameColumnToken; + mdb_token m_DisplayNameColumnToken; + mdb_token m_NickNameColumnToken; + mdb_token m_PriEmailColumnToken; + mdb_token m_2ndEmailColumnToken; + mdb_token m_DefaultEmailColumnToken; + mdb_token m_CardTypeColumnToken; + mdb_token m_WorkPhoneColumnToken; + mdb_token m_HomePhoneColumnToken; + mdb_token m_FaxColumnToken; + mdb_token m_PagerColumnToken; + mdb_token m_CellularColumnToken; + mdb_token m_WorkPhoneTypeColumnToken; + mdb_token m_HomePhoneTypeColumnToken; + mdb_token m_FaxTypeColumnToken; + mdb_token m_PagerTypeColumnToken; + mdb_token m_CellularTypeColumnToken; + mdb_token m_HomeAddressColumnToken; + mdb_token m_HomeAddress2ColumnToken; + mdb_token m_HomeCityColumnToken; + mdb_token m_HomeStateColumnToken; + mdb_token m_HomeZipCodeColumnToken; + mdb_token m_HomeCountryColumnToken; + mdb_token m_WorkAddressColumnToken; + mdb_token m_WorkAddress2ColumnToken; + mdb_token m_WorkCityColumnToken; + mdb_token m_WorkStateColumnToken; + mdb_token m_WorkZipCodeColumnToken; + mdb_token m_WorkCountryColumnToken; + mdb_token m_JobTitleColumnToken; + mdb_token m_DepartmentColumnToken; + mdb_token m_CompanyColumnToken; + mdb_token m_AimScreenNameColumnToken; + mdb_token m_AnniversaryYearColumnToken; + mdb_token m_AnniversaryMonthColumnToken; + mdb_token m_AnniversaryDayColumnToken; + mdb_token m_SpouseNameColumnToken; + mdb_token m_FamilyNameColumnToken; + mdb_token m_DefaultAddressColumnToken; + mdb_token m_CategoryColumnToken; + mdb_token m_WebPage1ColumnToken; + mdb_token m_WebPage2ColumnToken; + mdb_token m_BirthYearColumnToken; + mdb_token m_BirthMonthColumnToken; + mdb_token m_BirthDayColumnToken; + mdb_token m_Custom1ColumnToken; + mdb_token m_Custom2ColumnToken; + mdb_token m_Custom3ColumnToken; + mdb_token m_Custom4ColumnToken; + mdb_token m_NotesColumnToken; + mdb_token m_LastModDateColumnToken; + mdb_token m_RecordKeyColumnToken; + mdb_token m_LowerPriEmailColumnToken; + mdb_token m_Lower2ndEmailColumnToken; + + mdb_token m_PopularityIndexColumnToken; + + mdb_token m_AddressCharSetColumnToken; + mdb_token m_LastRecordKeyColumnToken; + + mdb_token m_ListNameColumnToken; + mdb_token m_ListNickNameColumnToken; + mdb_token m_ListDescriptionColumnToken; + mdb_token m_ListTotalColumnToken; + mdb_token m_LowerListNameColumnToken; + + nsCOMPtr mMdbFactory; +}; + +#endif diff --git a/comm/mailnews/import/src/nsAppleMailImport.cpp b/comm/mailnews/import/src/nsAppleMailImport.cpp new file mode 100644 index 0000000000..0404014527 --- /dev/null +++ b/comm/mailnews/import/src/nsAppleMailImport.cpp @@ -0,0 +1,609 @@ +/* -*- 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/. */ + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsISupportsPrimitives.h" +#include "nsIImportService.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" +#include "nsIMsgFolder.h" +#include "nsIMsgHdr.h" +#include "nsIMsgPluggableStore.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +#include "mozilla/Components.h" + +#include "nsEmlxHelperUtils.h" +#include "nsAppleMailImport.h" +#include "nsIOutputStream.h" + +// some hard-coded strings +#define DEFAULT_MAIL_FOLDER "~/Library/Mail/" +#define POP_MBOX_SUFFIX ".mbox" +#define IMAP_MBOX_SUFFIX ".imapmbox" + +// stringbundle URI +#define APPLEMAIL_MSGS_URL \ + "chrome://messenger/locale/appleMailImportMsgs.properties" + +// magic constants +#define kAccountMailboxID 1234 + +nsAppleMailImportModule::nsAppleMailImportModule() { + IMPORT_LOG0("nsAppleMailImportModule Created"); + + nsCOMPtr bundleService = + mozilla::components::StringBundle::Service(); + if (bundleService) + bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle)); +} + +nsAppleMailImportModule::~nsAppleMailImportModule() { + IMPORT_LOG0("nsAppleMailImportModule Deleted"); +} + +NS_IMPL_ISUPPORTS(nsAppleMailImportModule, nsIImportModule) + +NS_IMETHODIMP nsAppleMailImportModule::GetName(char16_t** aName) { + if (!mBundle) { + return NS_ERROR_FAILURE; + } + nsAutoString name; + nsresult rv = mBundle->GetStringFromName("ApplemailImportName", name); + NS_ENSURE_SUCCESS(rv, rv); + *aName = ToNewUnicode(name); + return rv; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetDescription(char16_t** aName) { + if (!mBundle) { + return NS_ERROR_FAILURE; + } + nsAutoString name; + nsresult rv = mBundle->GetStringFromName("ApplemailImportDescription", name); + NS_ENSURE_SUCCESS(rv, rv); + *aName = ToNewUnicode(name); + return rv; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetSupports(char** aSupports) { + NS_ENSURE_ARG_POINTER(aSupports); + *aSupports = strdup(NS_IMPORT_MAIL_STR); + return NS_OK; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetSupportsUpgrade(bool* aUpgrade) { + NS_ENSURE_ARG_POINTER(aUpgrade); + *aUpgrade = false; + return NS_OK; +} + +NS_IMETHODIMP nsAppleMailImportModule::GetImportInterface( + const char* aImportType, nsISupports** aInterface) { + NS_ENSURE_ARG_POINTER(aImportType); + NS_ENSURE_ARG_POINTER(aInterface); + *aInterface = nullptr; + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + if (!strcmp(aImportType, "mail")) { + nsCOMPtr mail( + do_CreateInstance(NS_APPLEMAILIMPL_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr generic; + rv = impSvc->CreateNewGenericMail(getter_AddRefs(generic)); + if (NS_SUCCEEDED(rv)) { + nsAutoString name; + rv = mBundle->GetStringFromName("ApplemailImportName", name); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr nameString( + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nameString->SetData(name); + + generic->SetData("name", nameString); + generic->SetData("mailInterface", mail); + + generic.forget(aInterface); + } + } + } + } + + return rv; +} + +#pragma mark - + +nsAppleMailImportMail::nsAppleMailImportMail() : mProgress(0), mCurDepth(0) { + IMPORT_LOG0("nsAppleMailImportMail created"); +} + +nsresult nsAppleMailImportMail::Initialize() { + nsCOMPtr bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + return bundleService->CreateBundle(APPLEMAIL_MSGS_URL, + getter_AddRefs(mBundle)); +} + +nsAppleMailImportMail::~nsAppleMailImportMail() { + IMPORT_LOG0("nsAppleMailImportMail destroyed"); +} + +NS_IMPL_ISUPPORTS(nsAppleMailImportMail, nsIImportMail) + +NS_IMETHODIMP nsAppleMailImportMail::GetDefaultLocation(nsIFile** aLocation, + bool* aFound, + bool* aUserVerify) { + NS_ENSURE_ARG_POINTER(aFound); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aUserVerify); + + *aLocation = nullptr; + *aFound = false; + *aUserVerify = true; + + // try to find current user's top-level Mail folder + nsCOMPtr mailFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + if (mailFolder) { + nsresult rv = + mailFolder->InitWithNativePath(nsLiteralCString(DEFAULT_MAIL_FOLDER)); + if (NS_SUCCEEDED(rv)) { + *aFound = true; + *aUserVerify = false; + mailFolder.forget(aLocation); + } + } + + return NS_OK; +} + +// this is the method that initiates all searching for mailboxes. +// it will assume that it has a directory like ~/Library/Mail/ +NS_IMETHODIMP nsAppleMailImportMail::FindMailboxes( + nsIFile* aMailboxFile, + nsTArray>& boxes) { + NS_ENSURE_ARG_POINTER(aMailboxFile); + + IMPORT_LOG0("FindMailboxes for Apple mail invoked"); + + boxes.Clear(); + bool exists = false; + nsresult rv = aMailboxFile->Exists(&exists); + if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE; + + nsCOMPtr importService( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + mCurDepth = 1; + + // 1. look for accounts with mailboxes + FindAccountMailDirs(aMailboxFile, boxes, importService); + mCurDepth--; + + if (NS_SUCCEEDED(rv)) { + // 2. look for "global" mailboxes, that don't belong to any specific + // account. they are inside the + // root's Mailboxes/ folder + nsCOMPtr mailboxesDir( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + mailboxesDir->InitWithFile(aMailboxFile); + rv = mailboxesDir->Append(u"Mailboxes"_ns); + if (NS_SUCCEEDED(rv)) { + IMPORT_LOG0("Looking for global Apple mailboxes"); + + mCurDepth++; + rv = FindMboxDirs(mailboxesDir, boxes, importService); + mCurDepth--; + } + } + } + return rv; +} + +// operates on the Mail/ directory root, trying to find accounts (which are +// folders named something like "POP-hwaara@gmail.com") and add their .mbox dirs +void nsAppleMailImportMail::FindAccountMailDirs( + nsIFile* aRoot, nsTArray>& aMailboxDescs, + nsIImportService* aImportService) { + nsCOMPtr directoryEnumerator; + nsresult rv = aRoot->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + if (NS_FAILED(rv)) return; + + bool hasMore = false; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + // get the next file entry + nsCOMPtr currentEntry; + directoryEnumerator->GetNextFile(getter_AddRefs(currentEntry)); + if (!currentEntry) continue; + + // make sure it's a directory + bool isDirectory = false; + currentEntry->IsDirectory(&isDirectory); + + if (isDirectory) { + // now let's see if it's an account folder. if so, we want to traverse it + // for .mbox children + nsAutoString folderName; + currentEntry->GetLeafName(folderName); + bool isAccountFolder = false; + + if (StringBeginsWith(folderName, u"POP-"_ns)) { + // cut off "POP-" prefix so we get a nice folder name + folderName.Cut(0, 4); + isAccountFolder = true; + } else if (StringBeginsWith(folderName, u"IMAP-"_ns)) { + // cut off "IMAP-" prefix so we get a nice folder name + folderName.Cut(0, 5); + isAccountFolder = true; + } + + if (isAccountFolder) { + IMPORT_LOG1("Found account: %s\n", + NS_ConvertUTF16toUTF8(folderName).get()); + + // create a mailbox for this account, so we get a parent for "Inbox", + // "Sent Messages", etc. + nsCOMPtr desc; + rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc)); + if (NS_FAILED(rv)) continue; + desc->SetSize(1); + desc->SetDepth(mCurDepth); + desc->SetDisplayName(folderName.get()); + desc->SetIdentifier(kAccountMailboxID); + + nsCOMPtr mailboxDescFile; + rv = desc->GetFile(getter_AddRefs(mailboxDescFile)); + if (NS_FAILED(rv) || !mailboxDescFile) continue; + + mailboxDescFile->InitWithFile(currentEntry); + + // add this mailbox descriptor to the list + aMailboxDescs.AppendElement(desc); + + // now add all the children mailboxes + mCurDepth++; + FindMboxDirs(currentEntry, aMailboxDescs, aImportService); + mCurDepth--; + } + } + } +} + +// adds the specified file as a mailboxdescriptor to the array +nsresult nsAppleMailImportMail::AddMboxDir( + nsIFile* aFolder, + nsTArray>& aMailboxDescs, + nsIImportService* aImportService) { + nsAutoString folderName; + aFolder->GetLeafName(folderName); + + // cut off the suffix, if any, or prefix if this is an account folder. + if (StringEndsWith(folderName, + NS_LITERAL_STRING_FROM_CSTRING(POP_MBOX_SUFFIX))) + folderName.SetLength(folderName.Length() - 5); + else if (StringEndsWith(folderName, + NS_LITERAL_STRING_FROM_CSTRING(IMAP_MBOX_SUFFIX))) + folderName.SetLength(folderName.Length() - 9); + else if (StringBeginsWith(folderName, u"POP-"_ns)) + folderName.Cut(4, folderName.Length()); + else if (StringBeginsWith(folderName, u"IMAP-"_ns)) + folderName.Cut(5, folderName.Length()); + + nsCOMPtr desc; + nsresult rv = + aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc)); + if (NS_SUCCEEDED(rv)) { + // find out number of messages in this .mbox + uint32_t numMessages = 0; + { + // move to the .mbox's Messages folder + nsCOMPtr messagesFolder; + aFolder->Clone(getter_AddRefs(messagesFolder)); + nsresult rv = messagesFolder->Append(u"Messages"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // count the number of messages in this folder. it sucks that we have to + // iterate through the folder but XPCOM doesn't give us any way to just + // get the file count, unfortunately. :-( + nsCOMPtr dirEnumerator; + messagesFolder->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + if (dirEnumerator) { + bool hasMore = false; + while (NS_SUCCEEDED(dirEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr file; + dirEnumerator->GetNextFile(getter_AddRefs(file)); + if (file) { + bool isFile = false; + file->IsFile(&isFile); + if (isFile) numMessages++; + } + } + } + } + + desc->SetSize(numMessages); + desc->SetDisplayName(folderName.get()); + desc->SetDepth(mCurDepth); + + IMPORT_LOG3("Will import %s with approx %d messages, depth is %d", + NS_ConvertUTF16toUTF8(folderName).get(), numMessages, + mCurDepth); + + // XXX: this is silly. there's no setter for the mailbox descriptor's file, + // so we need to get it, and then modify it. + nsCOMPtr mailboxDescFile; + rv = desc->GetFile(getter_AddRefs(mailboxDescFile)); + NS_ENSURE_SUCCESS(rv, rv); + + if (mailboxDescFile) mailboxDescFile->InitWithFile(aFolder); + + // add this mailbox descriptor to the list + aMailboxDescs.AppendElement(desc); + } + + return NS_OK; +} + +// Starts looking for .mbox dirs in the specified dir. The .mbox dirs contain +// messages and can be considered leafs in a tree of nested mailboxes +// (subfolders). +// +// If a mailbox has sub-mailboxes, they are contained in a sibling folder with +// the same name without the ".mbox" part. example: +// MyParentMailbox.mbox/ +// MyParentMailbox/ +// MyChildMailbox.mbox/ +// MyOtherChildMailbox.mbox/ +// +nsresult nsAppleMailImportMail::FindMboxDirs( + nsIFile* aFolder, + nsTArray>& aMailboxDescs, + nsIImportService* aImportService) { + NS_ENSURE_ARG_POINTER(aFolder); + NS_ENSURE_ARG_POINTER(aImportService); + + // make sure this is a directory. + bool isDir = false; + if (NS_FAILED(aFolder->IsDirectory(&isDir)) || !isDir) + return NS_ERROR_FAILURE; + + // iterate through the folder contents + nsCOMPtr directoryEnumerator; + nsresult rv = + aFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator)); + if (NS_FAILED(rv) || !directoryEnumerator) return rv; + + bool hasMore = false; + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + // get the next file entry + nsCOMPtr currentEntry; + directoryEnumerator->GetNextFile(getter_AddRefs(currentEntry)); + if (!currentEntry) continue; + + // we only care about directories... + if (NS_FAILED(currentEntry->IsDirectory(&isDir)) || !isDir) continue; + + // now find out if this is a .mbox dir + nsAutoString currentFolderName; + if (NS_SUCCEEDED(currentEntry->GetLeafName(currentFolderName)) && + (StringEndsWith(currentFolderName, + NS_LITERAL_STRING_FROM_CSTRING(POP_MBOX_SUFFIX)) || + StringEndsWith(currentFolderName, + NS_LITERAL_STRING_FROM_CSTRING(IMAP_MBOX_SUFFIX)))) { + IMPORT_LOG1("Adding .mbox dir: %s", + NS_ConvertUTF16toUTF8(currentFolderName).get()); + + // add this .mbox + rv = AddMboxDir(currentEntry, aMailboxDescs, aImportService); + if (NS_FAILED(rv)) { + IMPORT_LOG1("Couldn't add .mbox for import: %s ... continuing anyway", + NS_ConvertUTF16toUTF8(currentFolderName).get()); + continue; + } + + // see if this .mbox dir has any sub-mailboxes + nsAutoString siblingMailboxDirPath; + currentEntry->GetPath(siblingMailboxDirPath); + + // cut off suffix + if (StringEndsWith(siblingMailboxDirPath, + NS_LITERAL_STRING_FROM_CSTRING(IMAP_MBOX_SUFFIX))) + siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length() - 9); + else if (StringEndsWith(siblingMailboxDirPath, + NS_LITERAL_STRING_FROM_CSTRING(POP_MBOX_SUFFIX))) + siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length() - 5); + + IMPORT_LOG1("trying to locate a '%s'", + NS_ConvertUTF16toUTF8(siblingMailboxDirPath).get()); + nsCOMPtr siblingMailboxDir( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) continue; + + rv = siblingMailboxDir->InitWithPath(siblingMailboxDirPath); + bool reallyExists = false; + siblingMailboxDir->Exists(&reallyExists); + + if (NS_SUCCEEDED(rv) && reallyExists) { + IMPORT_LOG1("Found what looks like an .mbox container: %s", + NS_ConvertUTF16toUTF8(currentFolderName).get()); + + // traverse this folder for other .mboxes + mCurDepth++; + FindMboxDirs(siblingMailboxDir, aMailboxDescs, aImportService); + mCurDepth--; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAppleMailImportMail::ImportMailbox(nsIImportMailboxDescriptor* aMailbox, + nsIMsgFolder* aDstFolder, + char16_t** aErrorLog, + char16_t** aSuccessLog, + bool* aFatalError) { + nsAutoString errorLog, successLog; + + // reset progress + mProgress = 0; + + nsAutoString mailboxName; + aMailbox->GetDisplayName(getter_Copies(mailboxName)); + + nsCOMPtr mboxFolder; + nsresult rv = aMailbox->GetFile(getter_AddRefs(mboxFolder)); + if (NS_FAILED(rv) || !mboxFolder) { + ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_ERROR_FAILURE; + } + + // if we're an account mailbox, nothing do. if we're a real mbox + // then we've got some messages to import! + uint32_t mailboxIdentifier; + aMailbox->GetIdentifier(&mailboxIdentifier); + + if (mailboxIdentifier != kAccountMailboxID) { + // move to the .mbox's Messages folder + nsCOMPtr messagesFolder; + mboxFolder->Clone(getter_AddRefs(messagesFolder)); + rv = messagesFolder->Append(u"Messages"_ns); + if (NS_FAILED(rv)) { + // even if there are no messages, it might still be a valid mailbox, or + // even a parent for other mailboxes. + // + // just indicate that we're done, using the same number that we used to + // estimate number of messages earlier. + uint32_t finalSize; + aMailbox->GetSize(&finalSize); + mProgress = finalSize; + + // report that we successfully imported this mailbox + ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_OK; + } + + // let's import the messages! + nsCOMPtr directoryEnumerator; + rv = messagesFolder->GetDirectoryEntries( + getter_AddRefs(directoryEnumerator)); + if (NS_FAILED(rv)) { + ReportStatus(u"ApplemailImportMailboxConvertError", mailboxName, + errorLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_ERROR_FAILURE; + } + + // prepare an outstream to the destination file + nsCOMPtr msgStore; + rv = aDstFolder->GetMsgStore(getter_AddRefs(msgStore)); + if (!msgStore || NS_FAILED(rv)) { + ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, + errorLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + return NS_ERROR_FAILURE; + } + + bool hasMore = false; + nsCOMPtr outStream; + + while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + // get the next file entry + nsCOMPtr currentEntry; + directoryEnumerator->GetNextFile(getter_AddRefs(currentEntry)); + if (!currentEntry) continue; + + // make sure it's an .emlx file + bool isFile = false; + currentEntry->IsFile(&isFile); + if (!isFile) continue; + + nsAutoString leafName; + currentEntry->GetLeafName(leafName); + if (!StringEndsWith(leafName, u".emlx"_ns)) continue; + + nsCOMPtr msgHdr; + rv = msgStore->GetNewMsgOutputStream(aDstFolder, getter_AddRefs(msgHdr), + getter_AddRefs(outStream)); + if (NS_FAILED(rv)) break; + + // Add the data to the mbox stream. + if (NS_SUCCEEDED(nsEmlxHelperUtils::AddEmlxMessageToStream(currentEntry, + outStream))) { + mProgress++; + msgStore->FinishNewMessage(outStream, msgHdr); + outStream = nullptr; + } else { + msgStore->DiscardNewMessage(outStream, msgHdr); + outStream = nullptr; + break; + } + } + } + // just indicate that we're done, using the same number that we used to + // estimate number of messages earlier. + uint32_t finalSize; + aMailbox->GetSize(&finalSize); + mProgress = finalSize; + + // report that we successfully imported this mailbox + ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog); + SetLogs(successLog, errorLog, aSuccessLog, aErrorLog); + + return NS_OK; +} + +void nsAppleMailImportMail::ReportStatus(const char16_t* aErrorName, + nsString& aName, nsAString& aStream) { + // get (and format, if needed) the error string from the bundle + nsAutoString outString; + AutoTArray fmt = {aName}; + nsresult rv = mBundle->FormatStringFromName( + NS_ConvertUTF16toUTF8(aErrorName).get(), fmt, outString); + // write it out the stream + if (NS_SUCCEEDED(rv)) { + aStream.Append(outString); + aStream.Append(char16_t('\n')); + } +} + +void nsAppleMailImportMail::SetLogs(const nsAString& aSuccess, + const nsAString& aError, + char16_t** aOutSuccess, + char16_t** aOutError) { + if (aOutError && !*aOutError) *aOutError = ToNewUnicode(aError); + if (aOutSuccess && !*aOutSuccess) *aOutSuccess = ToNewUnicode(aSuccess); +} + +NS_IMETHODIMP nsAppleMailImportMail::GetImportProgress(uint32_t* aDoneSoFar) { + NS_ENSURE_ARG_POINTER(aDoneSoFar); + *aDoneSoFar = mProgress; + return NS_OK; +} + +NS_IMETHODIMP nsAppleMailImportMail::TranslateFolderName( + const nsAString& aFolderName, nsAString& aResult) { + aResult = aFolderName; + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsAppleMailImport.h b/comm/mailnews/import/src/nsAppleMailImport.h new file mode 100644 index 0000000000..dd799b06be --- /dev/null +++ b/comm/mailnews/import/src/nsAppleMailImport.h @@ -0,0 +1,86 @@ +/* -*- 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 nsAppleMailImport_h___ +#define nsAppleMailImport_h___ + +#include "mozilla/Logging.h" +#include "nsIImportModule.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsIImportMail.h" +#include "ImportDebug.h" + +#define NS_APPLEMAILIMPL_CID \ + { \ + 0x9117a1ea, 0xe012, 0x43b5, { \ + 0xa0, 0x20, 0xcb, 0x8a, 0x66, 0xcc, 0x09, 0xe1 \ + } \ + } + +#define NS_APPLEMAILIMPORT_CID \ + { \ + 0x6d3f101c, 0x70ec, 0x4e04, { \ + 0xb6, 0x8d, 0x99, 0x08, 0xd1, 0xae, 0xdd, 0xf3 \ + } \ + } + +#define NS_APPLEMAILIMPL_CONTRACTID "@mozilla.org/import/import-appleMailImpl;1" + +#define kAppleMailSupportsString "mail" + +class nsIImportService; + +class nsAppleMailImportModule : public nsIImportModule { + public: + nsAppleMailImportModule(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTMODULE + + private: + virtual ~nsAppleMailImportModule(); + + nsCOMPtr mBundle; +}; + +class nsAppleMailImportMail : public nsIImportMail { + public: + nsAppleMailImportMail(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTMAIL + + nsresult Initialize(); + + private: + virtual ~nsAppleMailImportMail(); + + void FindAccountMailDirs( + nsIFile* aRoot, + nsTArray>& aMailboxDescs, + nsIImportService* aImportService); + nsresult FindMboxDirs( + nsIFile* aFolder, + nsTArray>& aMailboxDescs, + nsIImportService* aImportService); + nsresult AddMboxDir( + nsIFile* aFolder, + nsTArray>& aMailboxDescs, + nsIImportService* aImportService); + + // aInfoString is the format to a "foo %s" string. It may be NULL if the error + // string needs no such format. + void ReportStatus(const char16_t* aErrorName, nsString& aName, + nsAString& aStream); + static void SetLogs(const nsAString& success, const nsAString& error, + char16_t** aOutErrorLog, char16_t** aSuccessLog); + + nsCOMPtr mBundle; + uint32_t mProgress; + uint16_t mCurDepth; +}; + +#endif /* nsAppleMailImport_h___ */ diff --git a/comm/mailnews/import/src/nsBeckyAddressBooks.cpp b/comm/mailnews/import/src/nsBeckyAddressBooks.cpp new file mode 100644 index 0000000000..651bbbeb94 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyAddressBooks.cpp @@ -0,0 +1,311 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIFile.h" +#include "nsIDirectoryEnumerator.h" +#include "nsString.h" +#include "nsIImportService.h" +#include "nsIImportABDescriptor.h" +#include "nsMsgUtils.h" +#include "nsVCardAddress.h" + +#include "nsBeckyAddressBooks.h" +#include "nsBeckyStringBundle.h" +#include "nsBeckyUtils.h" + +NS_IMPL_ISUPPORTS(nsBeckyAddressBooks, nsIImportAddressBooks) + +nsresult nsBeckyAddressBooks::Create(nsIImportAddressBooks** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new nsBeckyAddressBooks()); + return NS_OK; +} + +nsBeckyAddressBooks::nsBeckyAddressBooks() : mReadBytes(0) {} + +nsBeckyAddressBooks::~nsBeckyAddressBooks() {} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetSupportsMultiple(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetAutoFind(char16_t** aDescription, bool* _retval) { + NS_ENSURE_ARG_POINTER(aDescription); + NS_ENSURE_ARG_POINTER(_retval); + + *aDescription = + nsBeckyStringBundle::GetStringByName("BeckyImportDescription"); + *_retval = false; + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetNeedsFieldMap(nsIFile* aLocation, bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = false; + return NS_OK; +} + +nsresult nsBeckyAddressBooks::FindAddressBookDirectory( + nsIFile** aAddressBookDirectory) { + nsCOMPtr userDirectory; + nsresult rv = nsBeckyUtils::FindUserDirectory(getter_AddRefs(userDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = userDirectory->Append(u"AddrBook"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = userDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = userDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND; + + userDirectory.forget(aAddressBookDirectory); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetDefaultLocation(nsIFile** aLocation, bool* aFound, + bool* aUserVerify) { + NS_ENSURE_ARG_POINTER(aFound); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aUserVerify); + + *aLocation = nullptr; + *aFound = false; + *aUserVerify = true; + + if (NS_SUCCEEDED(nsBeckyAddressBooks::FindAddressBookDirectory(aLocation))) { + *aFound = true; + *aUserVerify = false; + } + + return NS_OK; +} + +nsresult nsBeckyAddressBooks::CreateAddressBookDescriptor( + nsIImportABDescriptor** aDescriptor) { + nsresult rv; + nsCOMPtr importService = + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return importService->CreateNewABDescriptor(aDescriptor); +} + +bool nsBeckyAddressBooks::IsAddressBookFile(nsIFile* aFile) { + if (!aFile) return false; + + nsresult rv; + bool isFile = false; + rv = aFile->IsFile(&isFile); + if (NS_FAILED(rv) && !isFile) return false; + + nsAutoString name; + rv = aFile->GetLeafName(name); + return StringEndsWith(name, u".bab"_ns); +} + +bool nsBeckyAddressBooks::HasAddressBookFile(nsIFile* aDirectory) { + if (!aDirectory) return false; + + nsresult rv; + bool isDirectory = false; + rv = aDirectory->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) return false; + + nsCOMPtr entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, false); + + bool more; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, false); + if (IsAddressBookFile(file)) return true; + } + + return false; +} + +uint32_t nsBeckyAddressBooks::CountAddressBookSize(nsIFile* aDirectory) { + if (!aDirectory) return 0; + + nsresult rv; + bool isDirectory = false; + rv = aDirectory->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) return 0; + + nsCOMPtr entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, 0); + + uint32_t total = 0; + bool more; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, 0); + + int64_t size; + file->GetFileSize(&size); + if (total + size > std::numeric_limits::max()) + return std::numeric_limits::max(); + + total += static_cast(size); + } + + return total; +} + +nsresult nsBeckyAddressBooks::AppendAddressBookDescriptor( + nsIFile* aEntry, nsTArray>& books) { + if (!HasAddressBookFile(aEntry)) return NS_OK; + + nsresult rv; + nsCOMPtr descriptor; + rv = CreateAddressBookDescriptor(getter_AddRefs(descriptor)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t size = CountAddressBookSize(aEntry); + descriptor->SetSize(size); + descriptor->SetAbFile(aEntry); + + nsAutoString name; + aEntry->GetLeafName(name); + descriptor->SetPreferredName(name); + + books.AppendElement(descriptor); + return NS_OK; +} + +// Recursively descend down the dirs, appending to the books array. +nsresult nsBeckyAddressBooks::CollectAddressBooks( + nsIFile* aTarget, nsTArray>& books) { + nsresult rv = AppendAddressBookDescriptor(aTarget, books); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr entries; + rv = aTarget->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = file->IsDirectory(&isDirectory); + if (NS_SUCCEEDED(rv) && isDirectory) { + rv = CollectAddressBooks(file, books); + } + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::FindAddressBooks( + nsIFile* aLocation, nsTArray>& books) { + NS_ENSURE_ARG_POINTER(aLocation); + + books.Clear(); + bool isDirectory = false; + nsresult rv = aLocation->IsDirectory(&isDirectory); + if (NS_FAILED(rv) || !isDirectory) return NS_ERROR_FAILURE; + + rv = CollectAddressBooks(aLocation, books); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::InitFieldMap(nsIImportFieldMap* aFieldMap) { + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::ImportAddressBook( + nsIImportABDescriptor* aSource, nsIAbDirectory* aDestination, + nsIImportFieldMap* aFieldMap, nsISupports* aSupportService, + char16_t** aErrorLog, char16_t** aSuccessLog, bool* aFatalError) { + NS_ENSURE_ARG_POINTER(aSource); + NS_ENSURE_ARG_POINTER(aDestination); + NS_ENSURE_ARG_POINTER(aErrorLog); + NS_ENSURE_ARG_POINTER(aSuccessLog); + NS_ENSURE_ARG_POINTER(aFatalError); + + mReadBytes = 0; + + nsCOMPtr file; + nsresult rv = aSource->GetAbFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr entries; + rv = file->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + nsAutoString error; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!IsAddressBookFile(file)) continue; + + bool aborted = false; + nsAutoString name; + aSource->GetPreferredName(name); + nsVCardAddress vcard; + rv = vcard.ImportAddresses(&aborted, name.get(), file, aDestination, error, + &mReadBytes); + if (NS_FAILED(rv)) { + break; + } + } + + if (!error.IsEmpty()) + *aErrorLog = ToNewUnicode(error); + else + *aSuccessLog = + nsBeckyStringBundle::GetStringByName("BeckyImportAddressSuccess"); + + return rv; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::GetImportProgress(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mReadBytes; + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyAddressBooks::SetSampleLocation(nsIFile* aLocation) { return NS_OK; } + +NS_IMETHODIMP +nsBeckyAddressBooks::GetSampleData(int32_t aRecordNumber, bool* aRecordExists, + char16_t** _retval) { + return NS_ERROR_FAILURE; +} diff --git a/comm/mailnews/import/src/nsBeckyAddressBooks.h b/comm/mailnews/import/src/nsBeckyAddressBooks.h new file mode 100644 index 0000000000..af19ea1917 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyAddressBooks.h @@ -0,0 +1,36 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 nsBeckyAddressBooks_h___ +#define nsBeckyAddressBooks_h___ + +#include "nsIImportAddressBooks.h" +#include "nsIFile.h" + +class nsBeckyAddressBooks final : public nsIImportAddressBooks { + public: + nsBeckyAddressBooks(); + static nsresult Create(nsIImportAddressBooks** aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTADDRESSBOOKS + + private: + virtual ~nsBeckyAddressBooks(); + + uint32_t mReadBytes; + + nsresult CollectAddressBooks(nsIFile* aTarget, + nsTArray>& books); + nsresult FindAddressBookDirectory(nsIFile** aAddressBookDirectory); + nsresult AppendAddressBookDescriptor( + nsIFile* aEntry, nsTArray>& books); + uint32_t CountAddressBookSize(nsIFile* aDirectory); + bool HasAddressBookFile(nsIFile* aDirectory); + bool IsAddressBookFile(nsIFile* aFile); + nsresult CreateAddressBookDescriptor(nsIImportABDescriptor** aDescriptor); +}; + +#endif /* nsBeckyAddressBooks_h___ */ diff --git a/comm/mailnews/import/src/nsBeckyFilters.cpp b/comm/mailnews/import/src/nsBeckyFilters.cpp new file mode 100644 index 0000000000..196560311b --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyFilters.cpp @@ -0,0 +1,711 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsILineInputStream.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIMsgFilter.h" +#include "nsIMsgFilterList.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMsgFolder.h" +#include "nsCOMPtr.h" +#include "nsMsgSearchCore.h" +#include "nsMsgUtils.h" +#include "msgCore.h" + +#include "nsBeckyFilters.h" +#include "nsBeckyStringBundle.h" +#include "nsBeckyUtils.h" + +NS_IMPL_ISUPPORTS(nsBeckyFilters, nsIImportFilters) + +nsresult nsBeckyFilters::Create(nsIImportFilters** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new nsBeckyFilters()); + return NS_OK; +} + +nsBeckyFilters::nsBeckyFilters() + : mLocation(nullptr), mServer(nullptr), mConvertedFile(nullptr) {} + +nsBeckyFilters::~nsBeckyFilters() {} + +nsresult nsBeckyFilters::GetDefaultFilterLocation(nsIFile** aFile) { + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv; + nsCOMPtr filterDir; + rv = nsBeckyUtils::GetDefaultMailboxDirectory(getter_AddRefs(filterDir)); + NS_ENSURE_SUCCESS(rv, rv); + + filterDir.forget(aFile); + return NS_OK; +} + +nsresult nsBeckyFilters::GetFilterFile(bool aIncoming, nsIFile* aLocation, + nsIFile** aFile) { + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aFile); + + // We assume the caller has already checked that aLocation is a directory, + // otherwise it would not make sense to call us. + + nsresult rv; + nsCOMPtr filter; + aLocation->Clone(getter_AddRefs(filter)); + if (aIncoming) + rv = filter->Append(u"IFilter.def"_ns); + else + rv = filter->Append(u"OFilter.def"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = filter->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + filter.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyFilters::AutoLocate(char16_t** aDescription, nsIFile** aLocation, + bool* _retval) { + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(_retval); + + if (aDescription) { + *aDescription = + nsBeckyStringBundle::GetStringByName("BeckyImportDescription"); + } + *aLocation = nullptr; + *_retval = false; + + nsresult rv; + nsCOMPtr location; + rv = GetDefaultFilterLocation(getter_AddRefs(location)); + if (NS_FAILED(rv)) + location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + else + *_retval = true; + + location.forget(aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyFilters::SetLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + bool exists = false; + nsresult rv = aLocation->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + mLocation = aLocation; + return NS_OK; +} + +static nsMsgSearchAttribValue ConvertSearchKeyToAttrib(const nsACString& aKey) { + if (aKey.EqualsLiteral("From") || aKey.EqualsLiteral("Sender") || + aKey.EqualsLiteral("From, Sender, X-Sender")) { + return nsMsgSearchAttrib::Sender; + } else if (aKey.EqualsLiteral("Subject")) { + return nsMsgSearchAttrib::Subject; + } else if (aKey.EqualsLiteral("[body]")) { + return nsMsgSearchAttrib::Body; + } else if (aKey.EqualsLiteral("Date")) { + return nsMsgSearchAttrib::Date; + } else if (aKey.EqualsLiteral("To")) { + return nsMsgSearchAttrib::To; + } else if (aKey.EqualsLiteral("Cc")) { + return nsMsgSearchAttrib::CC; + } else if (aKey.EqualsLiteral("To, Cc, Bcc:")) { + return nsMsgSearchAttrib::ToOrCC; + } + return -1; +} + +static nsMsgSearchOpValue ConvertSearchFlagsToOperator( + const nsACString& aFlags) { + nsCString flags(aFlags); + int32_t lastTabPosition = flags.RFindChar('\t'); + if ((lastTabPosition == -1) || + ((int32_t)aFlags.Length() == lastTabPosition - 1)) { + return -1; + } + + switch (aFlags.CharAt(0)) { + case 'X': + return nsMsgSearchOp::DoesntContain; + case 'O': + if (aFlags.FindChar('T', lastTabPosition + 1) >= 0) + return nsMsgSearchOp::BeginsWith; + return nsMsgSearchOp::Contains; + default: + return -1; + } +} + +nsresult nsBeckyFilters::ParseRuleLine(const nsCString& aLine, + nsMsgSearchAttribValue* aSearchAttribute, + nsMsgSearchOpValue* aSearchOperator, + nsString& aSearchKeyword) { + int32_t firstColonPosition = aLine.FindChar(':'); + if (firstColonPosition == -1 || + (int32_t)aLine.Length() == firstColonPosition - 1) { + return NS_ERROR_FAILURE; + } + + int32_t secondColonPosition = aLine.FindChar(':', firstColonPosition + 1); + if (secondColonPosition == -1 || + (int32_t)aLine.Length() == secondColonPosition - 1) { + return NS_ERROR_FAILURE; + } + + int32_t length = secondColonPosition - firstColonPosition - 1; + nsMsgSearchAttribValue searchAttribute; + searchAttribute = ConvertSearchKeyToAttrib( + Substring(aLine, firstColonPosition + 1, length)); + if (searchAttribute < 0) return NS_ERROR_FAILURE; + + int32_t tabPosition = aLine.FindChar('\t'); + if (tabPosition == -1 || (int32_t)aLine.Length() == tabPosition - 1) { + return NS_ERROR_FAILURE; + } + + nsMsgSearchOpValue searchOperator; + searchOperator = + ConvertSearchFlagsToOperator(Substring(aLine, tabPosition + 1)); + if (searchOperator < 0) return NS_ERROR_FAILURE; + + *aSearchOperator = searchOperator; + *aSearchAttribute = searchAttribute; + length = tabPosition - secondColonPosition - 1; + CopyUTF8toUTF16(Substring(aLine, secondColonPosition + 1, length), + aSearchKeyword); + return NS_OK; +} + +nsresult nsBeckyFilters::SetSearchTerm(const nsCString& aLine, + nsIMsgFilter* aFilter) { + NS_ENSURE_ARG_POINTER(aFilter); + + nsresult rv; + nsMsgSearchAttribValue searchAttribute = -1; + nsMsgSearchOpValue searchOperator = -1; + nsAutoString searchKeyword; + rv = ParseRuleLine(aLine, &searchAttribute, &searchOperator, searchKeyword); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr term; + rv = aFilter->CreateTerm(getter_AddRefs(term)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = term->SetAttrib(searchAttribute); + NS_ENSURE_SUCCESS(rv, rv); + rv = term->SetOp(searchOperator); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr value; + rv = term->GetValue(getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = value->SetAttrib(searchAttribute); + NS_ENSURE_SUCCESS(rv, rv); + rv = value->SetStr(searchKeyword); + NS_ENSURE_SUCCESS(rv, rv); + rv = term->SetValue(value); + NS_ENSURE_SUCCESS(rv, rv); + rv = term->SetBooleanAnd(false); + NS_ENSURE_SUCCESS(rv, rv); + + if (!searchKeyword.IsEmpty()) + rv = aFilter->SetFilterName(searchKeyword); + else + rv = aFilter->SetFilterName(u"No name"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + return aFilter->AppendTerm(term); +} + +nsresult nsBeckyFilters::CreateRuleAction(nsIMsgFilter* aFilter, + nsMsgRuleActionType actionType, + nsIMsgRuleAction** _retval) { + nsresult rv; + nsCOMPtr action; + rv = aFilter->CreateAction(getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + rv = action->SetType(actionType); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult nsBeckyFilters::GetActionTarget(const nsCString& aLine, + nsCString& aTarget) { + int32_t firstColonPosition = aLine.FindChar(':'); + if (firstColonPosition < -1 || + aLine.Length() == static_cast(firstColonPosition)) { + return NS_ERROR_FAILURE; + } + + aTarget.Assign(Substring(aLine, firstColonPosition + 1)); + + return NS_OK; +} + +nsresult nsBeckyFilters::GetResendTarget(const nsCString& aLine, + nsCString& aTemplate, + nsCString& aTargetAddress) { + nsresult rv; + nsAutoCString target; + rv = GetActionTarget(aLine, target); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t asteriskPosition = target.FindChar('*'); + if (asteriskPosition < 0) { + aTemplate.Assign(target); + return NS_OK; + } + + if (target.Length() == static_cast(asteriskPosition)) + return NS_ERROR_FAILURE; + + aTemplate.Assign(StringHead(target, asteriskPosition - 1)); + aTargetAddress.Assign(Substring(target, asteriskPosition + 1)); + + return NS_OK; +} + +nsresult nsBeckyFilters::CreateResendAction( + const nsCString& aLine, nsIMsgFilter* aFilter, + const nsMsgRuleActionType& aActionType, nsIMsgRuleAction** _retval) { + nsresult rv; + nsCOMPtr action; + rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString templateString; + nsAutoCString targetAddress; + rv = GetResendTarget(aLine, templateString, targetAddress); + NS_ENSURE_SUCCESS(rv, rv); + + if (aActionType == nsMsgFilterAction::Forward) + rv = action->SetStrValue(targetAddress); + else + rv = action->SetStrValue(templateString); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult nsBeckyFilters::GetFolderNameFromTarget(const nsCString& aTarget, + nsAString& aName) { + int32_t backslashPosition = aTarget.RFindChar('\\'); + if (backslashPosition > 0) { + NS_ConvertUTF8toUTF16 utf16String( + Substring(aTarget, backslashPosition + 1)); + nsBeckyUtils::TranslateFolderName(utf16String, aName); + } + + return NS_OK; +} + +nsresult nsBeckyFilters::GetDistributeTarget(const nsCString& aLine, + nsCString& aTargetFolder) { + nsresult rv; + nsAutoCString target; + rv = GetActionTarget(aLine, target); + NS_ENSURE_SUCCESS(rv, rv); + + target.Trim("\\", false, true); + nsAutoString folderName; + rv = GetFolderNameFromTarget(target, folderName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr folder; + rv = GetMessageFolder(folderName, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!folder) { + rv = mServer->GetRootMsgFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + } + return folder->GetURI(aTargetFolder); +} + +nsresult nsBeckyFilters::CreateDistributeAction( + const nsCString& aLine, nsIMsgFilter* aFilter, + const nsMsgRuleActionType& aActionType, nsIMsgRuleAction** _retval) { + nsresult rv; + nsCOMPtr action; + rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString targetFolder; + rv = GetDistributeTarget(aLine, targetFolder); + NS_ENSURE_SUCCESS(rv, rv); + rv = action->SetTargetFolderUri(targetFolder); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult nsBeckyFilters::CreateLeaveOrDeleteAction(const nsCString& aLine, + nsIMsgFilter* aFilter, + nsIMsgRuleAction** _retval) { + nsresult rv; + nsMsgRuleActionType actionType; + if (aLine.CharAt(3) == '0') { + actionType = nsMsgFilterAction::LeaveOnPop3Server; + } else if (aLine.CharAt(3) == '1') { + if (aLine.CharAt(5) == '1') + actionType = nsMsgFilterAction::Delete; + else + actionType = nsMsgFilterAction::DeleteFromPop3Server; + } else { + return NS_ERROR_FAILURE; + } + nsCOMPtr action; + rv = CreateRuleAction(aFilter, actionType, getter_AddRefs(action)); + NS_ENSURE_SUCCESS(rv, rv); + + action.forget(_retval); + + return NS_OK; +} + +nsresult nsBeckyFilters::SetRuleAction(const nsCString& aLine, + nsIMsgFilter* aFilter) { + if (!aFilter || aLine.Length() < 4) return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + nsCOMPtr action; + switch (aLine.CharAt(1)) { + case 'R': // Reply + rv = CreateResendAction(aLine, aFilter, nsMsgFilterAction::Reply, + getter_AddRefs(action)); + break; + case 'F': // Forward + rv = CreateResendAction(aLine, aFilter, nsMsgFilterAction::Forward, + getter_AddRefs(action)); + break; + case 'L': // Leave or delete + rv = CreateLeaveOrDeleteAction(aLine, aFilter, getter_AddRefs(action)); + break; + case 'Y': // Copy + rv = CreateDistributeAction(aLine, aFilter, + nsMsgFilterAction::CopyToFolder, + getter_AddRefs(action)); + break; + case 'M': // Move + rv = CreateDistributeAction(aLine, aFilter, + nsMsgFilterAction::MoveToFolder, + getter_AddRefs(action)); + break; + case 'G': // Set flag + if (aLine.CharAt(3) == 'R') // Read + rv = CreateRuleAction(aFilter, nsMsgFilterAction::MarkRead, + getter_AddRefs(action)); + break; + default: + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + + if (action) { + rv = aFilter->AppendAction(action); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult nsBeckyFilters::CreateFilter(bool aIncoming, nsIMsgFilter** _retval) { + NS_ENSURE_STATE(mServer); + + nsCOMPtr filterList; + mServer->GetFilterList(nullptr, getter_AddRefs(filterList)); + NS_ENSURE_STATE(filterList); + + nsCOMPtr filter; + nsresult rv = filterList->CreateFilter(EmptyString(), getter_AddRefs(filter)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aIncoming) + filter->SetFilterType(nsMsgFilterType::InboxRule | nsMsgFilterType::Manual); + else + filter->SetFilterType(nsMsgFilterType::PostOutgoing | + nsMsgFilterType::Manual); + + filter->SetEnabled(true); + filter.forget(_retval); + + return NS_OK; +} + +nsresult nsBeckyFilters::AppendFilter(nsIMsgFilter* aFilter) { + NS_ENSURE_STATE(mServer); + + nsCOMPtr filterList; + mServer->GetFilterList(nullptr, getter_AddRefs(filterList)); + NS_ENSURE_STATE(filterList); + + uint32_t count; + nsresult rv = filterList->GetFilterCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + + return filterList->InsertFilterAt(count, aFilter); +} + +nsresult nsBeckyFilters::ParseFilterFile(nsIFile* aFile, bool aIncoming) { + nsresult rv; + nsCOMPtr lineStream; + rv = nsBeckyUtils::CreateLineInputStream(aFile, getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + nsAutoCString line; + + nsCOMPtr filter; + while (NS_SUCCEEDED(rv) && more) { + rv = lineStream->ReadLine(line, &more); + + switch (line.CharAt(0)) { + case ':': + if (line.EqualsLiteral(":Begin \"\"")) { + CreateFilter(aIncoming, getter_AddRefs(filter)); + } else if (line.EqualsLiteral(":End \"\"")) { + if (filter) AppendFilter(filter); + filter = nullptr; + } + break; + case '!': + SetRuleAction(line, filter); + break; + case '@': + SetSearchTerm(line, filter); + break; + case '$': // $X: disabled + if (StringBeginsWith(line, "$X"_ns) && filter) { + filter->SetEnabled(false); + } + break; + default: + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyFilters::Import(char16_t** aError, bool* _retval) { + NS_ENSURE_ARG_POINTER(aError); + NS_ENSURE_ARG_POINTER(_retval); + + // If mLocation is null, set it to the default filter directory. + // If mLocation is a file, we import it as incoming folder. + // If mLocation is a directory, we try to import incoming and outgoing folders + // from it (in default files). + + *_retval = false; + nsresult rv; + nsCOMPtr filterFile; + + bool haveFile = false; + + if (!mLocation) { + bool retval = false; + rv = AutoLocate(nullptr, getter_AddRefs(mLocation), &retval); + NS_ENSURE_SUCCESS(rv, rv); + if (!retval) return NS_ERROR_FILE_NOT_FOUND; + } + + // What type of location do we have? + bool isDirectory = false; + rv = mLocation->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (isDirectory) { + haveFile = false; + } else { + bool isFile = false; + rv = mLocation->IsFile(&isFile); + NS_ENSURE_SUCCESS(rv, rv); + if (isFile) { + haveFile = true; + mLocation->Clone(getter_AddRefs(filterFile)); + } else { + // mLocation is neither file nor directory. + return NS_ERROR_UNEXPECTED; + } + } + + bool haveIncoming = true; + if (haveFile) { + // If the passed filename equals OFilter.def, import as outgoing filters. + // Everything else is considered incoming. + nsAutoString fileName; + rv = mLocation->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + if (fileName.EqualsLiteral("OFilter.def")) haveIncoming = false; + } + + // Try importing from the passed in file or the default incoming filters file. + if ((haveFile && haveIncoming) || + (!haveFile && NS_SUCCEEDED(GetFilterFile(true, mLocation, + getter_AddRefs(filterFile))))) { + rv = CollectServers(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsBeckyUtils::ConvertToUTF8File(filterFile, + getter_AddRefs(mConvertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ParseFilterFile(mConvertedFile, true); + if (NS_SUCCEEDED(rv)) *_retval = true; + + (void)RemoveConvertedFile(); + } + + // If we didn't have a file passed (but a directory), try finding also + // outgoing filters. + if ((haveFile && !haveIncoming) || + (!haveFile && NS_SUCCEEDED(GetFilterFile(false, mLocation, + getter_AddRefs(filterFile))))) { + rv = CollectServers(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nsBeckyUtils::ConvertToUTF8File(filterFile, + getter_AddRefs(mConvertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ParseFilterFile(mConvertedFile, false); + if (NS_SUCCEEDED(rv)) *_retval = true; + + (void)RemoveConvertedFile(); + } + + return rv; +} + +nsresult nsBeckyFilters::FindMessageFolder(const nsAString& aName, + nsIMsgFolder* aParentFolder, + nsIMsgFolder** _retval) { + nsresult rv; + + nsCOMPtr found; + rv = aParentFolder->GetChildNamed(aName, getter_AddRefs(found)); + if (found) { + NS_ADDREF(*_retval = found); + return NS_OK; + } + + nsTArray> children; + rv = aParentFolder->GetSubFolders(children); + NS_ENSURE_SUCCESS(rv, rv); + + for (nsIMsgFolder* child : children) { + rv = FindMessageFolder(aName, child, getter_AddRefs(found)); + if (found) { + NS_ADDREF(*_retval = found); + return NS_OK; + } + } + + return NS_MSG_ERROR_INVALID_FOLDER_NAME; +} + +nsresult nsBeckyFilters::FindMessageFolderInServer( + const nsAString& aName, nsIMsgIncomingServer* aServer, + nsIMsgFolder** _retval) { + nsresult rv; + nsCOMPtr rootFolder; + rv = aServer->GetRootMsgFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + return FindMessageFolder(aName, rootFolder, _retval); +} + +nsresult nsBeckyFilters::GetMessageFolder(const nsAString& aName, + nsIMsgFolder** _retval) { + nsresult rv; + + nsCOMPtr accountManager; + accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray> accounts; + rv = accountManager->GetAccounts(accounts); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr found; + for (auto account : accounts) { + if (!account) continue; + + nsCOMPtr server; + account->GetIncomingServer(getter_AddRefs(server)); + if (!server) continue; + FindMessageFolderInServer(aName, server, getter_AddRefs(found)); + if (found) break; + } + + if (!found) { + nsCOMPtr server; + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + FindMessageFolderInServer(aName, server, getter_AddRefs(found)); + } + + if (!found) return NS_MSG_ERROR_INVALID_FOLDER_NAME; + + found.forget(_retval); + + return NS_OK; +} + +nsresult nsBeckyFilters::CollectServers() { + nsresult rv; + nsCOMPtr accountManager; + accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, rv); + if (defaultAccount) + return defaultAccount->GetIncomingServer(getter_AddRefs(mServer)); + + // We can also import filters into the Local Folders account. + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(mServer)); + NS_ENSURE_SUCCESS(rv, rv); + if (!mServer) return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +nsresult nsBeckyFilters::RemoveConvertedFile() { + nsresult rv = NS_OK; + if (mConvertedFile) { + bool exists = false; + mConvertedFile->Exists(&exists); + if (exists) { + rv = mConvertedFile->Remove(false); + if (NS_SUCCEEDED(rv)) mConvertedFile = nullptr; + } + } + return rv; +} diff --git a/comm/mailnews/import/src/nsBeckyFilters.h b/comm/mailnews/import/src/nsBeckyFilters.h new file mode 100644 index 0000000000..91ee2ed813 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyFilters.h @@ -0,0 +1,73 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 nsBeckyFilters_h___ +#define nsBeckyFilters_h___ + +#include "nsIImportFilters.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgFilterCore.h" + +class nsIMsgFilter; +class nsIMsgRuleAction; + +class nsBeckyFilters final : public nsIImportFilters { + public: + nsBeckyFilters(); + static nsresult Create(nsIImportFilters** aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTFILTERS + + private: + virtual ~nsBeckyFilters(); + + nsCOMPtr mLocation; + nsCOMPtr mServer; + nsCOMPtr mConvertedFile; + + nsresult GetDefaultFilterLocation(nsIFile** aFile); + nsresult GetFilterFile(bool aIncoming, nsIFile* aLocation, nsIFile** aFile); + nsresult ParseFilterFile(nsIFile* aFile, bool aIncoming); + nsresult ParseRuleLine(const nsCString& aLine, + nsMsgSearchAttribValue* aSearchAttribute, + nsMsgSearchOpValue* aSearchOperator, + nsString& aSearchKeyword); + nsresult CollectServers(); + nsresult FindMessageFolder(const nsAString& aName, + nsIMsgFolder* aParantFolder, + nsIMsgFolder** _retval); + nsresult FindMessageFolderInServer(const nsAString& aName, + nsIMsgIncomingServer* aServer, + nsIMsgFolder** _retval); + nsresult GetMessageFolder(const nsAString& aName, nsIMsgFolder** _retval); + nsresult GetActionTarget(const nsCString& aLine, nsCString& aTarget); + nsresult GetFolderNameFromTarget(const nsCString& aTarget, nsAString& aName); + nsresult GetDistributeTarget(const nsCString& aLine, + nsCString& aTargetFolder); + nsresult GetResendTarget(const nsCString& aLine, nsCString& aTemplate, + nsCString& aTargetAddress); + nsresult CreateRuleAction(nsIMsgFilter* aFilter, + nsMsgRuleActionType actionType, + nsIMsgRuleAction** _retval); + nsresult CreateDistributeAction(const nsCString& aLine, nsIMsgFilter* aFilter, + const nsMsgRuleActionType& aActionType, + nsIMsgRuleAction** _retval); + nsresult CreateLeaveOrDeleteAction(const nsCString& aLine, + nsIMsgFilter* aFilter, + nsIMsgRuleAction** _retval); + nsresult CreateResendAction(const nsCString& aLine, nsIMsgFilter* aFilter, + const nsMsgRuleActionType& aActionType, + nsIMsgRuleAction** _retval); + nsresult CreateFilter(bool aIncoming, nsIMsgFilter** _retval); + nsresult AppendFilter(nsIMsgFilter* aFilter); + nsresult SetRuleAction(const nsCString& aLine, nsIMsgFilter* aFilter); + nsresult SetSearchTerm(const nsCString& aLine, nsIMsgFilter* aFilter); + nsresult RemoveConvertedFile(); +}; + +#endif /* nsBeckyFilters_h___ */ diff --git a/comm/mailnews/import/src/nsBeckyImport.cpp b/comm/mailnews/import/src/nsBeckyImport.cpp new file mode 100644 index 0000000000..8b56ba95d3 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyImport.cpp @@ -0,0 +1,143 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nscore.h" +#include "nsIImportService.h" +#include "nsIImportMail.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportSettings.h" +#include "nsIImportFilters.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsTextFormatter.h" +#include "nsUnicharUtils.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" + +#include "nsBeckyImport.h" +#include "nsBeckyMail.h" +#include "nsBeckyAddressBooks.h" +#include "nsBeckySettings.h" +#include "nsBeckyFilters.h" +#include "nsBeckyStringBundle.h" + +nsBeckyImport::nsBeckyImport() {} + +nsBeckyImport::~nsBeckyImport() {} + +NS_IMPL_ISUPPORTS(nsBeckyImport, nsIImportModule) + +NS_IMETHODIMP +nsBeckyImport::GetName(char16_t** aName) { + NS_ENSURE_ARG_POINTER(aName); + *aName = nsBeckyStringBundle::GetStringByName("BeckyImportName"); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyImport::GetDescription(char16_t** aDescription) { + NS_ENSURE_ARG_POINTER(aDescription); + *aDescription = + nsBeckyStringBundle::GetStringByName("BeckyImportDescription"); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyImport::GetSupports(char** aSupports) { + NS_ENSURE_ARG_POINTER(aSupports); + *aSupports = strdup(kBeckySupportsString); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyImport::GetSupportsUpgrade(bool* aUpgrade) { + NS_ENSURE_ARG_POINTER(aUpgrade); + *aUpgrade = true; + return NS_OK; +} + +nsresult nsBeckyImport::GetMailImportInterface(nsISupports** aInterface) { + nsCOMPtr importer; + nsresult rv = nsBeckyMail::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr importService( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr generic; + rv = importService->CreateNewGenericMail(getter_AddRefs(generic)); + NS_ENSURE_SUCCESS(rv, rv); + + generic->SetData("mailInterface", importer); + + nsString name; + name.Adopt(nsBeckyStringBundle::GetStringByName("BeckyImportName")); + + nsCOMPtr nameString( + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nameString->SetData(name); + generic->SetData("name", nameString); + + return CallQueryInterface(generic, aInterface); +} + +nsresult nsBeckyImport::GetAddressBookImportInterface( + nsISupports** aInterface) { + nsresult rv; + nsCOMPtr importer; + rv = nsBeckyAddressBooks::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr importService( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr generic; + rv = importService->CreateNewGenericAddressBooks(getter_AddRefs(generic)); + NS_ENSURE_SUCCESS(rv, rv); + + generic->SetData("addressInterface", importer); + return CallQueryInterface(generic, aInterface); +} + +nsresult nsBeckyImport::GetSettingsImportInterface(nsISupports** aInterface) { + nsresult rv; + nsCOMPtr importer; + rv = nsBeckySettings::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(importer, aInterface); +} + +nsresult nsBeckyImport::GetFiltersImportInterface(nsISupports** aInterface) { + nsresult rv; + nsCOMPtr importer; + rv = nsBeckyFilters::Create(getter_AddRefs(importer)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(importer, aInterface); +} + +NS_IMETHODIMP +nsBeckyImport::GetImportInterface(const char* aImportType, + nsISupports** aInterface) { + NS_ENSURE_ARG_POINTER(aImportType); + NS_ENSURE_ARG_POINTER(aInterface); + + *aInterface = nullptr; + if (!strcmp(aImportType, "mail")) return GetMailImportInterface(aInterface); + if (!strcmp(aImportType, "addressbook")) + return GetAddressBookImportInterface(aInterface); + if (!strcmp(aImportType, "settings")) + return GetSettingsImportInterface(aInterface); + if (!strcmp(aImportType, "filters")) + return GetFiltersImportInterface(aInterface); + + return NS_ERROR_NOT_AVAILABLE; +} diff --git a/comm/mailnews/import/src/nsBeckyImport.h b/comm/mailnews/import/src/nsBeckyImport.h new file mode 100644 index 0000000000..0884d417fb --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyImport.h @@ -0,0 +1,38 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 nsBeckyImport_h___ +#define nsBeckyImport_h___ + +#include "nsIImportModule.h" + +#define NS_BECKYIMPORT_CID \ + { \ + 0x7952a6cf, 0x2442, 0x4c04, { \ + 0x9f, 0x02, 0x15, 0x0b, 0x15, 0xa0, 0xa8, 0x41 \ + } \ + } + +#define kBeckySupportsString \ + NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR \ + "," NS_IMPORT_FILTERS_STR + +class nsBeckyImport final : public nsIImportModule { + public: + nsBeckyImport(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTMODULE + + private: + virtual ~nsBeckyImport(); + + nsresult GetMailImportInterface(nsISupports** aInterface); + nsresult GetAddressBookImportInterface(nsISupports** aInterface); + nsresult GetSettingsImportInterface(nsISupports** aInterface); + nsresult GetFiltersImportInterface(nsISupports** aInterface); +}; + +#endif /* nsBeckyImport_h___ */ diff --git a/comm/mailnews/import/src/nsBeckyMail.cpp b/comm/mailnews/import/src/nsBeckyMail.cpp new file mode 100644 index 0000000000..36ab1e1104 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyMail.cpp @@ -0,0 +1,566 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsILineInputStream.h" +#include "nsNetUtil.h" +#include "nsIImportService.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsIMsgPluggableStore.h" +#include "nsMsgUtils.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgMessageFlags.h" +#include "nsTArray.h" +#include "nspr.h" +#include "nsThreadUtils.h" +#include "nsIDirectoryEnumerator.h" + +#include "nsBeckyMail.h" +#include "nsBeckyUtils.h" +#include "nsBeckyStringBundle.h" + +#define FROM_LINE "From - Mon Jan 1 00:00:00 1965" MSG_LINEBREAK +#define X_BECKY_STATUS_HEADER "X-Becky-Status" +#define X_BECKY_INCLUDE_HEADER "X-Becky-Include" + +enum { + BECKY_STATUS_READ = 1 << 0, + BECKY_STATUS_FORWARDED = 1 << 1, + BECKY_STATUS_REPLIED = 1 << 2 +}; + +NS_IMPL_ISUPPORTS(nsBeckyMail, nsIImportMail) + +nsresult nsBeckyMail::Create(nsIImportMail** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new nsBeckyMail()); + return NS_OK; +} + +nsBeckyMail::nsBeckyMail() : mReadBytes(0) {} + +nsBeckyMail::~nsBeckyMail() {} + +NS_IMETHODIMP +nsBeckyMail::GetDefaultLocation(nsIFile** aLocation, bool* aFound, + bool* aUserVerify) { + NS_ENSURE_ARG_POINTER(aFound); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(aUserVerify); + + *aLocation = nullptr; + *aUserVerify = true; + *aFound = false; + if (NS_SUCCEEDED(nsBeckyUtils::GetDefaultMailboxDirectory(aLocation))) + *aFound = true; + + return NS_OK; +} + +nsresult nsBeckyMail::CreateMailboxDescriptor( + nsIImportMailboxDescriptor** aDescriptor) { + nsresult rv; + nsCOMPtr importService; + importService = do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return importService->CreateNewMailboxDescriptor(aDescriptor); +} + +nsresult nsBeckyMail::GetMailboxName(nsIFile* aMailbox, nsAString& aName) { + nsCOMPtr iniFile; + nsBeckyUtils::GetMailboxINIFile(aMailbox, getter_AddRefs(iniFile)); + if (iniFile) { + nsCOMPtr convertedFile; + nsBeckyUtils::ConvertToUTF8File(iniFile, getter_AddRefs(convertedFile)); + if (convertedFile) { + nsAutoCString utf8Name; + nsBeckyUtils::GetMailboxNameFromINIFile(convertedFile, utf8Name); + convertedFile->Remove(false); + CopyUTF8toUTF16(utf8Name, aName); + } + } + + if (aName.IsEmpty()) { + nsAutoString name; + aMailbox->GetLeafName(name); + name.Trim("!", true, false); + aName.Assign(name); + } + + return NS_OK; +} + +nsresult nsBeckyMail::AppendMailboxDescriptor( + nsIFile* aEntry, const nsString& aName, uint32_t aDepth, + nsTArray>& aCollected) { + nsresult rv; + nsCOMPtr descriptor; + rv = CreateMailboxDescriptor(getter_AddRefs(descriptor)); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t size; + rv = aEntry->GetFileSize(&size); + NS_ENSURE_SUCCESS(rv, rv); + + rv = descriptor->SetSize(size); + NS_ENSURE_SUCCESS(rv, rv); + + rv = descriptor->SetDisplayName(aName.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr mailboxFile; + rv = descriptor->GetFile(getter_AddRefs(mailboxFile)); + NS_ENSURE_SUCCESS(rv, rv); + + descriptor->SetDepth(aDepth); + + mailboxFile->InitWithFile(aEntry); + aCollected.AppendElement(descriptor); + + return NS_OK; +} + +nsresult nsBeckyMail::CollectMailboxesInFolderListFile( + nsIFile* aListFile, uint32_t aDepth, + nsTArray>& aCollected) { + nsresult rv; + nsCOMPtr lineStream; + rv = nsBeckyUtils::CreateLineInputStream(aListFile, + getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr parent; + rv = aListFile->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + nsAutoCString folderName; + bool isEmpty = true; + while (more && NS_SUCCEEDED(rv)) { + rv = lineStream->ReadLine(folderName, &more); + NS_ENSURE_SUCCESS(rv, rv); + + if (folderName.IsEmpty()) continue; + + nsCOMPtr folder; + rv = parent->Clone(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = folder->AppendNative(folderName); + NS_ENSURE_SUCCESS(rv, rv); + + isEmpty = false; + rv = CollectMailboxesInDirectory(folder, aDepth + 1, aCollected); + } + + return isEmpty ? NS_ERROR_FILE_NOT_FOUND : NS_OK; +} + +nsresult nsBeckyMail::CollectMailboxesInDirectory( + nsIFile* aDirectory, uint32_t aDepth, + nsTArray>& aCollected) { + nsAutoString mailboxName; + nsresult rv = GetMailboxName(aDirectory, mailboxName); + NS_ENSURE_SUCCESS(rv, rv); + + if (aDepth != 0) + AppendMailboxDescriptor(aDirectory, mailboxName, aDepth, aCollected); + + nsCOMPtr folderListFile; + rv = nsBeckyUtils::GetFolderListFile(aDirectory, + getter_AddRefs(folderListFile)); + bool folderListExists = false; + + if (NS_SUCCEEDED(rv)) { + rv = CollectMailboxesInFolderListFile(folderListFile, aDepth, aCollected); + folderListExists = true; + } + + nsCOMPtr entries; + rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString name; + rv = file->GetLeafName(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (StringEndsWith(name, u".bmf"_ns)) { + AppendMailboxDescriptor(file, mailboxName, aDepth, aCollected); + } + + // The Folder.lst file is not created if there is only one sub folder, + // so we need to find the sub folder by our hands. + // The folder name does not begin with # or ! maybe. Yes, maybe... + if (!folderListExists) { + if (StringBeginsWith(name, u"#"_ns) || StringBeginsWith(name, u"!"_ns)) + continue; + + bool isDirectory = false; + rv = file->IsDirectory(&isDirectory); + if (isDirectory) { + CollectMailboxesInDirectory(file, aDepth + 1, aCollected); + continue; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyMail::FindMailboxes( + nsIFile* aLocation, nsTArray>& boxes) { + NS_ENSURE_ARG_POINTER(aLocation); + + boxes.Clear(); + nsresult rv = CollectMailboxesInDirectory(aLocation, 0, boxes); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +static nsresult GetBeckyStatusValue(const nsCString& aHeader, + nsACString& aValue) { + int32_t valueStartPosition; + + valueStartPosition = aHeader.FindChar(':'); + if (valueStartPosition < 0) return NS_ERROR_UNEXPECTED; + + valueStartPosition++; + + int32_t commaPosition = aHeader.FindChar(',', valueStartPosition); + if (commaPosition < 0) return NS_ERROR_UNEXPECTED; + + nsAutoCString value(Substring(aHeader, valueStartPosition, + commaPosition - valueStartPosition)); + value.Trim(" \t"); + + aValue.Assign(value); + + return NS_OK; +} + +static nsresult GetBeckyIncludeValue(const nsCString& aHeader, + nsACString& aValue) { + int32_t valueStartPosition; + + valueStartPosition = aHeader.FindChar(':'); + if (valueStartPosition < 0) return NS_ERROR_FAILURE; + + valueStartPosition++; + nsAutoCString value(Substring(aHeader, valueStartPosition)); + value.Trim(" \t"); + + aValue.Assign(value); + + return NS_OK; +} + +static bool ConvertBeckyStatusToMozillaStatus( + const nsCString& aHeader, nsMsgMessageFlagType* aMozillaStatusFlag) { + nsresult rv; + nsAutoCString statusString; + rv = GetBeckyStatusValue(aHeader, statusString); + NS_ENSURE_SUCCESS(rv, false); + + nsresult errorCode; + uint32_t beckyStatusFlag = + static_cast(statusString.ToInteger(&errorCode, 16)); + if (NS_FAILED(errorCode)) return false; + + if (beckyStatusFlag & BECKY_STATUS_READ) + *aMozillaStatusFlag |= nsMsgMessageFlags::Read; + if (beckyStatusFlag & BECKY_STATUS_FORWARDED) + *aMozillaStatusFlag |= nsMsgMessageFlags::Forwarded; + if (beckyStatusFlag & BECKY_STATUS_REPLIED) + *aMozillaStatusFlag |= nsMsgMessageFlags::Replied; + + return true; +} + +static inline bool CheckHeaderKey(const nsCString& aHeader, + const char* aKeyString) { + nsAutoCString key(StringHead(aHeader, aHeader.FindChar(':'))); + key.Trim(" \t"); + return key.Equals(aKeyString); +} + +static inline bool IsBeckyStatusHeader(const nsCString& aHeader) { + return CheckHeaderKey(aHeader, X_BECKY_STATUS_HEADER); +} + +static inline bool IsBeckyIncludeLine(const nsCString& aLine) { + return CheckHeaderKey(aLine, X_BECKY_INCLUDE_HEADER); +} + +static inline bool IsEndOfHeaders(const nsCString& aLine) { + return aLine.IsEmpty(); +} + +static inline bool IsEndOfMessage(const nsCString& aLine) { + return aLine.EqualsLiteral("."); +} + +class ImportMessageRunnable : public mozilla::Runnable { + public: + ImportMessageRunnable(nsIFile* aMessageFile, nsIMsgFolder* aFolder); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + private: + nsresult WriteHeaders(nsCString& aHeaders, nsIOutputStream* aOutputStream); + nsresult HandleHeaderLine(const nsCString& aHeaderLine, nsACString& aHeaders); + nsresult GetAttachmentFile(nsIFile* aMailboxFile, const nsCString& aHeader, + nsIFile** _retval); + nsresult WriteAttachmentFile(nsIFile* aMailboxFile, const nsCString& aHeader, + nsIOutputStream* aOutputStream); + + nsCOMPtr mMessageFile; + nsCOMPtr mFolder; +}; + +ImportMessageRunnable::ImportMessageRunnable(nsIFile* aMessageFile, + nsIMsgFolder* aFolder) + : mozilla::Runnable("ImportMessageRunnable"), + mMessageFile(aMessageFile), + mFolder(aFolder) {} + +nsresult ImportMessageRunnable::WriteHeaders(nsCString& aHeaders, + nsIOutputStream* aOutputStream) { + nsresult rv; + uint32_t writtenBytes = 0; + + rv = aOutputStream->Write(FROM_LINE, strlen(FROM_LINE), &writtenBytes); + NS_ENSURE_SUCCESS(rv, rv); + rv = aOutputStream->Write(aHeaders.get(), aHeaders.Length(), &writtenBytes); + NS_ENSURE_SUCCESS(rv, rv); + rv = + aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes); + NS_ENSURE_SUCCESS(rv, rv); + aHeaders.Truncate(); + + return NS_OK; +} + +nsresult ImportMessageRunnable::HandleHeaderLine(const nsCString& aHeaderLine, + nsACString& aHeaders) { + aHeaders.Append(aHeaderLine); + aHeaders.AppendLiteral(MSG_LINEBREAK); + + nsMsgMessageFlagType flag = 0; + if (IsBeckyStatusHeader(aHeaderLine) && + ConvertBeckyStatusToMozillaStatus(aHeaderLine, &flag)) { + char* statusLine; + statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flag); + aHeaders.Append(statusLine); + PR_smprintf_free(statusLine); + aHeaders.AppendLiteral(X_MOZILLA_KEYWORDS); + } + + return NS_OK; +} + +nsresult ImportMessageRunnable::GetAttachmentFile(nsIFile* aMailboxFile, + const nsCString& aHeader, + nsIFile** _retval) { + nsresult rv; + nsCOMPtr attachmentFile; + + rv = aMailboxFile->Clone(getter_AddRefs(attachmentFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = attachmentFile->Append(u"#Attach"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString nativeAttachmentPath; + rv = GetBeckyIncludeValue(aHeader, nativeAttachmentPath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = attachmentFile->AppendRelativeNativePath(nativeAttachmentPath); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + attachmentFile->Exists(&exists); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + attachmentFile.forget(_retval); + return NS_OK; +} + +nsresult ImportMessageRunnable::WriteAttachmentFile( + nsIFile* aMailboxFile, const nsCString& aHeader, + nsIOutputStream* aOutputStream) { + nsresult rv; + nsCOMPtr parentDirectory; + rv = aMailboxFile->GetParent(getter_AddRefs(parentDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr attachmentFile; + rv = GetAttachmentFile(parentDirectory, aHeader, + getter_AddRefs(attachmentFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), attachmentFile); + NS_ENSURE_SUCCESS(rv, rv); + + char buffer[FILE_IO_BUFFER_SIZE]; + uint32_t readBytes = 0; + uint32_t writtenBytes = 0; + rv = + aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes); + while (NS_SUCCEEDED(inputStream->Read(buffer, sizeof(buffer), &readBytes)) && + readBytes > 0) { + rv = aOutputStream->Write(buffer, readBytes, &writtenBytes); + if (NS_FAILED(rv)) break; + } + + return rv; +} + +NS_IMETHODIMP ImportMessageRunnable::Run() { + nsCOMPtr msgStore; + mResult = mFolder->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(mResult, NS_OK); + + nsCOMPtr lineStream; + mResult = nsBeckyUtils::CreateLineInputStream(mMessageFile, + getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(mResult, NS_OK); + + nsCOMPtr msgHdr; + nsCOMPtr outputStream; + mResult = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr), + getter_AddRefs(outputStream)); + NS_ENSURE_SUCCESS(mResult, NS_OK); + + bool inHeader = true; + bool more = true; + nsAutoCString headers; + while (NS_SUCCEEDED(mResult) && more) { + nsAutoCString line; + mResult = lineStream->ReadLine(line, &more); + if (NS_FAILED(mResult)) break; + + if (inHeader) { + if (IsEndOfHeaders(line)) { + inHeader = false; + mResult = WriteHeaders(headers, outputStream); + } else { + mResult = HandleHeaderLine(line, headers); + } + } else if (IsEndOfMessage(line)) { + inHeader = true; + mResult = msgStore->FinishNewMessage(outputStream, msgHdr); + // outputStream is closed by FinishNewMessage(). + outputStream = nullptr; + mResult = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr), + getter_AddRefs(outputStream)); + } else if (IsBeckyIncludeLine(line)) { + mResult = WriteAttachmentFile(mMessageFile, line, outputStream); + } else { + uint32_t writtenBytes = 0; + if (StringBeginsWith(line, ".."_ns)) + line.Cut(0, 1); + else if (CheckHeaderKey(line, "From")) + line.Insert('>', 0); + + line.AppendLiteral(MSG_LINEBREAK); + mResult = outputStream->Write(line.get(), line.Length(), &writtenBytes); + } + } + + if (outputStream) { + // DiscardNewMessage() closes outputStream. + if (NS_FAILED(mResult)) + msgStore->DiscardNewMessage(outputStream, msgHdr); + else + outputStream->Close(); /* No check? */ + } + + return NS_OK; +} + +static nsresult ProxyImportMessage(nsIFile* aMessageFile, + nsIMsgFolder* aFolder) { + RefPtr importMessage = + new ImportMessageRunnable(aMessageFile, aFolder); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyImportMessage"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(importMessage)); + NS_ENSURE_SUCCESS(rv, rv); + return importMessage->mResult; +} + +nsresult nsBeckyMail::ImportMailFile(nsIFile* aMailFile, + nsIMsgFolder* aDestination) { + int64_t size; + aMailFile->GetFileSize(&size); + if (size == 0) return NS_OK; + + return ProxyImportMessage(aMailFile, aDestination); +} + +NS_IMETHODIMP +nsBeckyMail::ImportMailbox(nsIImportMailboxDescriptor* aSource, + nsIMsgFolder* aDestination, char16_t** aErrorLog, + char16_t** aSuccessLog, bool* aFatalError) { + NS_ENSURE_ARG_POINTER(aSource); + NS_ENSURE_ARG_POINTER(aDestination); + NS_ENSURE_ARG_POINTER(aErrorLog); + NS_ENSURE_ARG_POINTER(aSuccessLog); + NS_ENSURE_ARG_POINTER(aFatalError); + + mReadBytes = 0; + + nsresult rv; + nsCOMPtr mailboxFolder; + rv = aSource->GetFile(getter_AddRefs(mailboxFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ImportMailFile(mailboxFolder, aDestination); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t finalSize; + aSource->GetSize(&finalSize); + mReadBytes = finalSize; + + nsAutoString name; + aSource->GetDisplayName(getter_Copies(name)); + + nsAutoString successMessage; + AutoTArray format = {name}; + rv = nsBeckyStringBundle::FormatStringFromName("BeckyImportMailboxSuccess", + format, successMessage); + successMessage.AppendLiteral("\n"); + *aSuccessLog = ToNewUnicode(successMessage); + + return rv; +} + +NS_IMETHODIMP +nsBeckyMail::GetImportProgress(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mReadBytes; + return NS_OK; +} + +NS_IMETHODIMP +nsBeckyMail::TranslateFolderName(const nsAString& aFolderName, + nsAString& _retval) { + return nsBeckyUtils::TranslateFolderName(aFolderName, _retval); +} diff --git a/comm/mailnews/import/src/nsBeckyMail.h b/comm/mailnews/import/src/nsBeckyMail.h new file mode 100644 index 0000000000..e0b445b1fe --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyMail.h @@ -0,0 +1,41 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 nsBeckyMail_h___ +#define nsBeckyMail_h___ + +#include "nsIImportMail.h" + +class nsIFile; +class nsIMsgFolder; + +class nsBeckyMail final : public nsIImportMail { + public: + nsBeckyMail(); + static nsresult Create(nsIImportMail** aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTMAIL + + private: + virtual ~nsBeckyMail(); + + uint32_t mReadBytes; + + nsresult CollectMailboxesInDirectory( + nsIFile* aDirectory, uint32_t aDepth, + nsTArray>& aCollected); + nsresult CollectMailboxesInFolderListFile( + nsIFile* aListFile, uint32_t aDepth, + nsTArray>& aCollected); + nsresult AppendMailboxDescriptor( + nsIFile* aEntry, const nsString& aName, uint32_t aDepth, + nsTArray>& aCollected); + nsresult ImportMailFile(nsIFile* aMailFile, nsIMsgFolder* aDestination); + nsresult CreateMailboxDescriptor(nsIImportMailboxDescriptor** aDescriptor); + nsresult GetMailboxName(nsIFile* aMailbox, nsAString& aName); +}; + +#endif /* nsBeckyMail_h___ */ diff --git a/comm/mailnews/import/src/nsBeckySettings.cpp b/comm/mailnews/import/src/nsBeckySettings.cpp new file mode 100644 index 0000000000..9722a62007 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckySettings.cpp @@ -0,0 +1,379 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMsgAccountManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIINIParser.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsIPop3IncomingServer.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "msgCore.h" +#include "nsBeckySettings.h" +#include "nsBeckyStringBundle.h" +#include "nsBeckyUtils.h" + +NS_IMPL_ISUPPORTS(nsBeckySettings, nsIImportSettings) + +nsresult nsBeckySettings::Create(nsIImportSettings** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new nsBeckySettings()); + return NS_OK; +} + +nsBeckySettings::nsBeckySettings() {} + +nsBeckySettings::~nsBeckySettings() {} + +NS_IMETHODIMP +nsBeckySettings::AutoLocate(char16_t** aDescription, nsIFile** aLocation, + bool* _retval) { + NS_ENSURE_ARG_POINTER(aDescription); + NS_ENSURE_ARG_POINTER(aLocation); + NS_ENSURE_ARG_POINTER(_retval); + + *aDescription = nsBeckyStringBundle::GetStringByName("BeckyImportName"); + *aLocation = nullptr; + *_retval = false; + + nsCOMPtr location; + nsresult rv = + nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(location)); + if (NS_FAILED(rv)) + location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + else + *_retval = true; + + location.forget(aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsBeckySettings::SetLocation(nsIFile* aLocation) { + mLocation = aLocation; + return NS_OK; +} + +nsresult nsBeckySettings::CreateParser() { + if (!mLocation) { + nsresult rv = + nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(mLocation)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // nsIINIParser accepts only UTF-8 encoding, so we need to convert the file + // first. + nsresult rv; + rv = nsBeckyUtils::ConvertToUTF8File(mLocation, + getter_AddRefs(mConvertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + + return nsBeckyUtils::CreateINIParserForFile(mConvertedFile, + getter_AddRefs(mParser)); +} + +nsresult nsBeckySettings::CreateSmtpServer(const nsCString& aUserName, + const nsCString& aServerName, + nsISmtpServer** aServer, + bool* existing) { + nsresult rv; + + nsCOMPtr smtpService = + do_GetService("@mozilla.org/messengercompose/smtp;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr server; + rv = smtpService->FindServer(aUserName.get(), aServerName.get(), + getter_AddRefs(server)); + + if (NS_FAILED(rv) || !server) { + rv = smtpService->CreateServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + server->SetHostname(aServerName); + server->SetUsername(aUserName); + *existing = false; + } else { + *existing = true; + } + + server.forget(aServer); + + return NS_OK; +} + +nsresult nsBeckySettings::CreateIncomingServer(const nsCString& aUserName, + const nsCString& aServerName, + const nsCString& aProtocol, + nsIMsgIncomingServer** aServer) { + nsresult rv; + nsCOMPtr accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr incomingServer; + accountManager->FindServer(aUserName, aServerName, aProtocol, 0, + getter_AddRefs(incomingServer)); + + if (!incomingServer) { + rv = accountManager->CreateIncomingServer(aUserName, aServerName, aProtocol, + getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, rv); + } + incomingServer.forget(aServer); + + return NS_OK; +} + +nsresult nsBeckySettings::SetupSmtpServer(nsISmtpServer** aServer) { + nsresult rv; + nsAutoCString userName, serverName; + + mParser->GetString("Account"_ns, "SMTPServer"_ns, serverName); + mParser->GetString("Account"_ns, "UserID"_ns, userName); + + nsCOMPtr server; + bool existing = false; + rv = + CreateSmtpServer(userName, serverName, getter_AddRefs(server), &existing); + NS_ENSURE_SUCCESS(rv, rv); + + // If we already have an existing server, do not touch it's settings. + if (existing) { + server.forget(aServer); + return NS_OK; + } + + nsAutoCString value; + rv = mParser->GetString("Account"_ns, "SMTPPort"_ns, value); + int32_t port = 25; + if (NS_SUCCEEDED(rv)) { + nsresult errorCode; + port = value.ToInteger(&errorCode, 10); + } + server->SetPort(port); + + mParser->GetString("Account"_ns, "SSLSMTP"_ns, value); + if (value.EqualsLiteral("1")) server->SetSocketType(nsMsgSocketType::SSL); + + mParser->GetString("Account"_ns, "SMTPAUTH"_ns, value); + if (value.EqualsLiteral("1")) { + mParser->GetString("Account"_ns, "SMTPAUTHMODE"_ns, value); + nsMsgAuthMethodValue authMethod = nsMsgAuthMethod::none; + if (value.EqualsLiteral("1")) { + authMethod = nsMsgAuthMethod::passwordEncrypted; + } else if (value.EqualsLiteral("2") || value.EqualsLiteral("4") || + value.EqualsLiteral("6")) { + authMethod = nsMsgAuthMethod::passwordCleartext; + } else { + authMethod = nsMsgAuthMethod::anything; + } + server->SetAuthMethod(authMethod); + } + + server.forget(aServer); + + return NS_OK; +} + +nsresult nsBeckySettings::SetPop3ServerProperties( + nsIMsgIncomingServer* aServer) { + nsCOMPtr pop3Server = do_QueryInterface(aServer); + + nsAutoCString value; + mParser->GetString("Account"_ns, "POP3Auth"_ns, + value); // 0: plain, 1: APOP, 2: CRAM-MD5, 3: NTLM + nsMsgAuthMethodValue authMethod; + if (value.IsEmpty() || value.EqualsLiteral("0")) { + authMethod = nsMsgAuthMethod::passwordCleartext; + } else if (value.EqualsLiteral("1")) { + authMethod = nsMsgAuthMethod::old; + } else if (value.EqualsLiteral("2")) { + authMethod = nsMsgAuthMethod::passwordEncrypted; + } else if (value.EqualsLiteral("3")) { + authMethod = nsMsgAuthMethod::NTLM; + } else { + authMethod = nsMsgAuthMethod::none; + } + aServer->SetAuthMethod(authMethod); + + mParser->GetString("Account"_ns, "LeaveServer"_ns, value); + if (value.EqualsLiteral("1")) { + pop3Server->SetLeaveMessagesOnServer(true); + nsresult rv = mParser->GetString("Account"_ns, "KeepDays"_ns, value); + if (NS_FAILED(rv)) return NS_OK; + + nsresult errorCode; + int32_t leftDays = value.ToInteger(&errorCode, 10); + if (NS_SUCCEEDED(errorCode)) { + pop3Server->SetNumDaysToLeaveOnServer(leftDays); + pop3Server->SetDeleteByAgeFromServer(true); + } + } + + return NS_OK; +} + +nsresult nsBeckySettings::SetupIncomingServer(nsIMsgIncomingServer** aServer) { + nsAutoCString value; + mParser->GetString("Account"_ns, "Protocol"_ns, value); + nsCString protocol; + if (value.EqualsLiteral("1")) { + protocol = "imap"_ns; + } else { + protocol = "pop3"_ns; + } + + nsAutoCString userName, serverName; + mParser->GetString("Account"_ns, "MailServer"_ns, serverName); + mParser->GetString("Account"_ns, "UserID"_ns, userName); + + nsresult rv; + nsCOMPtr server; + rv = CreateIncomingServer(userName, serverName, protocol, + getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isSecure = false; + int32_t port = 0; + nsresult errorCode; + if (protocol.EqualsLiteral("pop3")) { + SetPop3ServerProperties(server); + rv = mParser->GetString("Account"_ns, "POP3Port"_ns, value); + if (NS_SUCCEEDED(rv)) + port = value.ToInteger(&errorCode, 10); + else + port = 110; + mParser->GetString("Account"_ns, "SSLPOP"_ns, value); + if (value.EqualsLiteral("1")) isSecure = true; + } else if (protocol.EqualsLiteral("imap")) { + rv = mParser->GetString("Account"_ns, "IMAP4Port"_ns, value); + if (NS_SUCCEEDED(rv)) + port = value.ToInteger(&errorCode, 10); + else + port = 143; + mParser->GetString("Account"_ns, "SSLIMAP"_ns, value); + if (value.EqualsLiteral("1")) isSecure = true; + } + + server->SetPort(port); + if (isSecure) server->SetSocketType(nsMsgSocketType::SSL); + + mParser->GetString("Account"_ns, "CheckInt"_ns, value); + if (value.EqualsLiteral("1")) server->SetDoBiff(true); + rv = mParser->GetString("Account"_ns, "CheckEvery"_ns, value); + if (NS_SUCCEEDED(rv)) { + int32_t minutes = value.ToInteger(&errorCode, 10); + if (NS_SUCCEEDED(errorCode)) server->SetBiffMinutes(minutes); + } + + server.forget(aServer); + + return NS_OK; +} + +nsresult nsBeckySettings::CreateIdentity(nsIMsgIdentity** aIdentity) { + nsAutoCString email, fullName, identityName, bccAddress; + + mParser->GetString("Account"_ns, "Name"_ns, identityName); + mParser->GetString("Account"_ns, "YourName"_ns, fullName); + mParser->GetString("Account"_ns, "MailAddress"_ns, email); + mParser->GetString("Account"_ns, "PermBcc"_ns, bccAddress); + + nsresult rv; + nsCOMPtr accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr identity; + rv = accountManager->CreateIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + + identity->SetLabel(NS_ConvertUTF8toUTF16(identityName)); + identity->SetFullName(NS_ConvertUTF8toUTF16(fullName)); + identity->SetEmail(email); + if (!bccAddress.IsEmpty()) { + identity->SetDoBcc(true); + identity->SetDoBccList(bccAddress); + } + + identity.forget(aIdentity); + + return NS_OK; +} + +nsresult nsBeckySettings::CreateAccount(nsIMsgIdentity* aIdentity, + nsIMsgIncomingServer* aIncomingServer, + nsIMsgAccount** aAccount) { + nsresult rv; + nsCOMPtr accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr account; + rv = accountManager->CreateAccount(getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = account->AddIdentity(aIdentity); + NS_ENSURE_SUCCESS(rv, rv); + + rv = account->SetIncomingServer(aIncomingServer); + NS_ENSURE_SUCCESS(rv, rv); + + account.forget(aAccount); + + return NS_OK; +} + +nsresult nsBeckySettings::RemoveConvertedFile() { + if (mConvertedFile) { + bool exists; + mConvertedFile->Exists(&exists); + if (exists) mConvertedFile->Remove(false); + mConvertedFile = nullptr; + } + return NS_OK; +} + +#define NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(expr, rv) \ + if (NS_FAILED(expr)) { \ + RemoveConvertedFile(); \ + return rv; \ + } + +NS_IMETHODIMP +nsBeckySettings::Import(nsIMsgAccount** aLocalMailAccount, bool* _retval) { + NS_ENSURE_ARG_POINTER(aLocalMailAccount); + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = CreateParser(); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsCOMPtr incomingServer; + rv = SetupIncomingServer(getter_AddRefs(incomingServer)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsCOMPtr smtpServer; + rv = SetupSmtpServer(getter_AddRefs(smtpServer)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsCOMPtr identity; + rv = CreateIdentity(getter_AddRefs(identity)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + nsAutoCString smtpKey; + smtpServer->GetKey(getter_Copies(smtpKey)); + identity->SetSmtpServerKey(smtpKey); + + nsCOMPtr account; + rv = CreateAccount(identity, incomingServer, getter_AddRefs(account)); + NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv); + + RemoveConvertedFile(); + if (aLocalMailAccount) account.forget(aLocalMailAccount); + *_retval = true; + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsBeckySettings.h b/comm/mailnews/import/src/nsBeckySettings.h new file mode 100644 index 0000000000..bbbe9fe268 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckySettings.h @@ -0,0 +1,50 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 nsBeckySettings_h___ +#define nsBeckySettings_h___ + +#include "nsIImportSettings.h" +#include "nsIFile.h" +#include "nsIINIParser.h" + +class nsIMsgIncomingServer; +class nsIMsgIdentity; +class nsISmtpServer; + +class nsBeckySettings final : public nsIImportSettings { + public: + nsBeckySettings(); + static nsresult Create(nsIImportSettings** aImport); + + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTSETTINGS + + private: + virtual ~nsBeckySettings(); + + nsCOMPtr mLocation; + nsCOMPtr mConvertedFile; + nsCOMPtr mParser; + + nsresult CreateParser(); + nsresult CreateIdentity(nsIMsgIdentity** aIdentity); + nsresult CreateAccount(nsIMsgIdentity* aIdentity, + nsIMsgIncomingServer* aIncomingServer, + nsIMsgAccount** aAccount); + nsresult CreateSmtpServer(const nsCString& aUserName, + const nsCString& aServerName, + nsISmtpServer** aServer, bool* existing); + nsresult CreateIncomingServer(const nsCString& aUserName, + const nsCString& aServerName, + const nsCString& aProtocol, + nsIMsgIncomingServer** aServer); + nsresult SetupIncomingServer(nsIMsgIncomingServer** aServer); + nsresult SetupSmtpServer(nsISmtpServer** aServer); + nsresult SetPop3ServerProperties(nsIMsgIncomingServer* aServer); + nsresult RemoveConvertedFile(); +}; + +#endif /* nsBeckySettings_h___ */ diff --git a/comm/mailnews/import/src/nsBeckyStringBundle.cpp b/comm/mailnews/import/src/nsBeckyStringBundle.cpp new file mode 100644 index 0000000000..1037fdafb2 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyStringBundle.cpp @@ -0,0 +1,55 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsBeckyStringBundle.h" + +#define BECKY_MESSAGES_URL \ + "chrome://messenger/locale/beckyImportMsgs.properties" + +nsCOMPtr nsBeckyStringBundle::mBundle = nullptr; + +void nsBeckyStringBundle::GetStringBundle(void) { + if (mBundle) return; + + nsresult rv; + nsCOMPtr bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && bundleService) + rv = bundleService->CreateBundle(BECKY_MESSAGES_URL, + getter_AddRefs(mBundle)); +} + +void nsBeckyStringBundle::EnsureStringBundle(void) { + if (!mBundle) GetStringBundle(); +} + +char16_t* nsBeckyStringBundle::GetStringByName(const char* aName) { + EnsureStringBundle(); + + if (mBundle) { + nsAutoString string; + mBundle->GetStringFromName(aName, string); + return ToNewUnicode(string); + } + + return nullptr; +} + +nsresult nsBeckyStringBundle::FormatStringFromName(const char* name, + nsTArray& params, + nsAString& _retval) { + EnsureStringBundle(); + + return mBundle->FormatStringFromName(name, params, _retval); +} + +void nsBeckyStringBundle::Cleanup(void) { mBundle = nullptr; } diff --git a/comm/mailnews/import/src/nsBeckyStringBundle.h b/comm/mailnews/import/src/nsBeckyStringBundle.h new file mode 100644 index 0000000000..2b7045ebcc --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyStringBundle.h @@ -0,0 +1,32 @@ +/* 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 _nsBeckyStringBundle_H__ +#define _nsBeckyStringBundle_H__ + +#include "nsString.h" + +class nsIStringBundle; + +class nsBeckyStringBundle final { + public: + static char16_t* GetStringByName(const char* name); + static nsresult FormatStringFromName(const char* name, + nsTArray& params, + nsAString& _retval); + static void GetStringBundle(void); + static void EnsureStringBundle(void); + static void Cleanup(void); + + private: + static nsCOMPtr mBundle; +}; + +#define BECKYIMPORT_NAME 2000 +#define BECKYIMPORT_DESCRIPTION 2001 +#define BECKYIMPORT_MAILBOX_SUCCESS 2002 +#define BECKYIMPORT_MAILBOX_BADPARAM 2003 +#define BECKYIMPORT_MAILBOX_CONVERTERROR 2004 +#define BECKYIMPORT_ADDRESS_SUCCESS 2005 + +#endif /* _nsBeckyStringBundle_H__ */ diff --git a/comm/mailnews/import/src/nsBeckyUtils.cpp b/comm/mailnews/import/src/nsBeckyUtils.cpp new file mode 100644 index 0000000000..ecb1c35a53 --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyUtils.cpp @@ -0,0 +1,302 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIDirectoryEnumerator.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsString.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsILineInputStream.h" +#include "nsIConverterInputStream.h" +#include "nsIConverterOutputStream.h" +#include "nsMsgI18N.h" +#include "nsNetUtil.h" +#include "nsIINIParser.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsMsgUtils.h" +#include "msgCore.h" +#include "nsIImportMail.h" +#include "nsThreadUtils.h" + +#include "nsBeckyUtils.h" +#include "SpecialSystemDirectory.h" + +nsresult nsBeckyUtils::FindUserDirectoryOnWindows7(nsIFile** aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsresult rv; + nsCOMPtr directory; + rv = GetSpecialSystemDirectory(Win_Documents, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + rv = directory->AppendNative("Becky"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = directory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND; + + directory.forget(aLocation); + return NS_OK; +} + +nsresult nsBeckyUtils::FindUserDirectoryOnWindowsXP(nsIFile** aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsresult rv; + nsCOMPtr directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->InitWithPath(u"C:\\Becky!"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists = false; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = directory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND; + + nsCOMPtr entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more; + while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + file.forget(aLocation); + return NS_OK; + } + } + + directory.forget(aLocation); + return NS_OK; +} + +nsresult nsBeckyUtils::FindUserDirectory(nsIFile** aLocation) { + nsresult rv = FindUserDirectoryOnWindows7(aLocation); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + rv = FindUserDirectoryOnWindowsXP(aLocation); + } + return rv; +} + +nsresult nsBeckyUtils::ConvertNativeStringToUTF8(const nsACString& aOriginal, + nsACString& _retval) { + nsresult rv; + nsAutoString unicodeString; + rv = NS_CopyNativeToUnicode(aOriginal, unicodeString); + NS_ENSURE_SUCCESS(rv, rv); + + CopyUTF16toUTF8(unicodeString, _retval); + return NS_OK; +} + +nsresult nsBeckyUtils::CreateLineInputStream(nsIFile* aFile, + nsILineInputStream** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + nsCOMPtr inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(inputStream, _retval); +} + +nsresult nsBeckyUtils::GetFolderListFile(nsIFile* aLocation, + nsIFile** _retval) { + nsresult rv; + nsCOMPtr folderListFile; + rv = aLocation->Clone(getter_AddRefs(folderListFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = folderListFile->Append(u"Folder.lst"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = folderListFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + folderListFile.forget(_retval); + return NS_OK; +} + +nsresult nsBeckyUtils::GetDefaultFolderName(nsIFile* aFolderListFile, + nsACString& name) { + nsresult rv; + nsCOMPtr lineStream; + rv = CreateLineInputStream(aFolderListFile, getter_AddRefs(lineStream)); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + rv = lineStream->ReadLine(name, &more); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult nsBeckyUtils::GetDefaultMailboxDirectory(nsIFile** _retval) { + nsCOMPtr userDirectory; + nsresult rv = FindUserDirectory(getter_AddRefs(userDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr folderListFile; + rv = GetFolderListFile(userDirectory, getter_AddRefs(folderListFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString defaultFolderName; + rv = GetDefaultFolderName(folderListFile, defaultFolderName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = userDirectory->AppendNative(defaultFolderName); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = userDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + bool isDirectory = false; + rv = userDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND; + + userDirectory.forget(_retval); + return NS_OK; +} + +nsresult nsBeckyUtils::GetDefaultMailboxINIFile(nsIFile** _retval) { + nsresult rv; + nsCOMPtr mailboxDirectory; + rv = GetDefaultMailboxDirectory(getter_AddRefs(mailboxDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + return GetMailboxINIFile(mailboxDirectory, _retval); +} + +nsresult nsBeckyUtils::GetMailboxINIFile(nsIFile* aDirectory, + nsIFile** _retval) { + nsresult rv; + nsCOMPtr target; + rv = aDirectory->Clone(getter_AddRefs(target)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = target->Append(u"Mailbox.ini"_ns); + NS_ENSURE_SUCCESS(rv, rv); + bool exists; + rv = target->Exists(&exists); + if (!exists) return NS_ERROR_FILE_NOT_FOUND; + + target.forget(_retval); + return NS_OK; +} + +nsresult nsBeckyUtils::CreateINIParserForFile(nsIFile* aFile, + nsIINIParser** aParser) { + nsresult rv; + nsCOMPtr factory = + do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return factory->CreateINIParser(aFile, aParser); +} + +nsresult nsBeckyUtils::GetMailboxNameFromINIFile(nsIFile* aFile, + nsCString& aName) { + nsresult rv; + nsCOMPtr parser; + rv = CreateINIParserForFile(aFile, getter_AddRefs(parser)); + NS_ENSURE_SUCCESS(rv, rv); + + return parser->GetString("Account"_ns, "Name"_ns, aName); +} + +nsresult nsBeckyUtils::ConvertToUTF8File(nsIFile* aSourceFile, + nsIFile** _retval) { + nsresult rv; + nsCOMPtr convertedFile; + rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "thunderbird-becky-import", + getter_AddRefs(convertedFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = convertedFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr source; + rv = NS_NewLocalFileInputStream(getter_AddRefs(source), aSourceFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString sourceCharset; + rv = MsgDetectCharsetFromFile(aSourceFile, sourceCharset); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr destination; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(destination), convertedFile); + NS_ENSURE_SUCCESS(rv, rv); + + const uint32_t kBlock = 8192; + + nsCOMPtr convertedInput = + do_CreateInstance("@mozilla.org/intl/converter-input-stream;1"); + convertedInput->Init(source, sourceCharset.get(), kBlock, 0x0000); + + nsCOMPtr convertedOutput = + do_CreateInstance("@mozilla.org/intl/converter-output-stream;1"); + convertedOutput->Init(destination, "UTF-8"); + + char16_t* line = (char16_t*)moz_xmalloc(kBlock); + uint32_t readBytes = kBlock; + bool writtenBytes; + while (readBytes == kBlock) { + rv = convertedInput->Read(line, kBlock, &readBytes); + rv = convertedOutput->Write(readBytes, line, &writtenBytes); + } + convertedOutput->Close(); + convertedInput->Close(); + + convertedFile.forget(_retval); + return NS_OK; +} + +nsresult nsBeckyUtils::TranslateFolderName(const nsAString& aFolderName, + nsAString& _retval) { + if (aFolderName.LowerCaseEqualsLiteral("!trash")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestTrashFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("!!!!inbox")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestInboxFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("!!!!outbox")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestSentFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("!!!!unsent")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestUnsentMessagesFolderName); + else + _retval = aFolderName; + + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsBeckyUtils.h b/comm/mailnews/import/src/nsBeckyUtils.h new file mode 100644 index 0000000000..9a0529b5dc --- /dev/null +++ b/comm/mailnews/import/src/nsBeckyUtils.h @@ -0,0 +1,35 @@ +/* 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 _nsBeckyUtils_H__ +#define _nsBeckyUtils_H__ + +#include "nsString.h" +class nsIFile; +class nsILineInputStream; +class nsIINIParser; + +class nsBeckyUtils final { + public: + static nsresult FindUserDirectoryOnWindows7(nsIFile** aLocation); + static nsresult FindUserDirectoryOnWindowsXP(nsIFile** aLocation); + static nsresult FindUserDirectory(nsIFile** aFile); + static nsresult ConvertNativeStringToUTF8(const nsACString& aOriginal, + nsACString& _retval); + static nsresult CreateLineInputStream(nsIFile* aFile, + nsILineInputStream** _retval); + static nsresult GetDefaultMailboxDirectory(nsIFile** _retval); + static nsresult GetFolderListFile(nsIFile* aLocation, nsIFile** _retval); + static nsresult GetDefaultFolderName(nsIFile* aFolderListFile, + nsACString& name); + static nsresult GetDefaultMailboxINIFile(nsIFile** _retval); + static nsresult GetMailboxINIFile(nsIFile* aDirectory, nsIFile** _retval); + static nsresult CreateINIParserForFile(nsIFile* aFile, + nsIINIParser** aParser); + static nsresult GetMailboxNameFromINIFile(nsIFile* aFile, nsCString& aName); + static nsresult ConvertToUTF8File(nsIFile* aSourceFile, nsIFile** _retval); + static nsresult TranslateFolderName(const nsAString& aFolderName, + nsAString& _retval); +}; + +#endif /* _nsBeckyUtils_H__ */ diff --git a/comm/mailnews/import/src/nsEmlxHelperUtils.h b/comm/mailnews/import/src/nsEmlxHelperUtils.h new file mode 100644 index 0000000000..a7e0e69f11 --- /dev/null +++ b/comm/mailnews/import/src/nsEmlxHelperUtils.h @@ -0,0 +1,61 @@ +/* -*- 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 nsEmlxHelperUtils_h___ +#define nsEmlxHelperUtils_h___ + +#include "nscore.h" +#include "nsString.h" + +class nsIOutputStream; +class nsIFile; + +class nsEmlxHelperUtils { + /* All emlx messages have a "flags" number in the metadata. + These are the masks to decode that, found via + http://jwz.livejournal.com/505711.html */ + enum EmlxMetadataMask { + kRead = 1 << 0, // read + // 1 << 1, // deleted + kAnswered = 1 << 2, // answered + // 1 << 3, // encrypted + kFlagged = 1 << 4, // flagged + // 1 << 5, // recent + // 1 << 6, // draft + // 1 << 7, // initial (no longer used) + kForwarded = 1 << 8, // forwarded + // 1 << 9, // redirected + // 3F << 10, // attachment count (6 bits) + // 7F << 16, // priority level (7 bits) + // 1 << 23, // signed + // 1 << 24, // is junk + // 1 << 25, // is not junk + // 1 << 26, // font size delta 7 (3 bits) + // 1 << 29, // junk mail level recorded + // 1 << 30, // highlight text in toc + // 1 << 31 // (unused) + }; + + // This method will scan the raw EMLX message buffer for "dangerous" so-called + // "From-lines" that we need to escape. If it needs to modify any lines, it + // will return a non-NULL aOutBuffer. If aOutBuffer is NULL, no modification + // needed to be made. + static nsresult ConvertToMboxRD(const char* aMessageBufferStart, + const char* aMessageBufferEnd, + nsCString& aOutBuffer); + + // returns an int representing the X-Mozilla-Status flags set (e.g. "read", + // "flagged") converted from EMLX flags. + static nsresult ConvertToMozillaStatusFlags(const char* aXMLBufferStart, + const char* aXMLBufferEnd, + uint32_t* aMozillaStatusFlags); + + public: + // add an .emlx message to the mbox output + static nsresult AddEmlxMessageToStream(nsIFile* aEmlxFile, + nsIOutputStream* aOutoutStream); +}; + +#endif // nsEmlxHelperUtils_h___ diff --git a/comm/mailnews/import/src/nsEmlxHelperUtils.mm b/comm/mailnews/import/src/nsEmlxHelperUtils.mm new file mode 100644 index 0000000000..a0c5aaaee2 --- /dev/null +++ b/comm/mailnews/import/src/nsEmlxHelperUtils.mm @@ -0,0 +1,230 @@ +/* -*- 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/. */ + +#include "nsEmlxHelperUtils.h" +#include "nsIOutputStream.h" +#include "nsNetUtil.h" +#include "nsCOMPtr.h" +#include "nsObjCExceptions.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgLocalFolderHdrs.h" +#include "msgCore.h" +#include "nsTArray.h" +#include "nsAppleMailImport.h" +#include "prprf.h" +#include "nsIFile.h" + +#import + +nsresult nsEmlxHelperUtils::ConvertToMozillaStatusFlags(const char* aXMLBufferStart, + const char* aXMLBufferEnd, + uint32_t* aMozillaStatusFlags) { + // create a NSData wrapper around the buffer, so we can use the Cocoa call below + NSData* metadata = [[[NSData alloc] initWithBytesNoCopy:(void*)aXMLBufferStart + length:(aXMLBufferEnd - aXMLBufferStart) + freeWhenDone:NO] autorelease]; + + // get the XML data as a dictionary + NSPropertyListFormat format; + id plist = [NSPropertyListSerialization propertyListWithData:metadata + options:NSPropertyListImmutable + format:&format + error:NULL]; + + if (!plist) return NS_ERROR_FAILURE; + + // find the ... value and convert to int + const uint32_t emlxMessageFlags = [[(NSDictionary*)plist objectForKey:@"flags"] intValue]; + + if (emlxMessageFlags == 0) return NS_ERROR_FAILURE; + + if (emlxMessageFlags & nsEmlxHelperUtils::kRead) *aMozillaStatusFlags |= nsMsgMessageFlags::Read; + if (emlxMessageFlags & nsEmlxHelperUtils::kForwarded) + *aMozillaStatusFlags |= nsMsgMessageFlags::Forwarded; + if (emlxMessageFlags & nsEmlxHelperUtils::kAnswered) + *aMozillaStatusFlags |= nsMsgMessageFlags::Replied; + if (emlxMessageFlags & nsEmlxHelperUtils::kFlagged) + *aMozillaStatusFlags |= nsMsgMessageFlags::Marked; + + return NS_OK; +} + +nsresult nsEmlxHelperUtils::ConvertToMboxRD(const char* aMessageBufferStart, + const char* aMessageBufferEnd, nsCString& aOutBuffer) { + nsTArray foundFromLines; + + const char* cur = aMessageBufferStart; + while (cur < aMessageBufferEnd) { + const char* foundFromStr = strnstr(cur, "From ", aMessageBufferEnd - cur); + + if (foundFromStr) { + // skip all prepending '>' chars + const char* fromLineStart = foundFromStr; + while (fromLineStart-- >= aMessageBufferStart) { + if (*fromLineStart == '\n' || fromLineStart == aMessageBufferStart) { + if (fromLineStart > aMessageBufferStart) fromLineStart++; + foundFromLines.AppendElement(fromLineStart); + break; + } else if (*fromLineStart != '>') + break; + } + + // advance past the last found From string. + cur = foundFromStr + 5; + + // look for more From lines. + continue; + } + + break; + } + + // go through foundFromLines + if (foundFromLines.Length()) { + const char* chunkStart = aMessageBufferStart; + for (unsigned i = 0; i < foundFromLines.Length(); ++i) { + aOutBuffer.Append(chunkStart, (foundFromLines[i] - chunkStart)); + aOutBuffer.Append(">"_ns); + + chunkStart = foundFromLines[i]; + } + aOutBuffer.Append(chunkStart, (aMessageBufferEnd - chunkStart)); + } + + return NS_OK; +} + +nsresult nsEmlxHelperUtils::AddEmlxMessageToStream(nsIFile* aMessage, nsIOutputStream* aOut) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + // needed to be sure autoreleased objects are released too, which they might not + // in a C++ environment where the main event loop has no autorelease pool (e.g on a XPCOM thread) + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + nsresult rv = NS_ERROR_FAILURE; + + nsAutoCString path; + aMessage->GetNativePath(path); + + NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path.get()]]; + if (!data) { + [pool release]; + return NS_ERROR_FAILURE; + } + + char* startOfMessageData = NULL; + uint32_t actualBytesWritten = 0; + + // The anatomy of an EMLX file: + // + // ------------------------------- + // < A number describing how many bytes ahead there is message data > + // < Message data > + // < XML metadata for this message > + // ------------------------------- + + // read the first line of the emlx file, which is a number of how many bytes ahead the actual + // message data is. + uint64_t numberOfBytesToRead = strtol((char*)[data bytes], &startOfMessageData, 10); + if (numberOfBytesToRead <= 0 || !startOfMessageData) { + [pool release]; + return NS_ERROR_FAILURE; + } + + // skip whitespace + while (*startOfMessageData == ' ' || *startOfMessageData == '\n' || *startOfMessageData == '\r' || + *startOfMessageData == '\t') + ++startOfMessageData; + + constexpr auto kBogusFromLine = "From \n"_ns; + constexpr auto kEndOfMessage = "\n\n"_ns; + + // write the bogus "From " line which is a magic separator in the mbox format + rv = aOut->Write(kBogusFromLine.get(), kBogusFromLine.Length(), &actualBytesWritten); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // now read the XML metadata, so we can extract info like which flags (read? replied? flagged? + // etc) this message has. + const char* startOfXMLMetadata = startOfMessageData + numberOfBytesToRead; + const char* endOfXMLMetadata = (char*)[data bytes] + [data length]; + + uint32_t x_mozilla_flags = 0; + ConvertToMozillaStatusFlags(startOfXMLMetadata, endOfXMLMetadata, &x_mozilla_flags); + + // write the X-Mozilla-Status header according to which flags we've gathered above. + uint32_t dummyRv; + nsAutoCString buf(PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, x_mozilla_flags)); + NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status header"); + if (buf.IsEmpty()) { + [pool release]; + return rv; + } + + rv = aOut->Write(buf.get(), buf.Length(), &dummyRv); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // write out X-Mozilla-Keywords header as well to reserve some space for it + // in the mbox file. + rv = aOut->Write(X_MOZILLA_KEYWORDS, X_MOZILLA_KEYWORDS_LEN, &dummyRv); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // write out empty X-Mozilla_status2 header + buf.Adopt(PR_smprintf(X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, 0)); + NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status2 header"); + if (buf.IsEmpty()) { + [pool release]; + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = aOut->Write(buf.get(), buf.Length(), &dummyRv); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // do any conversion needed for the mbox data to be valid mboxrd. + nsCString convertedData; + rv = ConvertToMboxRD(startOfMessageData, (startOfMessageData + numberOfBytesToRead), + convertedData); + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + // write the actual message data. + if (convertedData.IsEmpty()) + rv = aOut->Write(startOfMessageData, (uint32_t)numberOfBytesToRead, &actualBytesWritten); + else { + IMPORT_LOG1("Escaped From-lines in %s!", path.get()); + rv = aOut->Write(convertedData.get(), convertedData.Length(), &actualBytesWritten); + } + + if (NS_FAILED(rv)) { + [pool release]; + return rv; + } + + NS_ASSERTION(actualBytesWritten == + (convertedData.IsEmpty() ? numberOfBytesToRead : convertedData.Length()), + "Didn't write as many bytes as expected for .emlx file?"); + + // add newlines to denote the end of this message in the mbox + rv = aOut->Write(kEndOfMessage.get(), kEndOfMessage.Length(), &actualBytesWritten); + + [pool release]; + + return rv; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} diff --git a/comm/mailnews/import/src/nsImportABDescriptor.cpp b/comm/mailnews/import/src/nsImportABDescriptor.cpp new file mode 100644 index 0000000000..983f1c6ffb --- /dev/null +++ b/comm/mailnews/import/src/nsImportABDescriptor.cpp @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsImportABDescriptor.h" + +//////////////////////////////////////////////////////////////////////// + +nsresult nsImportABDescriptor::Create(REFNSIID aIID, void** aResult) { + RefPtr it = new nsImportABDescriptor(); + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsImportABDescriptor, nsIImportABDescriptor) + +nsImportABDescriptor::nsImportABDescriptor() + : mId(0), mRef(0), mSize(0), mImport(true) {} diff --git a/comm/mailnews/import/src/nsImportABDescriptor.h b/comm/mailnews/import/src/nsImportABDescriptor.h new file mode 100644 index 0000000000..681ed8e83f --- /dev/null +++ b/comm/mailnews/import/src/nsImportABDescriptor.h @@ -0,0 +1,100 @@ +/* -*- 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 nsImportABDescriptor_h___ +#define nsImportABDescriptor_h___ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsString.h" +#include "nsIImportABDescriptor.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////// + +class nsImportABDescriptor : public nsIImportABDescriptor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD GetIdentifier(uint32_t* pIdentifier) override { + *pIdentifier = mId; + return NS_OK; + } + NS_IMETHOD SetIdentifier(uint32_t ident) override { + mId = ident; + return NS_OK; + } + + NS_IMETHOD GetRef(uint32_t* pRef) override { + *pRef = mRef; + return NS_OK; + } + NS_IMETHOD SetRef(uint32_t ref) override { + mRef = ref; + return NS_OK; + } + + /* attribute unsigned long size; */ + NS_IMETHOD GetSize(uint32_t* pSize) override { + *pSize = mSize; + return NS_OK; + } + NS_IMETHOD SetSize(uint32_t theSize) override { + mSize = theSize; + return NS_OK; + } + + /* attribute AString displayName; */ + NS_IMETHOD GetPreferredName(nsAString& aName) override { + aName = mDisplayName; + return NS_OK; + } + NS_IMETHOD SetPreferredName(const nsAString& aName) override { + mDisplayName = aName; + return NS_OK; + } + + /* readonly attribute nsIFile fileSpec; */ + NS_IMETHOD GetAbFile(nsIFile** aFile) override { + if (!mFile) return NS_ERROR_NULL_POINTER; + + return mFile->Clone(aFile); + } + + NS_IMETHOD SetAbFile(nsIFile* aFile) override { + if (!aFile) { + mFile = nullptr; + return NS_OK; + } + + return aFile->Clone(getter_AddRefs(mFile)); + } + + /* attribute boolean import; */ + NS_IMETHOD GetImport(bool* pImport) override { + *pImport = mImport; + return NS_OK; + } + NS_IMETHOD SetImport(bool doImport) override { + mImport = doImport; + return NS_OK; + } + + nsImportABDescriptor(); + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + virtual ~nsImportABDescriptor() {} + uint32_t mId; // used by creator of the structure + uint32_t mRef; // depth in the hierarchy + nsString mDisplayName; // name of this mailbox + nsCOMPtr mFile; // source file (if applicable) + uint32_t mSize; // size + bool mImport; // import it or not? +}; + +#endif diff --git a/comm/mailnews/import/src/nsImportAddressBooks.cpp b/comm/mailnews/import/src/nsImportAddressBooks.cpp new file mode 100644 index 0000000000..b39e7c68ef --- /dev/null +++ b/comm/mailnews/import/src/nsImportAddressBooks.cpp @@ -0,0 +1,571 @@ +/* -*- 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/. */ + +#include "nsImportAddressBooks.h" + +#include "plstr.h" +#include "nsIImportService.h" +#include "nsISupportsPrimitives.h" +#include "nsIImportABDescriptor.h" +#include "nsIAbManager.h" +#include "nsImportStringBundle.h" +#include "nsTextFormatter.h" +#include "msgCore.h" +#include "ImportDebug.h" + +nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric) { + NS_ASSERTION(aImportGeneric != nullptr, "null ptr"); + if (!aImportGeneric) return NS_ERROR_NULL_POINTER; + + RefPtr pGen = new nsImportGenericAddressBooks(); + return pGen->QueryInterface(NS_GET_IID(nsIImportGeneric), + (void**)aImportGeneric); +} + +nsImportGenericAddressBooks::nsImportGenericAddressBooks() { + m_totalSize = 0; + m_doImport = false; + m_pThreadData = nullptr; + + m_autoFind = false; + m_description = nullptr; + m_gotLocation = false; + m_found = false; + m_userVerify = false; + + nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL, + getter_AddRefs(m_stringBundle)); +} + +nsImportGenericAddressBooks::~nsImportGenericAddressBooks() { + if (m_description) free(m_description); +} + +NS_IMPL_ISUPPORTS(nsImportGenericAddressBooks, nsIImportGeneric) + +NS_IMETHODIMP nsImportGenericAddressBooks::GetData(const char* dataId, + nsISupports** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv; + *_retval = nullptr; + if (!PL_strcasecmp(dataId, "addressInterface")) { + NS_IF_ADDREF(*_retval = m_pInterface); + } + + if (!PL_strcasecmp(dataId, "addressLocation")) { + if (!m_pLocation) GetDefaultLocation(); + NS_IF_ADDREF(*_retval = m_pLocation); + } + + if (!PL_strcasecmp(dataId, "addressDestination")) { + if (!m_pDestinationUri.IsEmpty()) { + nsCOMPtr abString = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + abString->SetData(m_pDestinationUri); + abString.forget(_retval); + } + } + + if (!PL_strcasecmp(dataId, "fieldMap")) { + if (m_pFieldMap) { + NS_ADDREF(*_retval = m_pFieldMap); + } else { + if (m_pInterface && m_pLocation) { + bool needsIt = false; + m_pInterface->GetNeedsFieldMap(m_pLocation, &needsIt); + if (needsIt) { + GetDefaultFieldMap(); + if (m_pFieldMap) { + NS_ADDREF(*_retval = m_pFieldMap); + } + } + } + } + } + + if (!PL_strncasecmp(dataId, "sampleData-", 11)) { + // extra the record number + const char* pNum = dataId + 11; + int32_t rNum = 0; + while (*pNum) { + rNum *= 10; + rNum += (*pNum - '0'); + pNum++; + } + IMPORT_LOG1("Requesting sample data #: %ld\n", (long)rNum); + if (m_pInterface) { + nsCOMPtr data = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + char16_t* pData = nullptr; + bool found = false; + rv = m_pInterface->GetSampleData(rNum, &found, &pData); + if (NS_FAILED(rv)) return rv; + if (found) { + data->SetData(nsDependentString(pData)); + data.forget(_retval); + } + free(pData); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::SetData(const char* dataId, + nsISupports* item) { + NS_ASSERTION(dataId != nullptr, "null ptr"); + if (!dataId) return NS_ERROR_NULL_POINTER; + + if (!PL_strcasecmp(dataId, "addressInterface")) { + m_pInterface = nullptr; + if (item) m_pInterface = do_QueryInterface(item); + } + + if (!PL_strcasecmp(dataId, "addressLocation")) { + m_pLocation = nullptr; + + if (item) { + nsresult rv; + m_pLocation = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (m_pInterface) m_pInterface->SetSampleLocation(m_pLocation); + } + + if (!PL_strcasecmp(dataId, "addressDestination")) { + if (item) { + nsCOMPtr abString = do_QueryInterface(item); + if (abString) { + abString->GetData(m_pDestinationUri); + } + } + } + + if (!PL_strcasecmp(dataId, "fieldMap")) { + m_pFieldMap = nullptr; + if (item) m_pFieldMap = do_QueryInterface(item); + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::GetStatus(const char* statusKind, + int32_t* _retval) { + NS_ASSERTION(statusKind != nullptr, "null ptr"); + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!statusKind || !_retval) return NS_ERROR_NULL_POINTER; + + *_retval = 0; + + if (!PL_strcasecmp(statusKind, "isInstalled")) { + GetDefaultLocation(); + *_retval = (int32_t)m_found; + } + + if (!PL_strcasecmp(statusKind, "canUserSetLocation")) { + GetDefaultLocation(); + *_retval = (int32_t)m_userVerify; + } + + if (!PL_strcasecmp(statusKind, "autoFind")) { + GetDefaultLocation(); + *_retval = (int32_t)m_autoFind; + } + + if (!PL_strcasecmp(statusKind, "supportsMultiple")) { + bool multi = false; + if (m_pInterface) m_pInterface->GetSupportsMultiple(&multi); + *_retval = (int32_t)multi; + } + + if (!PL_strcasecmp(statusKind, "needsFieldMap")) { + bool needs = false; + if (m_pInterface && m_pLocation) + m_pInterface->GetNeedsFieldMap(m_pLocation, &needs); + *_retval = (int32_t)needs; + } + + return NS_OK; +} + +void nsImportGenericAddressBooks::GetDefaultLocation(void) { + if (!m_pInterface) return; + + if ((m_pLocation && m_gotLocation) || m_autoFind) return; + + if (m_description) free(m_description); + m_description = nullptr; + m_pInterface->GetAutoFind(&m_description, &m_autoFind); + m_gotLocation = true; + if (m_autoFind) { + m_found = true; + m_userVerify = false; + return; + } + + nsCOMPtr pLoc; + m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found, + &m_userVerify); + if (!m_pLocation) m_pLocation = pLoc; +} + +void nsImportGenericAddressBooks::GetDefaultBooks(void) { + if (!m_pInterface) return; + + if (!m_pLocation && !m_autoFind) return; + + nsresult rv = m_pInterface->FindAddressBooks(m_pLocation, m_Books); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error: FindAddressBooks failed\n"); + } +} + +void nsImportGenericAddressBooks::GetDefaultFieldMap(void) { + if (!m_pInterface || !m_pLocation) return; + + nsresult rv; + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Unable to get nsIImportService.\n"); + return; + } + + rv = impSvc->CreateNewFieldMap(getter_AddRefs(m_pFieldMap)); + if (NS_FAILED(rv)) return; + + int32_t sz = 0; + rv = m_pFieldMap->GetNumMozFields(&sz); + if (NS_SUCCEEDED(rv)) rv = m_pFieldMap->DefaultFieldMap(sz); + if (NS_SUCCEEDED(rv)) rv = m_pInterface->InitFieldMap(m_pFieldMap); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error: Unable to initialize field map\n"); + m_pFieldMap = nullptr; + } +} + +NS_IMETHODIMP nsImportGenericAddressBooks::WantsProgress(bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + NS_ENSURE_ARG_POINTER(_retval); + + GetDefaultLocation(); + GetDefaultBooks(); + + bool result = false; + uint32_t totalSize = 0; + + for (nsIImportABDescriptor* book : m_Books) { + bool doImport = false; + nsresult rv = book->GetImport(&doImport); + if (NS_SUCCEEDED(rv) && doImport) { + uint32_t size = 0; + (void)book->GetSize(&size); + result = true; + totalSize += size; + } + } + m_totalSize = totalSize; + m_doImport = result; + + *_retval = result; + + return NS_OK; +} + +void nsImportGenericAddressBooks::SetLogs(nsString& success, nsString& error, + nsISupportsString* pSuccess, + nsISupportsString* pError) { + nsAutoString str; + if (pSuccess) { + pSuccess->GetData(str); + str.Append(success); + pSuccess->SetData(success); + } + if (pError) { + pError->GetData(str); + str.Append(error); + pError->SetData(error); + } +} + +already_AddRefed GetAddressBookFromUri(const char* pUri) { + if (!pUri) return nullptr; + + nsCOMPtr abManager = do_GetService("@mozilla.org/abmanager;1"); + if (!abManager) return nullptr; + + nsCOMPtr directory; + abManager->GetDirectory(nsDependentCString(pUri), getter_AddRefs(directory)); + if (!directory) return nullptr; + + return directory.forget(); +} + +already_AddRefed GetAddressBook(nsString name, bool makeNew) { + if (!makeNew) { + // FIXME: How do I get the list of address books and look for a + // specific name. Major bogosity! + // For now, assume we didn't find anything with that name + } + + IMPORT_LOG0("In GetAddressBook\n"); + + nsresult rv; + nsCOMPtr directory; + nsCOMPtr abManager = + do_GetService("@mozilla.org/abmanager;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsAutoCString dirPrefId; + rv = abManager->NewAddressBook(name, EmptyCString(), + nsIAbManager::JS_DIRECTORY_TYPE, + EmptyCString(), dirPrefId); + if (NS_SUCCEEDED(rv)) { + rv = abManager->GetDirectoryFromId(dirPrefId, getter_AddRefs(directory)); + } + } + + return directory.forget(); +} + +NS_IMETHODIMP nsImportGenericAddressBooks::BeginImport( + nsISupportsString* successLog, nsISupportsString* errorLog, bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + nsString success; + nsString error; + + if (!m_doImport) { + *_retval = true; + nsImportStringBundle::GetStringByID(IMPORT_NO_ADDRBOOKS, m_stringBundle, + success); + SetLogs(success, error, successLog, errorLog); + return NS_OK; + } + + if (!m_pInterface) { + nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + bool needsFieldMap = false; + + if (NS_FAILED(m_pInterface->GetNeedsFieldMap(m_pLocation, &needsFieldMap)) || + (needsFieldMap && !m_pFieldMap)) { + nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + m_pSuccessLog = successLog; + m_pErrorLog = errorLog; + + // create the info need to drive address book import. We're + // not going to create a new thread for this since address books + // don't tend to be large, and import is rare. + m_pThreadData = new AddressThreadData(); + m_pThreadData->books = m_Books.Clone(); + m_pThreadData->addressImport = m_pInterface; + m_pThreadData->fieldMap = m_pFieldMap; + m_pThreadData->errorLog = m_pErrorLog; + m_pThreadData->successLog = m_pSuccessLog; + m_pThreadData->pDestinationUri = m_pDestinationUri; + + // Create/obtain any address books that we need here, so that we don't need + // to do so inside the import thread which would just proxy the create + // operations back to the main thread anyway. + nsCOMPtr db; + if (!m_pDestinationUri.IsEmpty()) { + db = GetAddressBookFromUri(m_pDestinationUri.get()); + } + for (nsIImportABDescriptor* book : m_Books) { + if (!db) { + nsString name; + book->GetPreferredName(name); + db = GetAddressBook(name, true); + } + m_DBs.AppendObject(db); + } + m_pThreadData->dBs = &m_DBs; + + m_pThreadData->stringBundle = m_stringBundle; + + nsresult rv; + m_pThreadData->ldifService = + do_GetService("@mozilla.org/addressbook/abldifservice;1", &rv); + + ImportAddressThread(m_pThreadData); + delete m_pThreadData; + m_pThreadData = nullptr; + *_retval = true; + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::ContinueImport(bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + *_retval = true; + if (m_pThreadData) { + if (m_pThreadData->fatalError) *_retval = false; + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::GetProgress(int32_t* _retval) { + // This returns the progress from the the currently + // running import mail or import address book thread. + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + if (!m_pThreadData || !(m_pThreadData->threadAlive)) { + *_retval = 100; + return NS_OK; + } + + uint32_t sz = 0; + if (m_pThreadData->currentSize && m_pInterface) { + if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) sz = 0; + } + + if (m_totalSize) + *_retval = ((m_pThreadData->currentTotal + sz) * 100) / m_totalSize; + else + *_retval = 0; + + // never return less than 5 so it looks like we are doing something! + if (*_retval < 5) *_retval = 5; + + // as long as the thread is alive don't return completely + // done. + if (*_retval > 99) *_retval = 99; + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericAddressBooks::CancelImport(void) { + if (m_pThreadData) { + m_pThreadData->abort = true; + m_pThreadData = nullptr; + } + + return NS_OK; +} + +AddressThreadData::AddressThreadData() { + fatalError = false; + driverAlive = true; + threadAlive = true; + abort = false; + currentTotal = 0; + currentSize = 0; +} + +AddressThreadData::~AddressThreadData() {} + +void nsImportGenericAddressBooks::ReportError(const char16_t* pName, + nsString* pStream, + nsIStringBundle* aBundle) { + if (!pStream) return; + // load the error string + char16_t* pFmt = + nsImportStringBundle::GetStringByID(IMPORT_ERROR_GETABOOK, aBundle); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, pName); + pStream->Append(pText); + free(pFmt); + pStream->AppendLiteral(MSG_LINEBREAK); +} + +static void ImportAddressThread(void* stuff) { + IMPORT_LOG0("In Begin ImportAddressThread\n"); + + AddressThreadData* pData = (AddressThreadData*)stuff; + + nsString success; + nsString error; + + uint32_t count = pData->books.Length(); + for (uint32_t i = 0; (i < count) && !(pData->abort); i++) { + nsIImportABDescriptor* book = pData->books[i]; + + uint32_t size = 0; + bool doImport = false; + nsresult rv = book->GetImport(&doImport); + if (NS_SUCCEEDED(rv) && doImport) rv = book->GetSize(&size); + + if (NS_SUCCEEDED(rv) && size && doImport) { + nsString name; + book->GetPreferredName(name); + + nsCOMPtr db = pData->dBs->ObjectAt(i); + + bool fatalError = false; + pData->currentSize = size; + if (db) { + char16_t* pSuccess = nullptr; + char16_t* pError = nullptr; + + /* + if (pData->fieldMap) { + int32_t sz = 0; + int32_t mapIndex; + bool active; + pData->fieldMap->GetMapSize(&sz); + IMPORT_LOG1("**** Field Map Size: %d\n", (int) sz); + for (int32_t i = 0; i < sz; i++) { + pData->fieldMap->GetFieldMap(i, &mapIndex); + pData->fieldMap->GetFieldActive(i, &active); + IMPORT_LOG3("Field map #%d: index=%d, active=%d\n", (int) i, (int) + mapIndex, (int) active); + } + } + */ + + rv = pData->addressImport->ImportAddressBook( + book, db, pData->fieldMap, pData->ldifService, &pError, &pSuccess, + &fatalError); + if (NS_SUCCEEDED(rv) && pSuccess) { + success.Append(pSuccess); + free(pSuccess); + } + if (pError) { + error.Append(pError); + free(pError); + } + } else { + nsImportGenericAddressBooks::ReportError(name.get(), &error, + pData->stringBundle); + } + + pData->currentSize = 0; + pData->currentTotal += size; + + if (fatalError) { + pData->fatalError = true; + break; + } + } + } + + nsImportGenericAddressBooks::SetLogs(success, error, pData->successLog, + pData->errorLog); + + if (pData->abort || pData->fatalError) { + // FIXME: do what is necessary to get rid of what has been imported so far. + // Nothing if we went into an existing address book! Otherwise, delete + // the ones we created? + } +} diff --git a/comm/mailnews/import/src/nsImportAddressBooks.h b/comm/mailnews/import/src/nsImportAddressBooks.h new file mode 100644 index 0000000000..8d0edd2c67 --- /dev/null +++ b/comm/mailnews/import/src/nsImportAddressBooks.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#include "nsCOMPtr.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportGeneric.h" +#include "nsIImportFieldMap.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsIAbDirectory.h" +#include "nsIAbLDIFService.h" +#include "nsIStringBundle.h" +#include "nsIArray.h" +#include "nsCOMArray.h" + +static void ImportAddressThread(void* stuff); + +class AddressThreadData; + +class nsImportGenericAddressBooks : public nsIImportGeneric { + public: + nsImportGenericAddressBooks(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTGENERIC + + private: + virtual ~nsImportGenericAddressBooks(); + void GetDefaultLocation(void); + void GetDefaultBooks(void); + void GetDefaultFieldMap(void); + + public: + static void SetLogs(nsString& success, nsString& error, + nsISupportsString* pSuccess, nsISupportsString* pError); + static void ReportError(const char16_t* pName, nsString* pStream, + nsIStringBundle* aBundle); + + private: + nsCOMPtr m_pInterface; + nsTArray> m_Books; + nsCOMArray m_DBs; + nsCOMPtr m_pLocation; + nsCOMPtr m_pFieldMap; + bool m_autoFind; + char16_t* m_description; + bool m_gotLocation; + bool m_found; + bool m_userVerify; + nsCOMPtr m_pSuccessLog; + nsCOMPtr m_pErrorLog; + uint32_t m_totalSize; + bool m_doImport; + AddressThreadData* m_pThreadData; + nsCString m_pDestinationUri; + nsCOMPtr m_stringBundle; +}; + +class AddressThreadData { + public: + bool driverAlive; + bool threadAlive; + bool abort; + bool fatalError; + uint32_t currentTotal; + uint32_t currentSize; + nsTArray> books; + nsCOMArray* dBs; + nsCOMPtr ldifService; + nsCOMPtr addressImport; + nsCOMPtr fieldMap; + nsCOMPtr successLog; + nsCOMPtr errorLog; + nsCString pDestinationUri; + nsCOMPtr stringBundle; + + AddressThreadData(); + ~AddressThreadData(); +}; diff --git a/comm/mailnews/import/src/nsImportEmbeddedImageData.cpp b/comm/mailnews/import/src/nsImportEmbeddedImageData.cpp new file mode 100644 index 0000000000..d49bccdbfc --- /dev/null +++ b/comm/mailnews/import/src/nsImportEmbeddedImageData.cpp @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +#include "nsImportEmbeddedImageData.h" + +NS_IMPL_ISUPPORTS(nsImportEmbeddedImageData, nsIMsgEmbeddedImageData) + +nsImportEmbeddedImageData::nsImportEmbeddedImageData() {} + +nsImportEmbeddedImageData::nsImportEmbeddedImageData(nsIURI* aUri, + const nsACString& aCid) + : m_uri(aUri), m_cid(aCid) {} + +nsImportEmbeddedImageData::nsImportEmbeddedImageData(nsIURI* aUri, + const nsACString& aCid, + const nsACString& aName) + : m_uri(aUri), m_cid(aCid), m_name(aName) {} + +nsImportEmbeddedImageData::~nsImportEmbeddedImageData() {} + +NS_IMETHODIMP nsImportEmbeddedImageData::GetUri(nsIURI** aUri) { + NS_ENSURE_ARG_POINTER(aUri); + NS_IF_ADDREF(*aUri = m_uri); + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::SetUri(nsIURI* aUri) { + m_uri = aUri; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::GetCid(nsACString& aCid) { + aCid = m_cid; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::SetCid(const nsACString& aCid) { + m_cid = aCid; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::GetName(nsACString& aName) { + aName = m_name; + return NS_OK; +} + +NS_IMETHODIMP nsImportEmbeddedImageData::SetName(const nsACString& aName) { + m_name = aName; + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsImportEmbeddedImageData.h b/comm/mailnews/import/src/nsImportEmbeddedImageData.h new file mode 100644 index 0000000000..0eaa08b113 --- /dev/null +++ b/comm/mailnews/import/src/nsImportEmbeddedImageData.h @@ -0,0 +1,31 @@ +/* -*- 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 __IMPORTEMBEDDEDIMAGETDATA_H__ +#define __IMPORTEMBEDDEDIMAGETDATA_H__ + +#include "nsIMsgSend.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" + +class nsImportEmbeddedImageData final : public nsIMsgEmbeddedImageData { + public: + nsImportEmbeddedImageData(nsIURI* aUri, const nsACString& aCID); + nsImportEmbeddedImageData(nsIURI* aUri, const nsACString& aCID, + const nsACString& aName); + nsImportEmbeddedImageData(); + NS_DECL_NSIMSGEMBEDDEDIMAGEDATA + NS_DECL_ISUPPORTS + + nsCOMPtr m_uri; + nsCString m_cid; + nsCString m_name; + + private: + ~nsImportEmbeddedImageData(); +}; + +#endif diff --git a/comm/mailnews/import/src/nsImportEncodeScan.cpp b/comm/mailnews/import/src/nsImportEncodeScan.cpp new file mode 100644 index 0000000000..64607688ef --- /dev/null +++ b/comm/mailnews/import/src/nsImportEncodeScan.cpp @@ -0,0 +1,334 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsImportEncodeScan.h" +#include "nsNetUtil.h" + +#define kBeginAppleSingle 0 +#define kBeginDataFork 1 +#define kBeginResourceFork 2 +#define kAddEntries 3 +#define kScanningDataFork 4 +#define kScanningRsrcFork 5 +#define kDoneWithFile 6 + +uint32_t gAppleSingleHeader[6] = {0x00051600, 0x00020000, 0, 0, 0, 0}; +#define kAppleSingleHeaderSize (6 * sizeof(uint32_t)) + +#ifdef _MAC_IMPORT_CODE +# include "MoreDesktopMgr.h" + +CInfoPBRec gCatInfoPB; +U32 g2000Secs = 0; +long gGMTDelta = 0; + +long GetGmtDelta(void); +U32 Get2000Secs(void); + +long GetGmtDelta(void) { + MachineLocation myLocation; + ReadLocation(&myLocation); + long myDelta = BitAnd(myLocation.u.gmtDelta, 0x00FFFFFF); + if (BitTst(&myDelta, 23)) myDelta = BitOr(myDelta, 0xFF000000); + return myDelta; +} + +U32 Get2000Secs(void) { + DateTimeRec dr; + dr.year = 2000; + dr.month = 1; + dr.day = 1; + dr.hour = 0; + dr.minute = 0; + dr.second = 0; + dr.dayOfWeek = 0; + U32 result; + DateToSeconds(&dr, &result); + return result; +} +#endif + +nsImportEncodeScan::nsImportEncodeScan() { + m_isAppleSingle = false; + m_encodeScanState = 0; + m_resourceForkSize = 0; + m_dataForkSize = 0; +} + +nsImportEncodeScan::~nsImportEncodeScan() {} + +bool nsImportEncodeScan::InitEncodeScan(bool appleSingleEncode, + nsIFile* fileLoc, const char* pName, + uint8_t* pBuf, uint32_t sz) { + CleanUpEncodeScan(); + m_isAppleSingle = appleSingleEncode; + m_encodeScanState = kBeginAppleSingle; + m_pInputFile = fileLoc; + m_useFileName = pName; + m_pBuf = pBuf; + m_bufSz = sz; + if (!m_isAppleSingle) { + if (!m_inputStream) { + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream), + m_pInputFile); + NS_ENSURE_SUCCESS(rv, false); + } + + InitScan(m_inputStream, pBuf, sz); + } else { +#ifdef _MAC_IMPORT_CODE + // Fill in the file sizes + m_resourceForkSize = fileLoc.GetMacFileSize(UFileLocation::eResourceFork); + m_dataForkSize = fileLoc.GetMacFileSize(UFileLocation::eDataFork); +#endif + } + + return true; +} + +void nsImportEncodeScan::CleanUpEncodeScan(void) { + m_pInputStream->Close(); + m_pInputStream = nullptr; + m_pInputFile = nullptr; +} + +// 26 + 12 per entry + +void nsImportEncodeScan::FillInEntries(int numEntries) { +#ifdef _MAC_IMPORT_CODE + int len = m_useFileName.GetLength(); + if (len < 32) len = 32; + long entry[3]; + long fileOffset = 26 + (12 * numEntries); + entry[0] = 3; + entry[1] = fileOffset; + entry[2] = m_useFileName.GetLength(); + fileOffset += len; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + Str255 comment; + comment[0] = 0; + OSErr err = FSpDTGetComment(m_inputFileLoc, comment); + if (comment[0] > 200) comment[0] = 200; + entry[0] = 4; + entry[1] = fileOffset; + entry[2] = comment[0]; + fileOffset += 200; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + entry[0] = 8; + entry[1] = fileOffset; + entry[2] = 16; + fileOffset += 16; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + entry[0] = 9; + entry[1] = fileOffset; + entry[2] = 32; + fileOffset += 32; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + entry[0] = 10; + entry[1] = fileOffset; + entry[2] = 4; + fileOffset += 4; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + + if (m_resourceForkSize) { + entry[0] = 2; + entry[1] = fileOffset; + entry[2] = m_resourceForkSize; + fileOffset += m_resourceForkSize; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + } + + if (m_dataForkSize) { + entry[0] = 1; + entry[1] = fileOffset; + entry[2] = m_dataForkSize; + fileOffset += m_dataForkSize; + MemCpy(m_pBuf + m_bytesInBuf, entry, 12); + m_bytesInBuf += 12; + } + +#endif +} + +bool nsImportEncodeScan::AddEntries(void) { +#ifdef _MAC_IMPORT_CODE + if (!g2000Secs) { + g2000Secs = Get2000Secs(); + gGMTDelta = GetGmtDelta(); + } + MemCpy(m_pBuf + m_bytesInBuf, (PC_S8)m_useFileName, + m_useFileName.GetLength()); + m_bytesInBuf += m_useFileName.GetLength(); + if (m_useFileName.GetLength() < 32) { + int len = m_useFileName.GetLength(); + while (len < 32) { + *((P_S8)m_pBuf + m_bytesInBuf) = 0; + m_bytesInBuf++; + len++; + } + } + + Str255 comment; + comment[0] = 0; + OSErr err = FSpDTGetComment(m_inputFileLoc, comment); + comment[0] = 200; + MemCpy(m_pBuf + m_bytesInBuf, &(comment[1]), comment[0]); + m_bytesInBuf += comment[0]; + + long dates[4]; + dates[0] = gCatInfoPB.hFileInfo.ioFlCrDat; + dates[1] = gCatInfoPB.hFileInfo.ioFlMdDat; + dates[2] = gCatInfoPB.hFileInfo.ioFlBkDat; + dates[3] = 0x80000000; + for (short i = 0; i < 3; i++) { + dates[i] -= g2000Secs; + dates[i] += gGMTDelta; + } + MemCpy(m_pBuf + m_bytesInBuf, dates, 16); + m_bytesInBuf += 16; + + FInfo fInfo = gCatInfoPB.hFileInfo.ioFlFndrInfo; + FXInfo fxInfo = gCatInfoPB.hFileInfo.ioFlXFndrInfo; + fInfo.fdFlags = 0; + fInfo.fdLocation.h = 0; + fInfo.fdLocation.v = 0; + fInfo.fdFldr = 0; + MemSet(&fxInfo, 0, sizeof(fxInfo)); + MemCpy(m_pBuf + m_bytesInBuf, &fInfo, 16); + m_bytesInBuf += 16; + MemCpy(m_pBuf + m_bytesInBuf, &fxInfo, 16); + m_bytesInBuf += 16; + + dates[0] = 0; + if ((gCatInfoPB.hFileInfo.ioFlAttrib & 1) != 0) dates[0] |= 1; + MemCpy(m_pBuf + m_bytesInBuf, dates, 4); + m_bytesInBuf += 4; + +#endif + return true; +} + +bool nsImportEncodeScan::Scan(bool* pDone) { + nsresult rv; + + *pDone = false; + if (m_isAppleSingle) { + // Stuff the buffer with things needed to encode the file... + // then just allow UScanFile to handle each fork, but be careful + // when handling eof. + switch (m_encodeScanState) { + case kBeginAppleSingle: { +#ifdef _MAC_IMPORT_CODE + OSErr err = GetCatInfoNoName( + m_inputFileLoc.GetVRefNum(), m_inputFileLoc.GetParID(), + m_inputFileLoc.GetFileNamePtr(), &gCatInfoPB); + if (err != noErr) return FALSE; +#endif + m_eof = false; + m_pos = 0; + memcpy(m_pBuf, gAppleSingleHeader, kAppleSingleHeaderSize); + m_bytesInBuf = kAppleSingleHeaderSize; + int numEntries = 5; + if (m_dataForkSize) numEntries++; + if (m_resourceForkSize) numEntries++; + memcpy(m_pBuf + m_bytesInBuf, &numEntries, sizeof(numEntries)); + m_bytesInBuf += sizeof(numEntries); + FillInEntries(numEntries); + m_encodeScanState = kAddEntries; + return ScanBuffer(pDone); + } break; + + case kBeginDataFork: { + if (!m_dataForkSize) { + m_encodeScanState = kDoneWithFile; + return true; + } + // Initialize the scan of the data fork... + if (!m_inputStream) { + rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream), + m_pInputFile); + NS_ENSURE_SUCCESS(rv, false); + } + m_encodeScanState = kScanningDataFork; + return true; + } break; + + case kScanningDataFork: { + bool result = FillBufferFromFile(); + if (!result) return false; + if (m_eof) { + m_eof = false; + result = ScanBuffer(pDone); + if (!result) return false; + m_inputStream->Close(); + m_inputStream = nullptr; + m_encodeScanState = kDoneWithFile; + return true; + } else + return ScanBuffer(pDone); + } break; + + case kScanningRsrcFork: { + bool result = FillBufferFromFile(); + if (!result) return false; + if (m_eof) { + m_eof = false; + result = ScanBuffer(pDone); + if (!result) return false; + m_inputStream->Close(); + m_inputStream = nullptr; + m_encodeScanState = kBeginDataFork; + return true; + } else + return ScanBuffer(pDone); + } break; + + case kBeginResourceFork: { + if (!m_resourceForkSize) { + m_encodeScanState = kBeginDataFork; + return true; + } + /* + // FIXME: Open the resource fork on the Mac!!! + m_fH = UFile::OpenRsrcFileRead(m_inputFileLoc); + if (m_fH == TR_FILE_ERROR) + return FALSE; + */ + m_encodeScanState = kScanningRsrcFork; + return true; + } break; + + case kAddEntries: { + ShiftBuffer(); + if (!AddEntries()) return false; + m_encodeScanState = kBeginResourceFork; + return ScanBuffer(pDone); + } break; + + case kDoneWithFile: { + ShiftBuffer(); + m_eof = true; + if (!ScanBuffer(pDone)) return false; + *pDone = true; + return true; + } break; + } + + } else + return nsImportScanFile::Scan(pDone); + + return false; +} diff --git a/comm/mailnews/import/src/nsImportEncodeScan.h b/comm/mailnews/import/src/nsImportEncodeScan.h new file mode 100644 index 0000000000..4c9b784fc6 --- /dev/null +++ b/comm/mailnews/import/src/nsImportEncodeScan.h @@ -0,0 +1,39 @@ +/* -*- 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 nsImportEncodeScan_h___ +#define nsImportEncodeScan_h___ + +#include "mozilla/Attributes.h" +#include "nsIFile.h" +#include "nsImportScanFile.h" +#include "nsString.h" + +class nsImportEncodeScan : public nsImportScanFile { + public: + nsImportEncodeScan(); + ~nsImportEncodeScan(); + + bool InitEncodeScan(bool appleSingleEncode, nsIFile* pFile, const char* pName, + uint8_t* pBuf, uint32_t sz); + void CleanUpEncodeScan(void); + + virtual bool Scan(bool* pDone) override; + + protected: + void FillInEntries(int numEntries); + bool AddEntries(void); + + protected: + bool m_isAppleSingle; + nsCOMPtr m_pInputFile; + nsCOMPtr m_inputStream; + int m_encodeScanState; + long m_resourceForkSize; + long m_dataForkSize; + nsCString m_useFileName; +}; + +#endif /* nsImportEncodeScan_h__ */ diff --git a/comm/mailnews/import/src/nsImportFieldMap.cpp b/comm/mailnews/import/src/nsImportFieldMap.cpp new file mode 100644 index 0000000000..74e15f11fe --- /dev/null +++ b/comm/mailnews/import/src/nsImportFieldMap.cpp @@ -0,0 +1,325 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsIAbCard.h" +#include "nsIStringBundle.h" +#include "nsImportFieldMap.h" +#include "nsImportStringBundle.h" +#include "nsCRTGlue.h" +#include "ImportDebug.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////// + +nsresult nsImportFieldMap::Create(nsIStringBundle* aBundle, REFNSIID aIID, + void** aResult) { + RefPtr it = new nsImportFieldMap(aBundle); + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsImportFieldMap, nsIImportFieldMap) + +NS_IMETHODIMP nsImportFieldMap::GetSkipFirstRecord(bool* result) { + NS_ENSURE_ARG_POINTER(result); + *result = m_skipFirstRecord; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetSkipFirstRecord(bool aResult) { + m_skipFirstRecord = aResult; + return NS_OK; +} + +nsImportFieldMap::nsImportFieldMap(nsIStringBundle* aBundle) { + m_numFields = 0; + m_pFields = nullptr; + m_pActive = nullptr; + m_allocated = 0; + // need to init the description array + m_mozFieldCount = 0; + m_skipFirstRecord = false; + nsCOMPtr pBundle = aBundle; + + nsString* pStr; + for (int32_t i = IMPORT_FIELD_DESC_START; i <= IMPORT_FIELD_DESC_END; + i++, m_mozFieldCount++) { + pStr = new nsString(); + if (pBundle) { + nsImportStringBundle::GetStringByID(i, pBundle, *pStr); + } else + pStr->AppendInt(i); + m_descriptions.AppendElement(pStr); + } +} + +nsImportFieldMap::~nsImportFieldMap() { + if (m_pFields) delete[] m_pFields; + if (m_pActive) delete[] m_pActive; + + nsString* pStr; + for (int32_t i = 0; i < m_mozFieldCount; i++) { + pStr = m_descriptions.ElementAt(i); + delete pStr; + } + m_descriptions.Clear(); +} + +NS_IMETHODIMP nsImportFieldMap::GetNumMozFields(int32_t* aNumFields) { + NS_ASSERTION(aNumFields != nullptr, "null ptr"); + if (!aNumFields) return NS_ERROR_NULL_POINTER; + + *aNumFields = m_mozFieldCount; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetMapSize(int32_t* aNumFields) { + NS_ASSERTION(aNumFields != nullptr, "null ptr"); + if (!aNumFields) return NS_ERROR_NULL_POINTER; + + *aNumFields = m_numFields; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetFieldDescription(int32_t index, + char16_t** _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + *_retval = nullptr; + if ((index < 0) || ((size_t)index >= m_descriptions.Length())) + return NS_ERROR_FAILURE; + + *_retval = ToNewUnicode(*(m_descriptions.ElementAt(index))); + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldMapSize(int32_t size) { + nsresult rv = Allocate(size); + if (NS_FAILED(rv)) return rv; + + m_numFields = size; + + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::DefaultFieldMap(int32_t size) { + nsresult rv = SetFieldMapSize(size); + if (NS_FAILED(rv)) return rv; + for (int32_t i = 0; i < size; i++) { + m_pFields[i] = i; + m_pActive[i] = true; + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetFieldMap(int32_t index, int32_t* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE; + + *_retval = m_pFields[index]; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldMap(int32_t index, int32_t fieldNum) { + if (index == -1) { + nsresult rv = Allocate(m_numFields + 1); + if (NS_FAILED(rv)) return rv; + index = m_numFields; + m_numFields++; + } else { + if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE; + } + + if ((fieldNum != -1) && ((fieldNum < 0) || (fieldNum >= m_mozFieldCount))) + return NS_ERROR_FAILURE; + + m_pFields[index] = fieldNum; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::GetFieldActive(int32_t index, bool* active) { + NS_ASSERTION(active != nullptr, "null ptr"); + if (!active) return NS_ERROR_NULL_POINTER; + if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE; + + *active = m_pActive[index]; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldActive(int32_t index, bool active) { + if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE; + + m_pActive[index] = active; + return NS_OK; +} + +NS_IMETHODIMP nsImportFieldMap::SetFieldValue(nsIAbDirectory* database, + nsIAbCard* row, int32_t fieldNum, + const nsAString& value) { + // Allow the special value for a null field + if (fieldNum == -1) return NS_OK; + + if ((fieldNum < 0) || (fieldNum >= m_mozFieldCount)) return NS_ERROR_FAILURE; + + // UGGG!!!!! lot's of typing here! + nsresult rv; + + switch (fieldNum) { + case 0: + rv = row->SetFirstName(value); + break; + case 1: + rv = row->SetLastName(value); + break; + case 2: + rv = row->SetDisplayName(value); + break; + case 3: + rv = row->SetPropertyAsAString(kNicknameProperty, value); + break; + case 4: + rv = row->SetPrimaryEmail(value); + break; + case 5: + rv = row->SetPropertyAsAString(k2ndEmailProperty, value); + break; + case 6: + rv = row->SetPropertyAsAString(kWorkPhoneProperty, value); + break; + case 7: + rv = row->SetPropertyAsAString(kHomePhoneProperty, value); + break; + case 8: + rv = row->SetPropertyAsAString(kFaxProperty, value); + break; + case 9: + rv = row->SetPropertyAsAString(kPagerProperty, value); + break; + case 10: + rv = row->SetPropertyAsAString(kCellularProperty, value); + break; + case 11: + rv = row->SetPropertyAsAString(kHomeAddressProperty, value); + break; + case 12: + rv = row->SetPropertyAsAString(kHomeAddress2Property, value); + break; + case 13: + rv = row->SetPropertyAsAString(kHomeCityProperty, value); + break; + case 14: + rv = row->SetPropertyAsAString(kHomeStateProperty, value); + break; + case 15: + rv = row->SetPropertyAsAString(kHomeZipCodeProperty, value); + break; + case 16: + rv = row->SetPropertyAsAString(kHomeCountryProperty, value); + break; + case 17: + rv = row->SetPropertyAsAString(kWorkAddressProperty, value); + break; + case 18: + rv = row->SetPropertyAsAString(kWorkAddress2Property, value); + break; + case 19: + rv = row->SetPropertyAsAString(kWorkCityProperty, value); + break; + case 20: + rv = row->SetPropertyAsAString(kWorkStateProperty, value); + break; + case 21: + rv = row->SetPropertyAsAString(kWorkZipCodeProperty, value); + break; + case 22: + rv = row->SetPropertyAsAString(kWorkCountryProperty, value); + break; + case 23: + rv = row->SetPropertyAsAString(kJobTitleProperty, value); + break; + case 24: + rv = row->SetPropertyAsAString(kDepartmentProperty, value); + break; + case 25: + rv = row->SetPropertyAsAString(kCompanyProperty, value); + break; + case 26: + rv = row->SetPropertyAsAString(kWorkWebPageProperty, value); + break; + case 27: + rv = row->SetPropertyAsAString(kHomeWebPageProperty, value); + break; + case 28: + rv = row->SetPropertyAsAString(kBirthYearProperty, value); + break; + case 29: + rv = row->SetPropertyAsAString(kBirthMonthProperty, value); + break; + case 30: + rv = row->SetPropertyAsAString(kBirthDayProperty, value); + break; + case 31: + rv = row->SetPropertyAsAString(kCustom1Property, value); + break; + case 32: + rv = row->SetPropertyAsAString(kCustom2Property, value); + break; + case 33: + rv = row->SetPropertyAsAString(kCustom3Property, value); + break; + case 34: + rv = row->SetPropertyAsAString(kCustom4Property, value); + break; + case 35: + rv = row->SetPropertyAsAString(kNotesProperty, value); + break; + case 36: + rv = row->SetPropertyAsAString(kAIMProperty, value); + break; + default: + /* Get the field description, and add it as an anonymous attr? */ + /* OR WHAT???? */ + { rv = NS_ERROR_FAILURE; } + } + + return rv; +} + +nsresult nsImportFieldMap::Allocate(int32_t newSize) { + if (newSize <= m_allocated) return NS_OK; + + int32_t sz = m_allocated; + while (sz < newSize) sz += 30; + + int32_t* pData = new int32_t[sz]; + if (!pData) return NS_ERROR_OUT_OF_MEMORY; + bool* pActive = new bool[sz]; + if (!pActive) { + delete[] pData; + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t i; + for (i = 0; i < sz; i++) { + pData[i] = -1; + pActive[i] = true; + } + if (m_numFields) { + for (i = 0; i < m_numFields; i++) { + pData[i] = m_pFields[i]; + pActive[i] = m_pActive[i]; + } + delete[] m_pFields; + delete[] m_pActive; + } + m_allocated = sz; + m_pFields = pData; + m_pActive = pActive; + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsImportFieldMap.h b/comm/mailnews/import/src/nsImportFieldMap.h new file mode 100644 index 0000000000..1d13df70a1 --- /dev/null +++ b/comm/mailnews/import/src/nsImportFieldMap.h @@ -0,0 +1,44 @@ +/* -*- 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 nsImportFieldMap_h___ +#define nsImportFieldMap_h___ + +#include "nscore.h" +#include "nsIImportFieldMap.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsIStringBundle.h" + +//////////////////////////////////////////////////////////////////////// + +class nsIStringBundle; + +class nsImportFieldMap : public nsIImportFieldMap { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIIMPORTFIELDMAP + + explicit nsImportFieldMap(nsIStringBundle* aBundle); + + static nsresult Create(nsIStringBundle* aBundle, REFNSIID aIID, + void** aResult); + + private: + virtual ~nsImportFieldMap(); + nsresult Allocate(int32_t newSize); + + private: + int32_t m_numFields; + int32_t* m_pFields; + bool* m_pActive; + int32_t m_allocated; + nsTArray m_descriptions; + int32_t m_mozFieldCount; + bool m_skipFirstRecord; +}; + +#endif diff --git a/comm/mailnews/import/src/nsImportMail.cpp b/comm/mailnews/import/src/nsImportMail.cpp new file mode 100644 index 0000000000..e845f773f8 --- /dev/null +++ b/comm/mailnews/import/src/nsImportMail.cpp @@ -0,0 +1,1007 @@ +/* -*- 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/. */ + +#include "nsImportMail.h" + +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIMsgAccountManager.h" +#include "nsImportStringBundle.h" +#include "nsTextFormatter.h" +#include "ImportDebug.h" +#include "plstr.h" +#include "nsThreadUtils.h" +#include "mozilla/Components.h" +#include "msgCore.h" + +// forward decl for proxy methods +nsresult ProxyGetSubFolders(nsIMsgFolder* aFolder); +nsresult ProxyGetChildNamed(nsIMsgFolder* aFolder, const nsAString& aName, + nsIMsgFolder** aChild); +nsresult ProxyGetParent(nsIMsgFolder* aFolder, nsIMsgFolder** aParent); +nsresult ProxyContainsChildNamed(nsIMsgFolder* aFolder, const nsAString& aName, + bool* aResult); +nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder* aFolder, + const nsAString& aPrefix, + nsIMsgFolder* aOtherFolder, + nsAString& aName); +nsresult ProxyCreateSubfolder(nsIMsgFolder* aFolder, const nsAString& aName); +nsresult ProxyForceDBClosed(nsIMsgFolder* aFolder); + +nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric) { + NS_ASSERTION(aImportGeneric != nullptr, "null ptr"); + if (!aImportGeneric) return NS_ERROR_NULL_POINTER; + + RefPtr pGen = new nsImportGenericMail(); + return pGen->QueryInterface(NS_GET_IID(nsIImportGeneric), + (void**)aImportGeneric); +} + +nsImportGenericMail::nsImportGenericMail() { + m_found = false; + m_userVerify = false; + m_gotLocation = false; + m_gotDefaultMailboxes = false; + m_totalSize = 0; + m_doImport = false; + m_pThreadData = nullptr; + + m_pDestFolder = nullptr; + m_deleteDestFolder = false; + m_createdFolder = false; + m_performingMigration = false; + + nsresult rv = nsImportStringBundle::GetStringBundle( + IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle)); + if (NS_FAILED(rv)) + IMPORT_LOG0("Failed to get string bundle for Importing Mail"); +} + +nsImportGenericMail::~nsImportGenericMail() { + if (m_pThreadData) { + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } +} + +NS_IMPL_ISUPPORTS(nsImportGenericMail, nsIImportGeneric) + +NS_IMETHODIMP nsImportGenericMail::GetData(const char* dataId, + nsISupports** _retval) { + nsresult rv = NS_OK; + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = nullptr; + if (!PL_strcasecmp(dataId, "mailInterface")) { + NS_IF_ADDREF(*_retval = m_pInterface); + } + + if (!PL_strcasecmp(dataId, "mailLocation")) { + if (!m_pSrcLocation) GetDefaultLocation(); + NS_IF_ADDREF(*_retval = m_pSrcLocation); + } + + if (!PL_strcasecmp(dataId, "mailDestination")) { + if (!m_pDestFolder) GetDefaultDestination(); + NS_IF_ADDREF(*_retval = m_pDestFolder); + } + + if (!PL_strcasecmp(dataId, "migration")) { + nsCOMPtr migrationString = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + migrationString->SetData(m_performingMigration); + migrationString.forget(_retval); + } + + if (!PL_strcasecmp(dataId, "currentMailbox")) { + // create an nsISupportsString, get the current mailbox + // name being imported and put it in the string + nsCOMPtr data = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + if (m_pThreadData) { + GetMailboxName(m_pThreadData->currentMailbox, data); + } + data.forget(_retval); + } + + return rv; +} + +NS_IMETHODIMP nsImportGenericMail::SetData(const char* dataId, + nsISupports* item) { + nsresult rv = NS_OK; + NS_ASSERTION(dataId != nullptr, "null ptr"); + if (!dataId) return NS_ERROR_NULL_POINTER; + + if (!PL_strcasecmp(dataId, "mailInterface")) { + m_pInterface = nullptr; + if (item) m_pInterface = do_QueryInterface(item); + } + + if (!PL_strcasecmp(dataId, "mailLocation")) { + m_mailboxes.Clear(); + m_gotDefaultMailboxes = false; + m_pSrcLocation = nullptr; + if (item) { + nsresult rv; + nsCOMPtr location = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_pSrcLocation = location; + } + } + + if (!PL_strcasecmp(dataId, "mailDestination")) { + m_pDestFolder = nullptr; + if (item) m_pDestFolder = do_QueryInterface(item); + m_deleteDestFolder = false; + } + + if (!PL_strcasecmp(dataId, "name")) { + if (item) { + nsCOMPtr nameString = do_QueryInterface(item, &rv); + if (NS_SUCCEEDED(rv)) rv = nameString->GetData(m_pName); + } + } + + if (!PL_strcasecmp(dataId, "migration")) { + if (item) { + nsCOMPtr migrationString = + do_QueryInterface(item, &rv); + if (NS_SUCCEEDED(rv)) + rv = migrationString->GetData(&m_performingMigration); + } + } + return rv; +} + +NS_IMETHODIMP nsImportGenericMail::GetStatus(const char* statusKind, + int32_t* _retval) { + NS_ASSERTION(statusKind != nullptr, "null ptr"); + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!statusKind || !_retval) return NS_ERROR_NULL_POINTER; + + *_retval = 0; + + if (!PL_strcasecmp(statusKind, "isInstalled")) { + GetDefaultLocation(); + *_retval = (int32_t)m_found; + } + + if (!PL_strcasecmp(statusKind, "canUserSetLocation")) { + GetDefaultLocation(); + *_retval = (int32_t)m_userVerify; + } + + return NS_OK; +} + +void nsImportGenericMail::GetDefaultLocation(void) { + if (!m_pInterface) return; + + if (m_pSrcLocation && m_gotLocation) return; + + m_gotLocation = true; + + nsCOMPtr pLoc; + m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found, + &m_userVerify); + if (!m_pSrcLocation) m_pSrcLocation = pLoc; +} + +void nsImportGenericMail::GetDefaultMailboxes(void) { + if (!m_pInterface || !m_pSrcLocation) return; + if (m_gotDefaultMailboxes) return; + m_pInterface->FindMailboxes(m_pSrcLocation, m_mailboxes); + m_gotDefaultMailboxes = true; +} + +void nsImportGenericMail::GetDefaultDestination(void) { + if (m_pDestFolder) return; + if (!m_pInterface) return; + + nsIMsgFolder* rootFolder; + m_deleteDestFolder = false; + m_createdFolder = false; + if (CreateFolder(&rootFolder)) { + m_pDestFolder = rootFolder; + m_deleteDestFolder = true; + m_createdFolder = true; + return; + } + IMPORT_LOG0( + "*** GetDefaultDestination: Failed to create a default import " + "destination folder."); +} + +NS_IMETHODIMP nsImportGenericMail::WantsProgress(bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + NS_ENSURE_ARG_POINTER(_retval); + + if (m_pThreadData) { + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + GetDefaultLocation(); + GetDefaultMailboxes(); + + if (!m_pDestFolder) { + GetDefaultDestination(); + } + + bool result = false; + uint32_t totalSize = 0; + for (nsIImportMailboxDescriptor* box : m_mailboxes) { + bool doImport = false; + uint32_t size = 0; + nsresult rv = box->GetImport(&doImport); + if (NS_SUCCEEDED(rv) && doImport) { + (void)box->GetSize(&size); + result = true; + } + totalSize += size; + } + m_totalSize = totalSize; + m_doImport = result; + *_retval = result; + return NS_OK; +} + +void nsImportGenericMail::GetMailboxName(uint32_t index, + nsISupportsString* pStr) { + if (index >= m_mailboxes.Length()) { + return; + } + nsAutoString name; + m_mailboxes[index]->GetDisplayName(getter_Copies(name)); + if (!name.IsEmpty()) { + pStr->SetData(name); + } +} + +NS_IMETHODIMP nsImportGenericMail::BeginImport(nsISupportsString* successLog, + nsISupportsString* errorLog, + bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + nsString success; + nsString error; + + if (!m_doImport) { + nsImportStringBundle::GetStringByID(IMPORT_NO_MAILBOXES, m_stringBundle, + success); + SetLogs(success, error, successLog, errorLog); + *_retval = true; + return NS_OK; + } + + if (!m_pInterface || !m_gotDefaultMailboxes) { + IMPORT_LOG0( + "*** BeginImport: Either the interface or source mailbox is not set " + "properly."); + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOTINITIALIZED, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + if (!m_pDestFolder) { + IMPORT_LOG0( + "*** BeginImport: The destination mailbox is not set properly."); + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NODESTFOLDER, + m_stringBundle, error); + SetLogs(success, error, successLog, errorLog); + *_retval = false; + return NS_OK; + } + + if (m_pThreadData) { + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + m_pSuccessLog = successLog; + m_pErrorLog = errorLog; + + // kick off the thread to do the import!!!! + m_pThreadData = new ImportThreadData(); + m_pThreadData->boxes = m_mailboxes.Clone(); + m_pThreadData->mailImport = m_pInterface; + m_pThreadData->errorLog = m_pErrorLog; + m_pThreadData->successLog = m_pSuccessLog; + + m_pThreadData->ownsDestRoot = m_deleteDestFolder; + m_pThreadData->destRoot = m_pDestFolder; + m_pThreadData->performingMigration = m_performingMigration; + + m_pThreadData->stringBundle = m_stringBundle; + + // Previously this was run in a sub-thread, after introducing + // SeamonkeyImport.jsm and because JS XPCOM can only run in the main thread, + // this has been changed to run in the main thread. + ImportMailThread(m_pThreadData); + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericMail::ContinueImport(bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + *_retval = true; + if (m_pThreadData) { + if (m_pThreadData->fatalError) *_retval = false; + } + + return NS_OK; +} + +NS_IMETHODIMP nsImportGenericMail::GetProgress(int32_t* _retval) { + // This returns the progress from the the currently + // running import mail or import address book thread. + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + if (!m_pThreadData || !(m_pThreadData->threadAlive)) { + *_retval = 100; + return NS_OK; + } + + uint32_t sz = 0; + if (m_pThreadData->currentSize && m_pInterface) { + if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) sz = 0; + } + + // *_retval = (int32_t) (((uint32_t)(m_pThreadData->currentTotal + sz) * + // (uint32_t)100) / m_totalSize); + + if (m_totalSize) { + double perc; + perc = (double)m_pThreadData->currentTotal; + perc += sz; + perc *= 100; + perc /= m_totalSize; + *_retval = (int32_t)perc; + if (*_retval > 100) *_retval = 100; + } else + *_retval = 0; + + // never return 100% while the thread is still alive + if (*_retval > 99) *_retval = 99; + + return NS_OK; +} + +void nsImportGenericMail::ReportError(int32_t id, const char16_t* pName, + nsString* pStream, + nsIStringBundle* aBundle) { + if (!pStream) return; + + // load the error string + char16_t* pFmt = nsImportStringBundle::GetStringByID(id, aBundle); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, pName); + pStream->Append(pText); + free(pFmt); + pStream->Append(NS_ConvertASCIItoUTF16(MSG_LINEBREAK)); +} + +void nsImportGenericMail::SetLogs(nsString& success, nsString& error, + nsISupportsString* pSuccess, + nsISupportsString* pError) { + nsAutoString str; + if (pSuccess) { + pSuccess->GetData(str); + str.Append(success); + pSuccess->SetData(str); + } + if (pError) { + pError->GetData(str); + str.Append(error); + pError->SetData(str); + } +} + +NS_IMETHODIMP nsImportGenericMail::CancelImport(void) { + if (m_pThreadData) { + m_pThreadData->abort = true; + m_pThreadData->DriverAbort(); + m_pThreadData = nullptr; + } + + return NS_OK; +} + +ImportThreadData::ImportThreadData() { + fatalError = false; + driverAlive = true; + threadAlive = true; + abort = false; + currentTotal = 0; + currentSize = 0; + destRoot = nullptr; + ownsDestRoot = false; +} + +ImportThreadData::~ImportThreadData() {} + +void ImportThreadData::DriverDelete(void) { + driverAlive = false; + if (!driverAlive && !threadAlive) delete this; +} + +void ImportThreadData::ThreadDelete() { + threadAlive = false; + if (!driverAlive && !threadAlive) delete this; +} + +void ImportThreadData::DriverAbort() { + if (abort && !threadAlive && destRoot) { + if (ownsDestRoot) { + destRoot->RecursiveDelete(true); + } else { + // FIXME: just delete the stuff we created? + } + } else + abort = true; + DriverDelete(); +} + +static void ImportMailThread(void* stuff) { + ImportThreadData* pData = (ImportThreadData*)stuff; + + IMPORT_LOG0("ImportMailThread: Starting..."); + + nsresult rv = NS_OK; + + nsCOMPtr destRoot(pData->destRoot); + + uint32_t count = pData->boxes.Length(); + + uint32_t size; + uint32_t depth = 1; + uint32_t newDepth; + nsString lastName; + + nsCOMPtr curFolder(destRoot); + + nsCOMPtr newFolder; + nsCOMPtr subFolder; + + bool exists; + + nsString success; + nsString error; + + // GetSubFolders() will initialize folders if they are not already + // initialized. + ProxyGetSubFolders(curFolder); + + IMPORT_LOG1("ImportMailThread: Total number of folders to import = %d.", + count); + + // Note that the front-end js script only displays one import result string so + // we combine both good and bad import status into one string (in var + // 'success'). + + for (uint32_t i = 0; (i < count) && !(pData->abort); i++) { + nsIImportMailboxDescriptor* box = pData->boxes[i]; + pData->currentMailbox = i; + + bool doImport = false; + size = 0; + rv = box->GetImport(&doImport); + if (doImport) rv = box->GetSize(&size); + rv = box->GetDepth(&newDepth); + if (newDepth > depth) { + // OK, we are going to add a subfolder under the last/previous folder we + // processed, so find this folder (stored in 'lastName') who is going to + // be the new parent folder. + IMPORT_LOG1("ImportMailThread: Processing child folder '%s'.", + NS_ConvertUTF16toUTF8(lastName).get()); + rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(subFolder)); + if (NS_FAILED(rv)) { + IMPORT_LOG1( + "*** ImportMailThread: Failed to get the interface for child " + "folder '%s'.", + NS_ConvertUTF16toUTF8(lastName).get()); + nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD, + lastName.get(), &error, + pData->stringBundle); + pData->fatalError = true; + break; + } + curFolder = subFolder; + // Make sure this new parent folder obj has the correct subfolder list + // so far. + rv = ProxyGetSubFolders(curFolder); + } else if (newDepth < depth) { + rv = NS_OK; + while ((newDepth < depth) && NS_SUCCEEDED(rv)) { + rv = curFolder->GetParent(getter_AddRefs(curFolder)); + if (NS_FAILED(rv)) { + IMPORT_LOG1( + "*** ImportMailThread: Failed to get the interface for parent " + "folder '%s'.", + NS_ConvertUTF16toUTF8(lastName).get()); + nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD, + lastName.get(), &error, + pData->stringBundle); + pData->fatalError = true; + break; + } + depth--; + } + if (NS_FAILED(rv)) { + IMPORT_LOG1( + "*** ImportMailThread: Failed to get the proxy interface for " + "parent folder '%s'.", + NS_ConvertUTF16toUTF8(lastName).get()); + nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOPROXY, + pData->stringBundle, error); + pData->fatalError = true; + break; + } + } + depth = newDepth; + char16_t* pName = nullptr; + box->GetDisplayName(&pName); + if (pName) { + lastName = pName; + free(pName); + } else + lastName.AssignLiteral("Unknown!"); + + // translate the folder name if we are doing migration, but + // only for special folders which are at the root level + if (pData->performingMigration && depth == 1) + pData->mailImport->TranslateFolderName(lastName, lastName); + + exists = false; + rv = ProxyContainsChildNamed(curFolder, lastName, &exists); + + // If we are performing profile migration (as opposed to importing) then + // we are starting with empty local folders. In that case, always choose + // to over-write the existing local folder with this name. Don't create a + // unique subfolder name. Otherwise you end up with "Inbox, Inbox0" or + // "Unsent Folders, UnsentFolders0" + if (exists && !pData->performingMigration) { + nsString subName; + ProxyGenerateUniqueSubfolderName(curFolder, lastName, nullptr, subName); + if (!subName.IsEmpty()) lastName.Assign(subName); + } + + IMPORT_LOG1("ImportMailThread: Creating new import folder '%s'.", + NS_ConvertUTF16toUTF8(lastName).get()); + ProxyCreateSubfolder( + curFolder, + lastName); // this may fail if the folder already exists..that's ok + + rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(newFolder)); + if (NS_FAILED(rv)) { + IMPORT_LOG1( + "*** ImportMailThread: Failed to locate subfolder '%s' after it's " + "been created.", + NS_ConvertUTF16toUTF8(lastName).get()); + nsImportGenericMail::ReportError(IMPORT_ERROR_MB_CREATE, lastName.get(), + &error, pData->stringBundle); + } + + if (size && doImport && newFolder && NS_SUCCEEDED(rv)) { + bool fatalError = false; + pData->currentSize = size; + char16_t* pSuccess = nullptr; + char16_t* pError = nullptr; + rv = pData->mailImport->ImportMailbox(box, newFolder, &pError, &pSuccess, + &fatalError); + if (pError) { + error.Append(pError); + free(pError); + } + if (pSuccess) { + success.Append(pSuccess); + free(pSuccess); + } + + pData->currentSize = 0; + pData->currentTotal += size; + + // commit to the db synchronously, but using a proxy since it doesn't + // like being used elsewhere than from the main thread. OK, we've copied + // the actual folder/file over if the folder size is not 0 (ie, the msg + // summary is no longer valid) so close the msg database so that when + // the folder is reopened the folder db can be reconstructed (which + // validates msg summary and forces folder to be reparsed). + rv = ProxyForceDBClosed(newFolder); + fatalError = NS_FAILED(rv); + + if (fatalError) { + IMPORT_LOG1( + "*** ImportMailThread: ImportMailbox returned fatalError, " + "mailbox #%d\n", + (int)i); + pData->fatalError = true; + break; + } + } + } + + // Now save the new acct info to pref file. + nsCOMPtr accMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_SUCCEEDED(rv) && accMgr) { + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + } + + nsImportGenericMail::SetLogs(success, error, pData->successLog, + pData->errorLog); + + if (pData->abort || pData->fatalError) { + IMPORT_LOG0("*** ImportMailThread: Abort or fatalError flag was set\n"); + if (pData->ownsDestRoot) { + IMPORT_LOG0("Calling destRoot->RecursiveDelete\n"); + destRoot->RecursiveDelete(true); + } else { + // FIXME: just delete the stuff we created? + } + } + + IMPORT_LOG1("Import mailbox thread done: %d\n", (int)pData->currentTotal); + + pData->ThreadDelete(); +} + +// Creates a folder in Local Folders with the module name + mail +// for e.g: Outlook Mail +bool nsImportGenericMail::CreateFolder(nsIMsgFolder** ppFolder) { + nsresult rv; + *ppFolder = nullptr; + + nsCOMPtr bundle; + nsCOMPtr bundleService = + mozilla::components::StringBundle::Service(); + if (!bundleService) return false; + rv = bundleService->CreateBundle(IMPORT_MSGS_URL, getter_AddRefs(bundle)); + if (NS_FAILED(rv)) return false; + nsString folderName; + if (!m_pName.IsEmpty()) { + AutoTArray moduleName = {m_pName}; + rv = bundle->FormatStringFromName("ImportModuleFolderName", moduleName, + folderName); + } else { + rv = bundle->GetStringFromName("DefaultFolderName", folderName); + } + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to get Folder Name!\n"); + return false; + } + nsCOMPtr accMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create account manager!\n"); + return false; + } + + nsCOMPtr server; + rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server)); + // if Local Folders does not exist already, create it + if (NS_FAILED(rv) || !server) { + rv = accMgr->CreateLocalMailAccount(); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + + rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server)); + } + + if (NS_SUCCEEDED(rv) && server) { + nsCOMPtr localRootFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(localRootFolder)); + if (localRootFolder) { + // we need to call GetSubFolders() so that the folders get initialized + // if they are not initialized yet. + nsTArray> dummy; + rv = localRootFolder->GetSubFolders(dummy); + if (NS_SUCCEEDED(rv)) { + // check if the folder name we picked already exists. + bool exists = false; + rv = localRootFolder->ContainsChildNamed(folderName, &exists); + if (exists) { + nsString name; + localRootFolder->GenerateUniqueSubfolderName(folderName, nullptr, + name); + if (!name.IsEmpty()) + folderName.Assign(name); + else { + IMPORT_LOG0("*** Failed to find a unique folder name!\n"); + return false; + } + } + IMPORT_LOG1("Creating folder for importing mail: '%s'\n", + NS_ConvertUTF16toUTF8(folderName).get()); + + // Bug 564162 identifies a dataloss design flaw. + // A working Thunderbird client can have mail in Local Folders and a + // subsequent import 'Everything' will trigger a migration which + // overwrites existing mailboxes with the imported mailboxes. + rv = localRootFolder->CreateSubfolder(folderName, nullptr); + if (NS_SUCCEEDED(rv)) { + rv = localRootFolder->GetChildNamed(folderName, ppFolder); + if (*ppFolder) { + IMPORT_LOG1("Folder '%s' created successfully\n", + NS_ConvertUTF16toUTF8(folderName).get()); + return true; + } + } + } + } // if localRootFolder + } // if server + IMPORT_LOG0("****** FAILED TO CREATE FOLDER FOR IMPORT\n"); + return false; +} + +/** + * These are the proxy objects we use to proxy nsIMsgFolder methods back + * the the main thread. Since there are only five, we can hand roll them. + * A better design might be a co-routine-ish design where the ui thread + * hands off each folder to the import thread and when the thread finishes + * the folder, the main thread hands it the next folder. + */ + +class GetSubFoldersRunnable : public mozilla::Runnable { + public: + explicit GetSubFoldersRunnable(nsIMsgFolder* aFolder); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + private: + nsCOMPtr m_folder; +}; + +GetSubFoldersRunnable::GetSubFoldersRunnable(nsIMsgFolder* aFolder) + : mozilla::Runnable("GetSubFoldersRunnable"), m_folder(aFolder) {} + +NS_IMETHODIMP GetSubFoldersRunnable::Run() { + nsTArray> dummy; + mResult = m_folder->GetSubFolders(dummy); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyGetSubFolders(nsIMsgFolder* aFolder) { + RefPtr getSubFolders = + new GetSubFoldersRunnable(aFolder); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyGetSubFolders"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(getSubFolders)); + NS_ENSURE_SUCCESS(rv, rv); + return getSubFolders->mResult; +} + +class GetChildNamedRunnable : public mozilla::Runnable { + public: + GetChildNamedRunnable(nsIMsgFolder* aFolder, const nsAString& aName, + nsIMsgFolder** aChild); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsCOMPtr m_folder; + nsString m_name; + nsIMsgFolder** m_child; +}; + +GetChildNamedRunnable::GetChildNamedRunnable(nsIMsgFolder* aFolder, + const nsAString& aName, + nsIMsgFolder** aChild) + : mozilla::Runnable("GetChildNamedRunnable"), + mResult(NS_OK), + m_folder(aFolder), + m_name(aName), + m_child(aChild) {} + +NS_IMETHODIMP GetChildNamedRunnable::Run() { + mResult = m_folder->GetChildNamed(m_name, m_child); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyGetChildNamed(nsIMsgFolder* aFolder, const nsAString& aName, + nsIMsgFolder** aChild) { + RefPtr getChildNamed = + new GetChildNamedRunnable(aFolder, aName, aChild); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyGetChildNamed"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(getChildNamed)); + NS_ENSURE_SUCCESS(rv, rv); + return getChildNamed->mResult; +} + +class GetParentRunnable : public mozilla::Runnable { + public: + GetParentRunnable(nsIMsgFolder* aFolder, nsIMsgFolder** aParent); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsCOMPtr m_folder; + nsIMsgFolder** m_parent; +}; + +GetParentRunnable::GetParentRunnable(nsIMsgFolder* aFolder, + nsIMsgFolder** aParent) + : mozilla::Runnable("GetParentRunnable"), + mResult(NS_OK), + m_folder(aFolder), + m_parent(aParent) {} + +NS_IMETHODIMP GetParentRunnable::Run() { + mResult = m_folder->GetParent(m_parent); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyGetParent(nsIMsgFolder* aFolder, nsIMsgFolder** aParent) { + RefPtr getParent = new GetParentRunnable(aFolder, aParent); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyGetParent"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(getParent)); + NS_ENSURE_SUCCESS(rv, rv); + return getParent->mResult; +} + +class ContainsChildNamedRunnable : public mozilla::Runnable { + public: + ContainsChildNamedRunnable(nsIMsgFolder* aFolder, const nsAString& aName, + bool* aResult); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsCOMPtr m_folder; + nsString m_name; + bool* m_result; +}; + +ContainsChildNamedRunnable::ContainsChildNamedRunnable(nsIMsgFolder* aFolder, + const nsAString& aName, + bool* aResult) + : mozilla::Runnable("ContainsChildNamedRunnable"), + mResult(NS_OK), + m_folder(aFolder), + m_name(aName), + m_result(aResult) {} + +NS_IMETHODIMP ContainsChildNamedRunnable::Run() { + mResult = m_folder->ContainsChildNamed(m_name, m_result); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyContainsChildNamed(nsIMsgFolder* aFolder, const nsAString& aName, + bool* aResult) { + NS_ENSURE_ARG(aFolder); + RefPtr containsChildNamed = + new ContainsChildNamedRunnable(aFolder, aName, aResult); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyContainsChildNamed"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(containsChildNamed)); + NS_ENSURE_SUCCESS(rv, rv); + return containsChildNamed->mResult; +} + +class GenerateUniqueSubfolderNameRunnable : public mozilla::Runnable { + public: + GenerateUniqueSubfolderNameRunnable(nsIMsgFolder* aFolder, + const nsAString& prefix, + nsIMsgFolder* otherFolder, + nsAString& name); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsCOMPtr m_folder; + nsString m_prefix; + nsCOMPtr m_otherFolder; + nsString m_name; +}; + +GenerateUniqueSubfolderNameRunnable::GenerateUniqueSubfolderNameRunnable( + nsIMsgFolder* aFolder, const nsAString& aPrefix, nsIMsgFolder* aOtherFolder, + nsAString& aName) + : mozilla::Runnable("GenerateUniqueSubfolderNameRunnable"), + mResult(NS_OK), + m_folder(aFolder), + m_prefix(aPrefix), + m_otherFolder(aOtherFolder), + m_name(aName) {} + +NS_IMETHODIMP GenerateUniqueSubfolderNameRunnable::Run() { + mResult = + m_folder->GenerateUniqueSubfolderName(m_prefix, m_otherFolder, m_name); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder* aFolder, + const nsAString& aPrefix, + nsIMsgFolder* aOtherFolder, + nsAString& aName) + +{ + RefPtr generateUniqueSubfolderName = + new GenerateUniqueSubfolderNameRunnable(aFolder, aPrefix, aOtherFolder, + aName); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyGenerateUniqueSubfolderName"_ns, + mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(generateUniqueSubfolderName)); + NS_ENSURE_SUCCESS(rv, rv); + return generateUniqueSubfolderName->mResult; +} + +class CreateSubfolderRunnable : public mozilla::Runnable { + public: + CreateSubfolderRunnable(nsIMsgFolder* aFolder, const nsAString& aName); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsCOMPtr m_folder; + nsString m_name; +}; + +CreateSubfolderRunnable::CreateSubfolderRunnable(nsIMsgFolder* aFolder, + const nsAString& aName) + : mozilla::Runnable("CreateSubfolderRunnable"), + mResult(NS_OK), + m_folder(aFolder), + m_name(aName) {} + +NS_IMETHODIMP CreateSubfolderRunnable::Run() { + mResult = m_folder->CreateSubfolder(m_name, nullptr); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyCreateSubfolder(nsIMsgFolder* aFolder, const nsAString& aName) { + NS_ENSURE_ARG_POINTER(aFolder); + RefPtr createSubfolder = + new CreateSubfolderRunnable(aFolder, aName); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyCreateSubfolder"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(createSubfolder)); + NS_ENSURE_SUCCESS(rv, rv); + return createSubfolder->mResult; +} + +class ForceDBClosedRunnable : public mozilla::Runnable { + public: + explicit ForceDBClosedRunnable(nsIMsgFolder* aFolder); + NS_DECL_NSIRUNNABLE + nsresult mResult; + + protected: + nsCOMPtr m_folder; +}; + +ForceDBClosedRunnable::ForceDBClosedRunnable(nsIMsgFolder* aFolder) + : mozilla::Runnable("ForceDBClosedRunnable"), m_folder(aFolder) {} + +NS_IMETHODIMP ForceDBClosedRunnable::Run() { + mResult = m_folder->ForceDBClosed(); + return NS_OK; // Sync runnable must return OK. +} + +nsresult ProxyForceDBClosed(nsIMsgFolder* aFolder) { + RefPtr forceDBClosed = + new ForceDBClosedRunnable(aFolder); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyForceDBClosed"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(forceDBClosed)); + NS_ENSURE_SUCCESS(rv, rv); + return forceDBClosed->mResult; +} diff --git a/comm/mailnews/import/src/nsImportMail.h b/comm/mailnews/import/src/nsImportMail.h new file mode 100644 index 0000000000..fcd2a0c40c --- /dev/null +++ b/comm/mailnews/import/src/nsImportMail.h @@ -0,0 +1,86 @@ +/* -*- 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/. */ + +#include "nsCOMPtr.h" +#include "nsIImportMail.h" +#include "nsIImportGeneric.h" +#include "nsString.h" +#include "nsIMsgFolder.h" +#include "nsIStringBundle.h" + +#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties" + +//////////////////////////////////////////////////////////////////////// + +static void ImportMailThread(void* stuff); + +class ImportThreadData; + +class nsImportGenericMail : public nsIImportGeneric { + public: + nsImportGenericMail(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTGENERIC + + private: + virtual ~nsImportGenericMail(); + bool CreateFolder(nsIMsgFolder** ppFolder); + void GetDefaultMailboxes(void); + void GetDefaultLocation(void); + void GetDefaultDestination(void); + void GetMailboxName(uint32_t index, nsISupportsString* pStr); + + public: + static void SetLogs(nsString& success, nsString& error, + nsISupportsString* pSuccess, nsISupportsString* pError); + static void ReportError(int32_t id, const char16_t* pName, nsString* pStream, + nsIStringBundle* aBundle); + + private: + nsString m_pName; // module name that created this interface + nsCOMPtr m_pDestFolder; + bool m_deleteDestFolder; + bool m_createdFolder; + nsCOMPtr m_pSrcLocation; + bool m_gotLocation; + bool m_gotDefaultMailboxes; + bool m_found; + bool m_userVerify; + nsCOMPtr m_pInterface; + nsTArray> m_mailboxes; + nsCOMPtr m_pSuccessLog; + nsCOMPtr m_pErrorLog; + uint32_t m_totalSize; + bool m_doImport; + ImportThreadData* m_pThreadData; + bool m_performingMigration; + nsCOMPtr m_stringBundle; +}; + +class ImportThreadData { + public: + bool driverAlive; + bool threadAlive; + bool abort; + bool fatalError; + uint32_t currentTotal; + uint32_t currentSize; + nsCOMPtr destRoot; + bool ownsDestRoot; + nsTArray> boxes; + nsCOMPtr mailImport; + nsCOMPtr successLog; + nsCOMPtr errorLog; + uint32_t currentMailbox; + bool performingMigration; + nsCOMPtr stringBundle; + + ImportThreadData(); + ~ImportThreadData(); + void DriverDelete(); + void ThreadDelete(); + void DriverAbort(); +}; diff --git a/comm/mailnews/import/src/nsImportMailboxDescriptor.cpp b/comm/mailnews/import/src/nsImportMailboxDescriptor.cpp new file mode 100644 index 0000000000..8dc17b9317 --- /dev/null +++ b/comm/mailnews/import/src/nsImportMailboxDescriptor.cpp @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsImportMailboxDescriptor.h" +#include "nsComponentManagerUtils.h" + +//////////////////////////////////////////////////////////////////////// + +nsresult nsImportMailboxDescriptor::Create(REFNSIID aIID, void** aResult) { + RefPtr it = new nsImportMailboxDescriptor(); + return it->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsImportMailboxDescriptor, nsIImportMailboxDescriptor) + +nsImportMailboxDescriptor::nsImportMailboxDescriptor() { + m_import = true; + m_size = 0; + m_depth = 0; + m_id = 0; + m_pFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); +} diff --git a/comm/mailnews/import/src/nsImportMailboxDescriptor.h b/comm/mailnews/import/src/nsImportMailboxDescriptor.h new file mode 100644 index 0000000000..b760cbd266 --- /dev/null +++ b/comm/mailnews/import/src/nsImportMailboxDescriptor.h @@ -0,0 +1,94 @@ +/* -*- 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 nsImportMailboxDescriptor_h___ +#define nsImportMailboxDescriptor_h___ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsString.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" + +//////////////////////////////////////////////////////////////////////// + +class nsImportMailboxDescriptor : public nsIImportMailboxDescriptor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD GetIdentifier(uint32_t* pIdentifier) override { + *pIdentifier = m_id; + return NS_OK; + } + NS_IMETHOD SetIdentifier(uint32_t ident) override { + m_id = ident; + return NS_OK; + } + + /* attribute unsigned long depth; */ + NS_IMETHOD GetDepth(uint32_t* pDepth) override { + *pDepth = m_depth; + return NS_OK; + } + NS_IMETHOD SetDepth(uint32_t theDepth) override { + m_depth = theDepth; + return NS_OK; + } + + /* attribute unsigned long size; */ + NS_IMETHOD GetSize(uint32_t* pSize) override { + *pSize = m_size; + return NS_OK; + } + NS_IMETHOD SetSize(uint32_t theSize) override { + m_size = theSize; + return NS_OK; + } + + /* attribute wstring displayName; */ + NS_IMETHOD GetDisplayName(char16_t** pName) override { + *pName = ToNewUnicode(m_displayName); + return NS_OK; + } + NS_IMETHOD SetDisplayName(const char16_t* pName) override { + m_displayName = pName; + return NS_OK; + } + + /* attribute boolean import; */ + NS_IMETHOD GetImport(bool* pImport) override { + *pImport = m_import; + return NS_OK; + } + NS_IMETHOD SetImport(bool doImport) override { + m_import = doImport; + return NS_OK; + } + + /* readonly attribute nsIFile file; */ + NS_IMETHOD GetFile(nsIFile** aFile) override { + if (m_pFile) { + NS_ADDREF(*aFile = m_pFile); + return NS_OK; + } else + return NS_ERROR_FAILURE; + } + + nsImportMailboxDescriptor(); + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + virtual ~nsImportMailboxDescriptor() {} + uint32_t m_id; // used by creator of the structure + uint32_t m_depth; // depth in the hierarchy + nsString m_displayName; // name of this mailbox + nsCOMPtr m_pFile; // source file (if applicable) + uint32_t m_size; + bool m_import; // import it or not? +}; + +#endif diff --git a/comm/mailnews/import/src/nsImportScanFile.cpp b/comm/mailnews/import/src/nsImportScanFile.cpp new file mode 100644 index 0000000000..79971ae10a --- /dev/null +++ b/comm/mailnews/import/src/nsImportScanFile.cpp @@ -0,0 +1,154 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsImportScanFile.h" +#include "ImportCharSet.h" + +nsImportScanFile::nsImportScanFile() { + m_allocated = false; + m_eof = false; + m_pBuf = nullptr; +} + +nsImportScanFile::~nsImportScanFile() { + if (m_allocated) CleanUpScan(); +} + +void nsImportScanFile::InitScan(nsIInputStream* pInputStream, uint8_t* pBuf, + uint32_t sz) { + m_pInputStream = pInputStream; + m_pBuf = pBuf; + m_bufSz = sz; + m_bytesInBuf = 0; + m_pos = 0; +} + +void nsImportScanFile::CleanUpScan(void) { + m_pInputStream = nullptr; + if (m_allocated) { + delete[] m_pBuf; + m_pBuf = NULL; + } +} + +void nsImportScanFile::ShiftBuffer(void) { + uint8_t* pTop; + uint8_t* pCurrent; + + if (m_pos < m_bytesInBuf) { + pTop = m_pBuf; + pCurrent = pTop + m_pos; + uint32_t cnt = m_bytesInBuf - m_pos; + while (cnt) { + *pTop = *pCurrent; + pTop++; + pCurrent++; + cnt--; + } + } + + m_bytesInBuf -= m_pos; + m_pos = 0; +} + +bool nsImportScanFile::FillBufferFromFile(void) { + uint64_t available; + nsresult rv = m_pInputStream->Available(&available); + if (NS_FAILED(rv)) return false; + + // Fill up a buffer and scan it + ShiftBuffer(); + + // Read in some more bytes + uint32_t cnt = m_bufSz - m_bytesInBuf; + // To distinguish from disk errors + // Check first for end of file? + // Set a done flag if true... + uint32_t read; + char* pBuf = (char*)m_pBuf; + pBuf += m_bytesInBuf; + rv = m_pInputStream->Read(pBuf, (int32_t)cnt, &read); + + if (NS_FAILED(rv)) return false; + rv = m_pInputStream->Available(&available); + if (NS_FAILED(rv)) m_eof = true; + + m_bytesInBuf += cnt; + return true; +} + +bool nsImportScanFile::Scan(bool* pDone) { + uint64_t available; + nsresult rv = m_pInputStream->Available(&available); + if (NS_FAILED(rv)) { + if (m_pos < m_bytesInBuf) ScanBuffer(pDone); + *pDone = true; + return true; + } + + // Fill up a buffer and scan it + if (!FillBufferFromFile()) return false; + + return ScanBuffer(pDone); +} + +bool nsImportScanFile::ScanBuffer(bool*) { return true; } + +bool nsImportScanFileLines::ScanBuffer(bool* pDone) { + // m_pos, m_bytesInBuf, m_eof, m_pBuf are relevant + + uint32_t pos = m_pos; + uint32_t max = m_bytesInBuf; + uint8_t* pChar = m_pBuf + pos; + uint32_t startPos; + + while (pos < max) { + if (m_needEol) { + // Find the next eol... + while ((pos < max) && (*pChar != ImportCharSet::cCRChar) && + (*pChar != ImportCharSet::cLinefeedChar)) { + pos++; + pChar++; + } + m_pos = pos; + if (pos < max) m_needEol = false; + if (pos == max) // need more buffer for an end of line + break; + } + // Skip past any eol characters + while ((pos < max) && ((*pChar == ImportCharSet::cCRChar) || + (*pChar == ImportCharSet::cLinefeedChar))) { + pos++; + pChar++; + } + m_pos = pos; + if (pos == max) break; + // Make sure we can find either the eof or the + // next end of line + startPos = pos; + while ((pos < max) && (*pChar != ImportCharSet::cCRChar) && + (*pChar != ImportCharSet::cLinefeedChar)) { + pos++; + pChar++; + } + + // Is line too big for our buffer? + if ((pos == max) && !m_eof) { + if (!m_pos) { // line too big for our buffer + m_pos = pos; + m_needEol = true; + } + break; + } + + if (!ProcessLine(m_pBuf + startPos, pos - startPos, pDone)) { + return false; + } + m_pos = pos; + } + + return true; +} diff --git a/comm/mailnews/import/src/nsImportScanFile.h b/comm/mailnews/import/src/nsImportScanFile.h new file mode 100644 index 0000000000..e5704aaf36 --- /dev/null +++ b/comm/mailnews/import/src/nsImportScanFile.h @@ -0,0 +1,56 @@ +/* -*- 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 nsImportScanFile_h__ +#define nsImportScanFile_h__ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" + +class nsImportScanFile { + public: + nsImportScanFile(); + virtual ~nsImportScanFile(); + + void InitScan(nsIInputStream* pInputStream, uint8_t* pBuf, uint32_t sz); + + void CleanUpScan(void); + + virtual bool Scan(bool* pDone); + + protected: + void ShiftBuffer(void); + bool FillBufferFromFile(void); + virtual bool ScanBuffer(bool* pDone); + + protected: + nsCOMPtr m_pInputStream; + uint8_t* m_pBuf; + uint32_t m_bufSz; + uint32_t m_bytesInBuf; + uint32_t m_pos; + bool m_eof; + bool m_allocated; +}; + +class nsImportScanFileLines : public nsImportScanFile { + public: + nsImportScanFileLines() { m_needEol = false; } + + void ResetLineScan(void) { m_needEol = false; } + + virtual bool ProcessLine(uint8_t* /* pLine */, uint32_t /* len */, + bool* /* pDone */) { + return true; + } + + protected: + virtual bool ScanBuffer(bool* pDone) override; + + bool m_needEol; +}; + +#endif /* nsImportScanFile_h__ */ diff --git a/comm/mailnews/import/src/nsImportService.cpp b/comm/mailnews/import/src/nsImportService.cpp new file mode 100644 index 0000000000..062e7d664c --- /dev/null +++ b/comm/mailnews/import/src/nsImportService.cpp @@ -0,0 +1,293 @@ +/* -*- 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/. */ + +#include "nsString.h" +#include "nsMemory.h" +#include "nsIImportModule.h" +#include "nsIImportService.h" +#include "nsImportMailboxDescriptor.h" +#include "nsImportABDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsImportFieldMap.h" +#include "nsICategoryManager.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsThreadUtils.h" +#include "ImportDebug.h" +#include "nsImportService.h" +#include "nsImportStringBundle.h" +#include "nsCRTGlue.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIMsgSend.h" +#include "nsMsgUtils.h" +#include "mozilla/SimpleEnumerator.h" + +mozilla::LazyLogModule IMPORTLOGMODULE("Import"); + +//////////////////////////////////////////////////////////////////////// + +nsImportService::nsImportService() { + IMPORT_LOG0("* nsImport Service Created\n"); + + m_didDiscovery = false; + + nsresult rv = nsImportStringBundle::GetStringBundle( + IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle)); + if (NS_FAILED(rv)) + IMPORT_LOG0("Failed to get string bundle for Importing Mail"); +} + +nsImportService::~nsImportService() { + IMPORT_LOG0("* nsImport Service Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsImportService, nsIImportService) + +NS_IMETHODIMP nsImportService::DiscoverModules(void) { + m_didDiscovery = false; + return DoDiscover(); +} + +NS_IMETHODIMP nsImportService::CreateNewFieldMap(nsIImportFieldMap** _retval) { + return nsImportFieldMap::Create(m_stringBundle, NS_GET_IID(nsIImportFieldMap), + (void**)_retval); +} + +NS_IMETHODIMP nsImportService::CreateNewMailboxDescriptor( + nsIImportMailboxDescriptor** _retval) { + return nsImportMailboxDescriptor::Create( + NS_GET_IID(nsIImportMailboxDescriptor), (void**)_retval); +} + +NS_IMETHODIMP nsImportService::CreateNewABDescriptor( + nsIImportABDescriptor** _retval) { + return nsImportABDescriptor::Create(NS_GET_IID(nsIImportABDescriptor), + (void**)_retval); +} + +extern nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric); + +NS_IMETHODIMP nsImportService::CreateNewGenericMail( + nsIImportGeneric** _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + return NS_NewGenericMail(_retval); +} + +extern nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric); + +NS_IMETHODIMP nsImportService::CreateNewGenericAddressBooks( + nsIImportGeneric** _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + return NS_NewGenericAddressBooks(_retval); +} + +NS_IMETHODIMP nsImportService::GetModuleCount(const char* filter, + int32_t* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + DoDiscover(); + + nsCString filterStr(filter); + int32_t count = 0; + for (auto& importModule : m_importModules) { + if (importModule.SupportsThings(filterStr)) count++; + } + *_retval = count; + + return NS_OK; +} + +ImportModuleDesc* nsImportService::GetImportModule(const char* filter, + int32_t index) { + DoDiscover(); + + nsCString filterStr(filter); + int32_t count = 0; + for (auto& importModule : m_importModules) { + if (importModule.SupportsThings(filterStr)) { + if (count++ == index) { + return &importModule; + } + } + } + + return nullptr; +} + +NS_IMETHODIMP nsImportService::GetModuleInfo(const char* filter, int32_t index, + nsAString& name, + nsAString& moduleDescription) { + ImportModuleDesc* importModule = GetImportModule(filter, index); + if (!importModule) return NS_ERROR_FAILURE; + + name = importModule->GetName(); + moduleDescription = importModule->GetDescription(); + return NS_OK; +} + +NS_IMETHODIMP nsImportService::GetModuleName(const char* filter, int32_t index, + nsAString& _retval) { + ImportModuleDesc* importModule = GetImportModule(filter, index); + if (!importModule) return NS_ERROR_FAILURE; + + _retval = importModule->GetName(); + return NS_OK; +} + +NS_IMETHODIMP nsImportService::GetModuleDescription(const char* filter, + int32_t index, + nsAString& _retval) { + ImportModuleDesc* importModule = GetImportModule(filter, index); + if (!importModule) return NS_ERROR_FAILURE; + + _retval = importModule->GetDescription(); + return NS_OK; +} + +class nsProxySendRunnable : public mozilla::Runnable { + public: + nsProxySendRunnable( + nsIMsgIdentity* aIdentity, nsIMsgCompFields* aMsgFields, + const char* attachment1_type, const nsACString& attachment1_body, + bool aIsDraft, + nsTArray> const& aLoadedAttachments, + nsTArray> const& aEmbeddedAttachments, + nsIMsgSendListener* aListener); + NS_DECL_NSIRUNNABLE + private: + nsCOMPtr m_identity; + nsCOMPtr m_compFields; + bool m_isDraft; + nsCString m_bodyType; + nsCString m_body; + nsTArray> m_loadedAttachments; + nsTArray> m_embeddedAttachments; + nsCOMPtr m_listener; +}; + +nsProxySendRunnable::nsProxySendRunnable( + nsIMsgIdentity* aIdentity, nsIMsgCompFields* aMsgFields, + const char* aBodyType, const nsACString& aBody, bool aIsDraft, + nsTArray> const& aLoadedAttachments, + nsTArray> const& aEmbeddedAttachments, + nsIMsgSendListener* aListener) + : mozilla::Runnable("nsProxySendRunnable"), + m_identity(aIdentity), + m_compFields(aMsgFields), + m_isDraft(aIsDraft), + m_bodyType(aBodyType), + m_body(aBody), + m_loadedAttachments(aLoadedAttachments.Clone()), + m_embeddedAttachments(aEmbeddedAttachments.Clone()), + m_listener(aListener) {} + +NS_IMETHODIMP nsProxySendRunnable::Run() { + nsresult rv; + nsCOMPtr msgSend = + do_CreateInstance("@mozilla.org/messengercompose/send;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return msgSend->CreateRFC822Message( + m_identity, m_compFields, m_bodyType.get(), m_body, m_isDraft, + m_loadedAttachments, m_embeddedAttachments, m_listener); +} + +NS_IMETHODIMP +nsImportService::CreateRFC822Message( + nsIMsgIdentity* aIdentity, nsIMsgCompFields* aMsgFields, + const char* aBodyType, const nsACString& aBody, bool aIsDraft, + nsTArray> const& aLoadedAttachments, + nsTArray> const& aEmbeddedAttachments, + nsIMsgSendListener* aListener) { + RefPtr runnable = new nsProxySendRunnable( + aIdentity, aMsgFields, aBodyType, aBody, aIsDraft, aLoadedAttachments, + aEmbeddedAttachments, aListener); + // invoke the callback + return NS_DispatchToMainThread(runnable); +} + +NS_IMETHODIMP nsImportService::GetModule(const char* filter, int32_t index, + nsIImportModule** _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + *_retval = nullptr; + + ImportModuleDesc* importModule = GetImportModule(filter, index); + if (!importModule) return NS_ERROR_FAILURE; + + nsCOMPtr modulePtr = importModule->GetModule(); + modulePtr.forget(_retval); + return NS_OK; +} + +nsresult nsImportService::DoDiscover(void) { + if (m_didDiscovery) return NS_OK; + + m_importModules.Clear(); + + nsresult rv; + + nsCOMPtr catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr e; + rv = catMan->EnumerateCategory("mailnewsimport", getter_AddRefs(e)); + NS_ENSURE_SUCCESS(rv, rv); + for (auto& key : mozilla::SimpleEnumerator(e)) { + nsCString keyStr; + key->ToString(getter_Copies(keyStr)); + nsCString contractIdStr; + rv = catMan->GetCategoryEntry("mailnewsimport", keyStr, contractIdStr); + if (NS_SUCCEEDED(rv)) LoadModuleInfo(contractIdStr); + } + + m_didDiscovery = true; + + return NS_OK; +} + +nsresult nsImportService::LoadModuleInfo(const nsCString& contractId) { + // load the component and get all of the info we need from it.... + nsresult rv; + nsCOMPtr module = do_CreateInstance(contractId.get(), &rv); + if (NS_FAILED(rv)) return rv; + + m_importModules.EmplaceBack(module); + + return NS_OK; +} + +ImportModuleDesc::ImportModuleDesc(nsIImportModule* importModule) + : m_pModule(importModule) { + nsresult rv; + rv = importModule->GetName(getter_Copies(m_name)); + if (NS_FAILED(rv)) m_name.AssignLiteral("Unknown"); + + rv = importModule->GetDescription(getter_Copies(m_description)); + if (NS_FAILED(rv)) m_description.AssignLiteral("Unknown description"); + + importModule->GetSupports(getter_Copies(m_supports)); + +#ifdef IMPORT_DEBUG + IMPORT_LOG3("* nsImportService registered import module: %s, %s, %s\n", + NS_LossyConvertUTF16toASCII(m_name).get(), + NS_LossyConvertUTF16toASCII(m_description).get(), + m_supports.get()); +#endif +} + +bool ImportModuleDesc::SupportsThings(const nsACString& thing) { + for (auto& item : m_supports.Split(',')) { + if (item == thing) return true; + } + return false; +} diff --git a/comm/mailnews/import/src/nsImportService.h b/comm/mailnews/import/src/nsImportService.h new file mode 100644 index 0000000000..37dd95b935 --- /dev/null +++ b/comm/mailnews/import/src/nsImportService.h @@ -0,0 +1,54 @@ +/* -*- 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 nsImportService_h__ +#define nsImportService_h__ + +#include "nsString.h" +#include "nsMemory.h" +#include "nsIImportModule.h" +#include "nsIImportService.h" +#include "nsIStringBundle.h" +#include "nsTArray.h" + +class ImportModuleDesc { + public: + explicit ImportModuleDesc(nsIImportModule* importModule); + + const nsAString& GetName(void) { return m_name; } + const nsAString& GetDescription(void) { return m_description; } + + nsCOMPtr& GetModule() { return m_pModule; } + + bool SupportsThings(const nsACString& pThings); + + private: + nsString m_name; + nsString m_description; + nsCString m_supports; + nsCOMPtr m_pModule; +}; + +class nsImportService : public nsIImportService { + public: + nsImportService(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMPORTSERVICE + + private: + virtual ~nsImportService(); + nsresult LoadModuleInfo(const nsCString& contractId); + nsresult DoDiscover(void); + ImportModuleDesc* GetImportModule(const char* filter, int32_t index); + + private: + AutoTArray m_importModules; + bool m_didDiscovery; + nsCString m_sysCharset; + nsCOMPtr m_stringBundle; +}; + +#endif // nsImportService_h__ diff --git a/comm/mailnews/import/src/nsImportStringBundle.cpp b/comm/mailnews/import/src/nsImportStringBundle.cpp new file mode 100644 index 0000000000..0ef79bfcfc --- /dev/null +++ b/comm/mailnews/import/src/nsImportStringBundle.cpp @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +#include "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" +#include "nsImportStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Components.h" + +nsresult nsImportStringBundle::GetStringBundle(const char* aPropertyURL, + nsIStringBundle** aBundle) { + nsresult rv; + + nsCOMPtr sBundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + rv = sBundleService->CreateBundle(aPropertyURL, aBundle); + + return rv; +} + +void nsImportStringBundle::GetStringByID(int32_t aStringID, + nsIStringBundle* aBundle, + nsString& aResult) { + aResult.Adopt(GetStringByID(aStringID, aBundle)); +} + +char16_t* nsImportStringBundle::GetStringByID(int32_t aStringID, + nsIStringBundle* aBundle) { + if (aBundle) { + nsAutoString str; + nsresult rv = aBundle->GetStringFromID(aStringID, str); + if (NS_SUCCEEDED(rv)) return ToNewUnicode(str); + } + + nsString resultString(u"[StringID "_ns); + resultString.AppendInt(aStringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsImportStringBundle::GetStringByName(const char* aName, + nsIStringBundle* aBundle, + nsString& aResult) { + aResult.Adopt(GetStringByName(aName, aBundle)); +} + +char16_t* nsImportStringBundle::GetStringByName(const char* aName, + nsIStringBundle* aBundle) { + if (aBundle) { + nsAutoString str; + nsresult rv = aBundle->GetStringFromName(aName, str); + if (NS_SUCCEEDED(rv)) return ToNewUnicode(str); + } + + nsString resultString(u"[StringName "_ns); + resultString.Append(NS_ConvertUTF8toUTF16(aName).get()); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} diff --git a/comm/mailnews/import/src/nsImportStringBundle.h b/comm/mailnews/import/src/nsImportStringBundle.h new file mode 100644 index 0000000000..ba234888fc --- /dev/null +++ b/comm/mailnews/import/src/nsImportStringBundle.h @@ -0,0 +1,43 @@ +/* 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 _nsImportStringBundle_H__ +#define _nsImportStringBundle_H__ + +#include "nsString.h" + +class nsIStringBundle; + +class nsImportStringBundle { + public: + static char16_t* GetStringByID(int32_t aStringID, + nsIStringBundle* aBundle = nullptr); + static void GetStringByID(int32_t aStringID, nsIStringBundle* aBundle, + nsString& aResult); + static char16_t* GetStringByName(const char* aName, + nsIStringBundle* aBundle = nullptr); + static void GetStringByName(const char* aName, nsIStringBundle* aBundle, + nsString& aResult); + static nsresult GetStringBundle(const char* aPropertyURL, + nsIStringBundle** aBundle); +}; + +#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties" + +#define IMPORT_NO_ADDRBOOKS 2000 +#define IMPORT_ERROR_AB_NOTINITIALIZED 2001 +#define IMPORT_ERROR_AB_NOTHREAD 2002 +#define IMPORT_ERROR_GETABOOK 2003 +#define IMPORT_NO_MAILBOXES 2004 +#define IMPORT_ERROR_MB_NOTINITIALIZED 2005 +#define IMPORT_ERROR_MB_NOTHREAD 2006 +#define IMPORT_ERROR_MB_NOPROXY 2007 +#define IMPORT_ERROR_MB_FINDCHILD 2008 +#define IMPORT_ERROR_MB_CREATE 2009 +#define IMPORT_ERROR_MB_NODESTFOLDER 2010 + +#define IMPORT_FIELD_DESC_START 2100 +#define IMPORT_FIELD_DESC_END 2136 + +#endif /* _nsImportStringBundle_H__ */ diff --git a/comm/mailnews/import/src/nsImportTranslator.cpp b/comm/mailnews/import/src/nsImportTranslator.cpp new file mode 100644 index 0000000000..f988e7035d --- /dev/null +++ b/comm/mailnews/import/src/nsImportTranslator.cpp @@ -0,0 +1,308 @@ +/* -*- 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/. */ + +#include "ImportOutFile.h" +#include "nsImportTranslator.h" + +#include "ImportCharSet.h" + +bool nsImportTranslator::ConvertToFile(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed) { + if (pProcessed) *pProcessed = inLen; + return (pOutFile->WriteData(pIn, inLen)); +} + +void CMHTranslator::ConvertBuffer(const uint8_t* pIn, uint32_t inLen, + uint8_t* pOut) { + while (inLen) { + if (!ImportCharSet::IsUSAscii(*pIn) || + ImportCharSet::Is822SpecialChar(*pIn) || + ImportCharSet::Is822CtlChar(*pIn) || + (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || + (*pIn == '\'') || (*pIn == '%')) { + // needs to be encode as %hex val + *pOut = '%'; + pOut++; + ImportCharSet::ByteToHex(*pIn, pOut); + pOut += 2; + } else { + *pOut = *pIn; + pOut++; + } + pIn++; + inLen--; + } + *pOut = 0; +} + +bool CMHTranslator::ConvertToFile(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed) { + uint8_t hex[2]; + while (inLen) { + if (!ImportCharSet::IsUSAscii(*pIn) || + ImportCharSet::Is822SpecialChar(*pIn) || + ImportCharSet::Is822CtlChar(*pIn) || + (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || + (*pIn == '\'') || (*pIn == '%')) { + // needs to be encode as %hex val + if (!pOutFile->WriteByte('%')) return false; + ImportCharSet::ByteToHex(*pIn, hex); + if (!pOutFile->WriteData(hex, 2)) return false; + } else { + if (!pOutFile->WriteByte(*pIn)) return false; + } + pIn++; + inLen--; + } + + if (pProcessed) *pProcessed = inLen; + + return true; +} + +bool C2047Translator::ConvertToFileQ(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed) { + if (!inLen) return true; + + int maxLineLen = 64; + int curLineLen = m_startLen; + bool startLine = true; + + uint8_t hex[2]; + while (inLen) { + if (startLine) { + if (!pOutFile->WriteStr(" =?")) return false; + if (!pOutFile->WriteStr(m_charset.get())) return false; + if (!pOutFile->WriteStr("?q?")) return false; + curLineLen += (6 + m_charset.Length()); + startLine = false; + } + + if (!ImportCharSet::IsUSAscii(*pIn) || + ImportCharSet::Is822SpecialChar(*pIn) || + ImportCharSet::Is822CtlChar(*pIn) || + (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '?') || (*pIn == '=')) { + // needs to be encode as =hex val + if (!pOutFile->WriteByte('=')) return false; + ImportCharSet::ByteToHex(*pIn, hex); + if (!pOutFile->WriteData(hex, 2)) return false; + curLineLen += 3; + } else { + if (!pOutFile->WriteByte(*pIn)) return false; + curLineLen++; + } + pIn++; + inLen--; + if (curLineLen > maxLineLen) { + if (!pOutFile->WriteStr("?=")) return false; + if (inLen) { + if (!pOutFile->WriteStr("\x0D\x0A ")) return false; + } + + startLine = true; + curLineLen = 0; + } + } + + if (!startLine) { + // end the encoding! + if (!pOutFile->WriteStr("?=")) return false; + } + + if (pProcessed) *pProcessed = inLen; + + return true; +} + +bool C2047Translator::ConvertToFile(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed) { + if (m_useQuotedPrintable) + return ConvertToFileQ(pIn, inLen, pOutFile, pProcessed); + + if (!inLen) return true; + + int maxLineLen = 64; + int curLineLen = m_startLen; + bool startLine = true; + int encodeMax; + uint8_t* pEncoded = new uint8_t[maxLineLen * 2]; + + while (inLen) { + if (startLine) { + if (!pOutFile->WriteStr(" =?")) { + delete[] pEncoded; + return false; + } + if (!pOutFile->WriteStr(m_charset.get())) { + delete[] pEncoded; + return false; + } + if (!pOutFile->WriteStr("?b?")) { + delete[] pEncoded; + return false; + } + curLineLen += (6 + m_charset.Length()); + startLine = false; + } + encodeMax = maxLineLen - curLineLen; + encodeMax *= 3; + encodeMax /= 4; + if ((uint32_t)encodeMax > inLen) encodeMax = (int)inLen; + + // encode the line, end the line + // then continue. Update curLineLen, pIn, startLine, and inLen + UMimeEncode::ConvertBuffer(pIn, encodeMax, pEncoded, maxLineLen, maxLineLen, + "\x0D\x0A"); + + if (!pOutFile->WriteStr((const char*)pEncoded)) { + delete[] pEncoded; + return false; + } + + pIn += encodeMax; + inLen -= encodeMax; + startLine = true; + curLineLen = 0; + if (!pOutFile->WriteStr("?=")) { + delete[] pEncoded; + return false; + } + if (inLen) { + if (!pOutFile->WriteStr("\x0D\x0A ")) { + delete[] pEncoded; + return false; + } + } + } + + delete[] pEncoded; + + if (pProcessed) *pProcessed = inLen; + + return true; +} + +uint32_t UMimeEncode::GetBufferSize(uint32_t inBytes) { + // it takes 4 base64 bytes to represent 3 regular bytes + inBytes += 3; + inBytes /= 3; + inBytes *= 4; + // This should be plenty, but just to be safe + inBytes += 4; + + // now allow for end of line characters + inBytes += ((inBytes + 39) / 40) * 4; + + return inBytes; +} + +static uint8_t gBase64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +uint32_t UMimeEncode::ConvertBuffer(const uint8_t* pIn, uint32_t inLen, + uint8_t* pOut, uint32_t maxLen, + uint32_t firstLineLen, + const char* pEolStr) { + uint32_t pos = 0; + uint32_t len = 0; + uint32_t lineLen = 0; + uint32_t maxLine = firstLineLen; + int eolLen = 0; + if (pEolStr) eolLen = strlen(pEolStr); + + while ((pos + 2) < inLen) { + // Encode 3 bytes + *pOut = gBase64[*pIn >> 2]; + pOut++; + len++; + lineLen++; + *pOut = gBase64[(((*pIn) & 0x3) << 4) | (((*(pIn + 1)) & 0xF0) >> 4)]; + pIn++; + pOut++; + len++; + lineLen++; + *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >> 6)]; + pIn++; + pOut++; + len++; + lineLen++; + *pOut = gBase64[(*pIn) & 0x3F]; + pIn++; + pOut++; + len++; + lineLen++; + pos += 3; + if (lineLen >= maxLine) { + lineLen = 0; + maxLine = maxLen; + if (pEolStr) { + memcpy(pOut, pEolStr, eolLen); + pOut += eolLen; + len += eolLen; + } + } + } + + if ((pos < inLen) && ((lineLen + 3) > maxLine)) { + lineLen = 0; + maxLine = maxLen; + if (pEolStr) { + memcpy(pOut, pEolStr, eolLen); + pOut += eolLen; + len += eolLen; + } + } + + if (pos < inLen) { + // Get the last few bytes! + *pOut = gBase64[*pIn >> 2]; + pOut++; + len++; + pos++; + if (pos < inLen) { + *pOut = gBase64[(((*pIn) & 0x3) << 4) | (((*(pIn + 1)) & 0xF0) >> 4)]; + pIn++; + pOut++; + pos++; + len++; + if (pos < inLen) { + // Should be dead code!! (Then why is it here doofus?) + *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >> 6)]; + pIn++; + pOut++; + len++; + *pOut = gBase64[(*pIn) & 0x3F]; + pos++; + pOut++; + len++; + } else { + *pOut = gBase64[(((*pIn) & 0xF) << 2)]; + pOut++; + len++; + *pOut = '='; + pOut++; + len++; + } + } else { + *pOut = gBase64[(((*pIn) & 0x3) << 4)]; + pOut++; + len++; + *pOut = '='; + pOut++; + len++; + *pOut = '='; + pOut++; + len++; + } + } + + *pOut = 0; + + return len; +} diff --git a/comm/mailnews/import/src/nsImportTranslator.h b/comm/mailnews/import/src/nsImportTranslator.h new file mode 100644 index 0000000000..c475700ad6 --- /dev/null +++ b/comm/mailnews/import/src/nsImportTranslator.h @@ -0,0 +1,87 @@ +/* -*- 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 nsImportTranslator_h___ +#define nsImportTranslator_h___ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +class ImportOutFile; + +class UMimeEncode { + public: + static uint32_t GetBufferSize(uint32_t inByes); + static uint32_t ConvertBuffer(const uint8_t* pIn, uint32_t inLen, + uint8_t* pOut, uint32_t maxLen = 72, + uint32_t firstLineLen = 72, + const char* pEolStr = nullptr); +}; + +class nsImportTranslator { + public: + virtual ~nsImportTranslator() {} + virtual bool Supports8bitEncoding(void) { return false; } + virtual uint32_t GetMaxBufferSize(uint32_t inLen) { return inLen + 1; } + virtual void ConvertBuffer(const uint8_t* pIn, uint32_t inLen, + uint8_t* pOut) { + memcpy(pOut, pIn, inLen); + pOut[inLen] = 0; + } + virtual bool ConvertToFile(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed = nullptr); + virtual bool FinishConvertToFile(ImportOutFile* /* pOutFile */) { + return true; + } + + virtual void GetCharset(nsCString& charSet) { charSet = "us-ascii"; } + virtual void GetLanguage(nsCString& lang) { lang = "en"; } + virtual void GetEncoding(nsCString& encoding) { encoding.Truncate(); } +}; + +// Specialized encoder, not a valid language translator, used for Mime headers. +// rfc2231 +class CMHTranslator : public nsImportTranslator { + public: + virtual uint32_t GetMaxBufferSize(uint32_t inLen) override { + return (inLen * 3) + 1; + } + virtual void ConvertBuffer(const uint8_t* pIn, uint32_t inLen, + uint8_t* pOut) override; + virtual bool ConvertToFile(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed = nullptr) override; +}; + +// Specialized encoder, not a valid language translator, used for mail headers +// rfc2047 +class C2047Translator : public nsImportTranslator { + public: + virtual ~C2047Translator() {} + + C2047Translator(const char* pCharset, uint32_t headerLen) { + m_charset = pCharset; + m_startLen = headerLen; + m_useQuotedPrintable = false; + } + + void SetUseQuotedPrintable(void) { m_useQuotedPrintable = true; } + + virtual bool ConvertToFile(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, + uint32_t* pProcessed = nullptr) override; + bool ConvertToFileQ(const uint8_t* pIn, uint32_t inLen, + ImportOutFile* pOutFile, uint32_t* pProcessed); + + protected: + bool m_useQuotedPrintable; + nsCString m_charset; + uint32_t m_startLen; +}; + +#endif /* nsImportTranslator_h__ */ diff --git a/comm/mailnews/import/src/nsOutlookCompose.cpp b/comm/mailnews/import/src/nsOutlookCompose.cpp new file mode 100644 index 0000000000..1098dcbab7 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookCompose.cpp @@ -0,0 +1,669 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "prthread.h" +#include "nsString.h" +#include "nsMsgUtils.h" +#include "nsUnicharUtils.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIURI.h" +#include "nsMsgI18N.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsMsgAttachmentData.h" +#include "nsIMsgCompFields.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgSend.h" +#include "nsImportEmbeddedImageData.h" +#include "nsNetCID.h" +#include "nsCRT.h" +#include "nsOutlookCompose.h" +#include "nsTArray.h" + +#include "ImportDebug.h" + +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" + +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include "nsMsgMessageFlags.h" +#include "nsMsgLocalFolderHdrs.h" + +#define NS_MSGCOMPFIELDS_CID \ + { /* e64b0f51-0d7b-4e2f-8c60-3862ee8c174f */ \ + 0xe64b0f51, 0x0d7b, 0x4e2f, { \ + 0x8c, 0x60, 0x38, 0x62, 0xee, 0x8c, 0x17, 0x4f \ + } \ + } +static NS_DEFINE_CID(kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID); + +#ifdef IMPORT_DEBUG +static const char* p_test_headers = + "Received: from netppl.invalid (IDENT:monitor@get.freebsd.because.microsoftsucks.invalid [209.3.31.115])\n\ + by mail4.sirius.invalid (8.9.1/8.9.1) with SMTP id PAA27232;\n\ + Mon, 17 May 1999 15:27:43 -0700 (PDT)\n\ +Message-ID: \n\ +From: \"adsales@qualityservice.invalid\" \n\ +Subject: Re: Your College Diploma (36822)\n\ +Date: Mon, 17 May 1999 15:09:29 -0400 (EDT)\n\ +MIME-Version: 1.0\n\ +Content-Type: TEXT/PLAIN; charset=\"US-ASCII\"\n\ +Content-Transfer-Encoding: 7bit\n\ +X-UIDL: 19990517.152941\n\ +Status: RO"; + +static const char* p_test_body = + "Hello world?\n\ +"; +#else +# define p_test_headers nullptr +# define p_test_body nullptr +#endif + +#define kWhitespace "\b\t\r\n " + +////////////////////////////////////////////////////////////////////////////////////////////////// + +// A replacement for SimpleBufferTonyRCopiedTwice round-robin buffer and +// ReadFileState classes +class CCompositionFile { + public: + // fifoBuffer is used for memory allocation optimization + // convertCRs controls if we want to convert standalone CRs to CRLFs + CCompositionFile(nsIFile* aFile, void* fifoBuffer, uint32_t fifoBufferSize, + bool convertCRs = false); + + explicit operator bool() const { return m_fileSize && m_pInputStream; } + + // Reads up to and including the term sequence, or entire file if term isn't + // found termSize may be used to include NULLs in the terminator sequences. + // termSize value of -1 means "zero-terminated string" -> size is calculated + // with strlen + nsresult ToString(nsCString& dest, const char* term = 0, int termSize = -1); + nsresult ToStream(nsIOutputStream* dest, const char* term = 0, + int termSize = -1); + char LastChar() { return m_lastChar; } + + private: + nsCOMPtr m_pFile; + nsCOMPtr m_pInputStream; + int64_t m_fileSize; + int64_t m_fileReadPos; + char* m_fifoBuffer; + uint32_t m_fifoBufferSize; + char* m_fifoBufferReadPos; // next character to read + char* m_fifoBufferWrittenPos; // if we have read less than buffer size then + // this will show it + bool m_convertCRs; + char m_lastChar; + + nsresult EnsureHasDataInBuffer(); + template + nsresult ToDest(_OutFn dest, const char* term, int termSize); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +// First off, a listener +class OutlookSendListener : public nsIMsgSendListener { + public: + OutlookSendListener() { m_done = false; } + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */ + NS_IMETHOD OnStartSending(const char* aMsgID, uint32_t aMsgSize) { + return NS_OK; + } + + /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t + * aProgressMax); */ + NS_IMETHOD OnProgress(const char* aMsgID, uint32_t aProgress, + uint32_t aProgressMax) { + return NS_OK; + } + + /* void OnStatus (in string aMsgID, in wstring aMsg); */ + NS_IMETHOD OnStatus(const char* aMsgID, const char16_t* aMsg) { + return NS_OK; + } + + /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, + * in nsIFile returnFile); */ + NS_IMETHOD OnStopSending(const char* aMsgID, nsresult aStatus, + const char16_t* aMsg, nsIFile* returnFile) { + m_done = true; + m_location = returnFile; + return NS_OK; + } + + /* void OnTransportSecurityError( in string msgID, in nsresult status, in + * nsITransportSecurityInfo secInfo, in ACString location); */ + NS_IMETHOD OnTransportSecurityError(const char* msgID, nsresult status, + nsITransportSecurityInfo* secInfo, + nsACString const& location) { + return NS_OK; + } + + /* void OnSendNotPerformed */ + NS_IMETHOD OnSendNotPerformed(const char* aMsgID, nsresult aStatus) { + return NS_OK; + } + + /* void OnGetDraftFolderURI (); */ + NS_IMETHOD OnGetDraftFolderURI(const char* aMsgID, + const nsACString& aFolderURI) { + return NS_OK; + } + + static nsresult CreateSendListener(nsIMsgSendListener** ppListener); + void Reset() { + m_done = false; + m_location = nullptr; + } + + public: + virtual ~OutlookSendListener() {} + + bool m_done; + nsCOMPtr m_location; +}; + +NS_IMPL_ISUPPORTS(OutlookSendListener, nsIMsgSendListener) + +nsresult OutlookSendListener::CreateSendListener( + nsIMsgSendListener** ppListener) { + NS_ENSURE_ARG_POINTER(ppListener); + NS_ADDREF(*ppListener = new OutlookSendListener()); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// + +nsOutlookCompose::nsOutlookCompose() { + m_optimizationBuffer = new char[FILE_IO_BUFFER_SIZE]; +} + +nsOutlookCompose::~nsOutlookCompose() { + if (m_pIdentity) { + nsresult rv = m_pIdentity->ClearAllValues(); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to clear values"); + if (NS_FAILED(rv)) return; + } + delete[] m_optimizationBuffer; +} + +nsCOMPtr nsOutlookCompose::m_pIdentity = nullptr; + +nsresult nsOutlookCompose::CreateIdentity(void) { + if (m_pIdentity) return NS_OK; + + nsresult rv; + nsCOMPtr accMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = accMgr->CreateIdentity(getter_AddRefs(m_pIdentity)); + nsString name; + name.AssignLiteral("Import Identity"); + if (m_pIdentity) { + m_pIdentity->SetFullName(name); + m_pIdentity->SetEmail("import@service.invalid"_ns); + } + return rv; +} + +void nsOutlookCompose::ReleaseIdentity() { m_pIdentity = nullptr; } + +nsresult nsOutlookCompose::CreateComponents(void) { + nsresult rv = NS_OK; + + m_pMsgFields = nullptr; + if (!m_pListener) + rv = OutlookSendListener::CreateSendListener(getter_AddRefs(m_pListener)); + + if (NS_SUCCEEDED(rv)) { + m_pMsgFields = do_CreateInstance(kMsgCompFieldsCID, &rv); + if (NS_SUCCEEDED(rv) && m_pMsgFields) { + // IMPORT_LOG0("nsOutlookCompose - CreateComponents succeeded\n"); + m_pMsgFields->SetForcePlainText(false); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +nsresult nsOutlookCompose::ComposeTheMessage(nsMsgDeliverMode mode, + CMapiMessage& msg, + nsIFile** pMsg) { + nsresult rv = CreateComponents(); + NS_ENSURE_SUCCESS(rv, rv); + rv = CreateIdentity(); + NS_ENSURE_SUCCESS(rv, rv); + + // IMPORT_LOG0("Outlook Compose created necessary components\n"); + + CMapiMessageHeaders* headers = msg.GetHeaders(); + + nsString unival; + headers->UnfoldValue(CMapiMessageHeaders::hdrFrom, unival, + msg.GetBodyCharset()); + m_pMsgFields->SetFrom(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrTo, unival, + msg.GetBodyCharset()); + m_pMsgFields->SetTo(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrSubject, unival, + msg.GetBodyCharset()); + m_pMsgFields->SetSubject(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrCc, unival, + msg.GetBodyCharset()); + m_pMsgFields->SetCc(unival); + headers->UnfoldValue(CMapiMessageHeaders::hdrReplyTo, unival, + msg.GetBodyCharset()); + m_pMsgFields->SetReplyTo(unival); + m_pMsgFields->SetMessageId(headers->Value(CMapiMessageHeaders::hdrMessageID)); + + // We only use those headers that may need to be processed by Thunderbird + // to create a good rfc822 document, or need to be encoded (like To and Cc). + // These will replace the originals on import. All the other headers + // will be copied to the destination unaltered in CopyComposedMessage(). + + nsTArray> attachments; + msg.GetAttachments(attachments); + + nsString bodyW; + bodyW = msg.GetBody(); + + nsTArray> embeddedObjects; + + if (msg.BodyIsHtml()) { + for (unsigned int i = 0; i < msg.EmbeddedAttachmentsCount(); i++) { + nsIURI* uri; + const char* cid; + const char* name; + if (msg.GetEmbeddedAttachmentInfo(i, &uri, &cid, &name)) { + nsCOMPtr imageData = + new nsImportEmbeddedImageData(uri, nsDependentCString(cid), + nsDependentCString(name)); + embeddedObjects.AppendElement(imageData); + } + } + } + + nsCString bodyA; + const char* charset = msg.GetBodyCharset(); + nsMsgI18NConvertFromUnicode( + charset ? nsDependentCString(charset) : EmptyCString(), bodyW, bodyA); + + nsCOMPtr impService( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIImportService::CreateRFC822Message creates a runnable and dispatches to + // the main thread. + rv = impService->CreateRFC822Message( + m_pIdentity, // dummy identity + m_pMsgFields, // message fields + msg.BodyIsHtml() ? "text/html" : "text/plain", + bodyA, // body pointer + mode == nsIMsgSend::nsMsgSaveAsDraft, + attachments, // local attachments + embeddedObjects, + m_pListener); // listener + + OutlookSendListener* pListen = + static_cast(m_pListener.get()); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** Error, CreateAndSendMessage FAILED: 0x%x\n", rv); + } else { + // Wait for the listener to get done. + nsCOMPtr thread(do_GetCurrentThread()); + while (!pListen->m_done) { + NS_ProcessNextEvent(thread, true); + } + } + + if (pListen->m_location) { + pListen->m_location->Clone(pMsg); + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + IMPORT_LOG0("*** Error, Outlook compose unsuccessful\n"); + } + + pListen->Reset(); + return rv; +} + +nsresult nsOutlookCompose::CopyComposedMessage(nsIFile* pSrc, + nsIOutputStream* pDst, + CMapiMessage& origMsg) { + // I'm unsure if we really need the convertCRs feature here. + // The headers in the file are generated by TB, the body was generated by rtf + // reader that always used CRLF, and the attachments were processed by TB + // either... However, I let it stay as it was in the original code. + CCompositionFile f(pSrc, m_optimizationBuffer, FILE_IO_BUFFER_SIZE, true); + if (!f) { + IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n"); + return NS_ERROR_FAILURE; + } + + // The "From ..." separates the messages. Without it, TB cannot see the + // messages in the mailbox file. Thus, the lines that look like "From ..." in + // the message must be escaped (see EscapeFromSpaceLine()) + int fromLineLen; + const char* fromLine = origMsg.GetFromLine(fromLineLen); + uint32_t written; + nsresult rv = pDst->Write(fromLine, fromLineLen, &written); + + // Bug 219269 + // Write out the x-mozilla-status headers. + char statusLine[50]; + uint32_t msgFlags = 0; + if (origMsg.IsRead()) msgFlags |= nsMsgMessageFlags::Read; + if (!origMsg.FullMessageDownloaded()) msgFlags |= nsMsgMessageFlags::Partial; + if (origMsg.IsForvarded()) msgFlags |= nsMsgMessageFlags::Forwarded; + if (origMsg.IsReplied()) msgFlags |= nsMsgMessageFlags::Replied; + if (origMsg.HasAttach()) msgFlags |= nsMsgMessageFlags::Attachment; + _snprintf(statusLine, sizeof(statusLine), + X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF); + rv = pDst->Write(statusLine, strlen(statusLine), &written); + _snprintf(statusLine, sizeof(statusLine), + X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000); + rv = pDst->Write(statusLine, strlen(statusLine), &written); + // End Bug 219269 + + // well, isn't this a hoot! + // Read the headers from the new message, get the ones we like + // and write out only the headers we want from the new message, + // along with all of the other headers from the "old" message! + + nsCString newHeadersStr; + rv = f.ToString(newHeadersStr, + MSG_LINEBREAK MSG_LINEBREAK); // Read all the headers + NS_ENSURE_SUCCESS(rv, rv); + UpdateHeaders(*origMsg.GetHeaders(), + CMapiMessageHeaders(newHeadersStr.get())); + rv = origMsg.GetHeaders()->ToStream(pDst); + NS_ENSURE_SUCCESS(rv, rv); + + // I use the terminating sequence here to avoid a possible situation when a + // "From " line gets split over two sequential reads and thus will not be + // escaped. This is done by reading up to CRLF (one line every time), though + // it may be slower + + // Here I revert the changes that were made when the multipart/related message + // was composed in nsMsgSend::ProcessMultipartRelated() - the Content-Ids of + // attachments were replaced with new ones. + nsCString line; + while (NS_SUCCEEDED(f.ToString(line, MSG_LINEBREAK))) { + EscapeFromSpaceLine(pDst, const_cast(line.get()), + line.get() + line.Length()); + } + + if (f.LastChar() != nsCRT::LF) { + rv = pDst->Write(MSG_LINEBREAK, 2, &written); + if (written != 2) rv = NS_ERROR_FAILURE; + } + + return rv; +} + +nsresult nsOutlookCompose::ProcessMessage(nsMsgDeliverMode mode, + CMapiMessage& msg, + nsIOutputStream* pDst) { + nsCOMPtr compositionFile; + nsresult rv = ComposeTheMessage(mode, msg, getter_AddRefs(compositionFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = CopyComposedMessage(compositionFile, pDst, msg); + compositionFile->Remove(false); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error copying composed message to destination mailbox\n"); + } + return rv; +} + +void nsOutlookCompose::UpdateHeader(CMapiMessageHeaders& oldHeaders, + const CMapiMessageHeaders& newHeaders, + CMapiMessageHeaders::SpecialHeader header, + bool addIfAbsent) { + const char* oldVal = oldHeaders.Value(header); + if (!addIfAbsent && !oldVal) return; + const char* newVal = newHeaders.Value(header); + if (!newVal) return; + // Bug 145150 - Turn "Content-Type: application/ms-tnef" into "Content-Type: + // text/plain" + // so the body text can be displayed normally (instead of in an + // attachment). + if (header == CMapiMessageHeaders::hdrContentType) + if (stricmp(newVal, "application/ms-tnef") == 0) newVal = "text/plain"; + // End Bug 145150 + if (oldVal) { + if (strcmp(oldVal, newVal) == 0) return; + // Backup the old header value + nsCString backupHdrName("X-MozillaBackup-"); + backupHdrName += CMapiMessageHeaders::SpecialName(header); + oldHeaders.SetValue(backupHdrName.get(), oldVal, false); + } + // Now replace it with new value + oldHeaders.SetValue(header, newVal); +} + +void nsOutlookCompose::UpdateHeaders(CMapiMessageHeaders& oldHeaders, + const CMapiMessageHeaders& newHeaders) { + // Well, ain't this a peach? + // This is rather disgusting but there really isn't much to be done about + // it.... + + // 1. For each "old" header, replace it with the new one if we want, + // then right it out. + // 2. Then if we haven't written the "important" new headers, write them out + // 3. Terminate the headers with an extra eol. + + // Important headers: + // "Content-type", + // "MIME-Version", + // "Content-transfer-encoding" + // consider "X-Accept-Language"? + + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentType); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrMimeVersion); + UpdateHeader(oldHeaders, newHeaders, + CMapiMessageHeaders::hdrContentTransferEncoding); + + // Other replaced headers (only if they exist): + // "From", + // "To", + // "Subject", + // "Reply-to", + // "Cc" + + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrFrom, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrTo, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrSubject, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrReplyTo, false); + UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrCc, false); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer, + uint32_t fifoBufferSize, bool convertCRs) + : m_pFile(aFile), + m_fileSize(0), + m_fileReadPos(0), + m_fifoBuffer(static_cast(fifoBuffer)), + m_fifoBufferSize(fifoBufferSize), + m_fifoBufferReadPos(static_cast(fifoBuffer)), + m_fifoBufferWrittenPos(static_cast(fifoBuffer)), + m_convertCRs(convertCRs), + m_lastChar(0) { + m_pFile->GetFileSize(&m_fileSize); + if (!m_fileSize) { + IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n"); + return; + } + + nsresult rv = + NS_NewLocalFileInputStream(getter_AddRefs(m_pInputStream), m_pFile); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error, unable to open composed message file\n"); + return; + } +} + +nsresult CCompositionFile::EnsureHasDataInBuffer() { + if (m_fifoBufferReadPos < m_fifoBufferWrittenPos) return NS_OK; + // Populate the buffer with new data! + uint32_t count = m_fifoBufferSize; + if ((m_fileReadPos + count) > m_fileSize) count = m_fileSize - m_fileReadPos; + if (!count) return NS_ERROR_FAILURE; // Isn't there a "No more data" error? + + uint32_t bytesRead = 0; + nsresult rv = m_pInputStream->Read(m_fifoBuffer, count, &bytesRead); + NS_ENSURE_SUCCESS(rv, rv); + if (!bytesRead || (bytesRead > count)) return NS_ERROR_FAILURE; + m_fifoBufferWrittenPos = m_fifoBuffer + bytesRead; + m_fifoBufferReadPos = m_fifoBuffer; + m_fileReadPos += bytesRead; + + return NS_OK; +} + +class CTermGuard { + public: + CTermGuard(const char* term, int termSize) + : m_term(term), + m_termSize(term ? ((termSize != -1) ? termSize : strlen(term)) : 0), + m_matchPos(0) {} + + // if the guard triggered + inline bool IsTriggered() const { + return m_termSize && (m_matchPos == m_termSize); + } + // indicates if the guard has something to check + inline bool IsChecking() const { return m_termSize; } + + bool Check(char c) // returns true only if the whole sequence passed + { + if (!m_termSize) // no guard + return false; + if (m_matchPos >= m_termSize) // check past success! + m_matchPos = 0; + if (m_term[m_matchPos] != c) // Reset sequence + m_matchPos = 0; + if (m_term[m_matchPos] == c) { // Sequence continues + return ++m_matchPos == m_termSize; // If equal then sequence complete! + } + // Sequence broken + return false; + } + + private: + const char* m_term; + int m_termSize; + int m_matchPos; +}; + +template +nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize) { + CTermGuard guard(term, termSize); + + // We already know the required string size, so reduce future reallocations + if (!guard.IsChecking() && !m_convertCRs) + dest.SetCapacity(m_fileSize - m_fileReadPos); + + bool wasCR = false; + char c = 0; + nsresult rv; + while (NS_SUCCEEDED(rv = EnsureHasDataInBuffer())) { + if (!guard.IsChecking() && !m_convertCRs) { // Use efficient algorithm + dest.Append(m_fifoBufferReadPos, + m_fifoBufferWrittenPos - m_fifoBufferReadPos); + } else { // Check character by character to convert CRs and find + // terminating sequence + while (m_fifoBufferReadPos < m_fifoBufferWrittenPos) { + c = *m_fifoBufferReadPos; + if (m_convertCRs && wasCR) { + wasCR = false; + if (c != nsCRT::LF) { + const char kTmpLF = nsCRT::LF; + dest.Append(&kTmpLF, 1); + if (guard.Check(nsCRT::LF)) { + c = nsCRT::LF; // save last char + break; + } + } + } + dest.Append(&c, 1); + m_fifoBufferReadPos++; + + if (guard.Check(c)) break; + + if (m_convertCRs && (c == nsCRT::CR)) wasCR = true; + } + if (guard.IsTriggered()) break; + } + } + + // check for trailing CR (only if caller didn't specify the terminating + // sequence that ends with CR - in this case he knows what he does!) + if (m_convertCRs && !guard.IsTriggered() && (c == nsCRT::CR)) { + c = nsCRT::LF; + dest.Append(&c, 1); + } + + NS_ENSURE_SUCCESS(rv, rv); + + m_lastChar = c; + return NS_OK; +} + +class dest_nsCString { + public: + explicit dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); } + void SetCapacity(int32_t sz) { m_str.SetCapacity(sz); } + nsresult Append(const char* buf, uint32_t count) { + m_str.Append(buf, count); + return NS_OK; + } + + private: + nsCString& m_str; +}; + +class dest_Stream { + public: + explicit dest_Stream(nsIOutputStream* dest) : m_stream(dest) {} + void SetCapacity(int32_t) { /*do nothing*/ + } + // const_cast here is due to the poor design of the EscapeFromSpaceLine() + // that requires a non-constant pointer while doesn't modify its data + nsresult Append(const char* buf, uint32_t count) { + return EscapeFromSpaceLine(m_stream, const_cast(buf), buf + count); + } + + private: + nsIOutputStream* m_stream; +}; + +nsresult CCompositionFile::ToString(nsCString& dest, const char* term, + int termSize) { + return ToDest(dest_nsCString(dest), term, termSize); +} + +nsresult CCompositionFile::ToStream(nsIOutputStream* dest, const char* term, + int termSize) { + return ToDest(dest_Stream(dest), term, termSize); +} diff --git a/comm/mailnews/import/src/nsOutlookCompose.h b/comm/mailnews/import/src/nsOutlookCompose.h new file mode 100644 index 0000000000..8d49157e68 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookCompose.h @@ -0,0 +1,63 @@ +/* -*- 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 nsOutlookCompose_h__ +#define nsOutlookCompose_h__ + +#include "nscore.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsIImportService.h" +#include "nsIOutputStream.h" + +class nsIMsgSend; +class nsIMsgCompFields; +class nsIMsgIdentity; +class nsIMsgSendListener; + +#include "nsIMsgSend.h" +#include "nsNetUtil.h" + +#include "MapiMessage.h" + +#include + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class nsOutlookCompose { + public: + nsOutlookCompose(); + ~nsOutlookCompose(); + + nsresult ProcessMessage(nsMsgDeliverMode mode, CMapiMessage& msg, + nsIOutputStream* pDst); + static nsresult CreateIdentity(void); + static void ReleaseIdentity(void); + + private: + nsresult CreateComponents(void); + + void UpdateHeader(CMapiMessageHeaders& oldHeaders, + const CMapiMessageHeaders& newHeaders, + CMapiMessageHeaders::SpecialHeader header, + bool addIfAbsent = true); + void UpdateHeaders(CMapiMessageHeaders& oldHeaders, + const CMapiMessageHeaders& newHeaders); + + nsresult ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage& msg, + nsIFile** pMsg); + nsresult CopyComposedMessage(nsIFile* pSrc, nsIOutputStream* pDst, + CMapiMessage& origMsg); + + private: + nsCOMPtr m_pListener; + nsCOMPtr m_pMsgFields; + static nsCOMPtr m_pIdentity; + char* m_optimizationBuffer; + nsCOMPtr m_pImportService; +}; + +#endif /* nsOutlookCompose_h__ */ diff --git a/comm/mailnews/import/src/nsOutlookImport.cpp b/comm/mailnews/import/src/nsOutlookImport.cpp new file mode 100644 index 0000000000..ceafa2d7a1 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookImport.cpp @@ -0,0 +1,522 @@ +/* -*- 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/. */ + +/* + Outlook (Win32) import mail and addressbook interfaces +*/ +#include "nscore.h" +#include "nsString.h" +#include "nsMsgUtils.h" +#include "nsIImportService.h" +#include "nsOutlookImport.h" +#include "nsIImportService.h" +#include "nsIImportMail.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIAbDirectory.h" +#include "nsOutlookSettings.h" +#include "nsTextFormatter.h" +#include "nsOutlookStringBundle.h" +#include "ImportDebug.h" +#include "nsUnicharUtils.h" + +#include "nsOutlookMail.h" + +#include "MapiApi.h" + +class ImportOutlookMailImpl : public nsIImportMail { + public: + ImportOutlookMailImpl(); + + static nsresult Create(nsIImportMail** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportmail interface + + /* void GetDefaultLocation (out nsIFile location, out boolean found, out + * boolean userVerify); */ + NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found, + bool* userVerify); + + NS_IMETHOD FindMailboxes(nsIFile* location, + nsTArray>& boxes); + + NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor* source, + nsIMsgFolder* dstFolder, char16_t** pErrorLog, + char16_t** pSuccessLog, bool* fatalError); + + /* unsigned long GetImportProgress (); */ + NS_IMETHOD GetImportProgress(uint32_t* _retval); + + NS_IMETHOD TranslateFolderName(const nsAString& aFolderName, + nsAString& _retval); + + public: + static void ReportSuccess(nsString& name, int32_t count, nsString* pStream); + static void ReportError(int32_t errorNum, nsString& name, nsString* pStream); + static void AddLinebreak(nsString* pStream); + static void SetLogs(nsString& success, nsString& error, char16_t** pError, + char16_t** pSuccess); + + private: + virtual ~ImportOutlookMailImpl(); + nsOutlookMail m_mail; + uint32_t m_bytesDone; +}; + +class ImportOutlookAddressImpl : public nsIImportAddressBooks { + public: + ImportOutlookAddressImpl(); + + static nsresult Create(nsIImportAddressBooks** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + NS_IMETHOD GetSupportsMultiple(bool* _retval) { + *_retval = true; + return NS_OK; + } + + NS_IMETHOD GetAutoFind(char16_t** description, bool* _retval); + + NS_IMETHOD GetNeedsFieldMap(nsIFile* location, bool* _retval) { + *_retval = false; + return NS_OK; + } + + NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found, + bool* userVerify) { + return NS_ERROR_FAILURE; + } + + NS_IMETHOD FindAddressBooks(nsIFile* location, + nsTArray>& books); + + NS_IMETHOD InitFieldMap(nsIImportFieldMap* fieldMap) { + return NS_ERROR_FAILURE; + } + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor* source, + nsIAbDirectory* destination, + nsIImportFieldMap* fieldMap, + nsISupports* aSupportService, + char16_t** errorLog, char16_t** successLog, + bool* fatalError); + + NS_IMETHOD GetImportProgress(uint32_t* _retval); + + NS_IMETHOD GetSampleData(int32_t index, bool* pFound, char16_t** pStr) { + return NS_ERROR_FAILURE; + } + + NS_IMETHOD SetSampleLocation(nsIFile*) { return NS_OK; } + + private: + virtual ~ImportOutlookAddressImpl(); + void ReportSuccess(nsString& name, nsString* pStream); + + private: + uint32_t m_msgCount; + uint32_t m_msgTotal; + nsOutlookMail m_address; +}; +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// + +nsOutlookImport::nsOutlookImport() { + IMPORT_LOG0("nsOutlookImport Module Created\n"); + + nsOutlookStringBundle::GetStringBundle(); +} + +nsOutlookImport::~nsOutlookImport() { + IMPORT_LOG0("nsOutlookImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsOutlookImport, nsIImportModule) + +NS_IMETHODIMP nsOutlookImport::GetName(char16_t** name) { + NS_ASSERTION(name != nullptr, "null ptr"); + if (!name) return NS_ERROR_NULL_POINTER; + + *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME); + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetDescription(char16_t** name) { + NS_ASSERTION(name != nullptr, "null ptr"); + if (!name) return NS_ERROR_NULL_POINTER; + + *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_DESCRIPTION); + + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetSupports(char** supports) { + NS_ASSERTION(supports != nullptr, "null ptr"); + if (!supports) return NS_ERROR_NULL_POINTER; + + *supports = strdup(kOutlookSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetSupportsUpgrade(bool* pUpgrade) { + NS_ASSERTION(pUpgrade != nullptr, "null ptr"); + if (!pUpgrade) return NS_ERROR_NULL_POINTER; + + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetImportInterface(const char* pImportType, + nsISupports** ppInterface) { + NS_ASSERTION(pImportType != nullptr, "null ptr"); + if (!pImportType) return NS_ERROR_NULL_POINTER; + NS_ASSERTION(ppInterface != nullptr, "null ptr"); + if (!ppInterface) return NS_ERROR_NULL_POINTER; + + *ppInterface = nullptr; + nsresult rv; + if (!strcmp(pImportType, "mail")) { + // create the nsIImportMail interface and return it! + nsCOMPtr pMail; + nsCOMPtr pGeneric; + rv = ImportOutlookMailImpl::Create(getter_AddRefs(pMail)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericMail(getter_AddRefs(pGeneric)); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("mailInterface", pMail); + nsString name; + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME, name); + nsCOMPtr nameString( + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nameString->SetData(name); + pGeneric->SetData("name", nameString); + nsCOMPtr pInterface(do_QueryInterface(pGeneric)); + pInterface.forget(ppInterface); + } + } + } + } + return rv; + } + + if (!strcmp(pImportType, "addressbook")) { + // create the nsIImportAddressBook interface and return it! + nsCOMPtr pAddress; + nsCOMPtr pGeneric; + rv = ImportOutlookAddressImpl::Create(getter_AddRefs(pAddress)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric)); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + nsCOMPtr pInterface(do_QueryInterface(pGeneric)); + pInterface.forget(ppInterface); + } + } + } + return rv; + } + + if (!strcmp(pImportType, "settings")) { + nsCOMPtr pSettings; + rv = nsOutlookSettings::Create(getter_AddRefs(pSettings)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr pInterface(do_QueryInterface(pSettings)); + pInterface.forget(ppInterface); + } + return rv; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// +nsresult ImportOutlookMailImpl::Create(nsIImportMail** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new ImportOutlookMailImpl()); + return NS_OK; +} + +ImportOutlookMailImpl::ImportOutlookMailImpl() { + nsOutlookCompose::CreateIdentity(); +} + +ImportOutlookMailImpl::~ImportOutlookMailImpl() { + nsOutlookCompose::ReleaseIdentity(); +} + +NS_IMPL_ISUPPORTS(ImportOutlookMailImpl, nsIImportMail) + +NS_IMETHODIMP ImportOutlookMailImpl::GetDefaultLocation(nsIFile** ppLoc, + bool* found, + bool* userVerify) { + NS_ASSERTION(ppLoc != nullptr, "null ptr"); + NS_ASSERTION(found != nullptr, "null ptr"); + NS_ASSERTION(userVerify != nullptr, "null ptr"); + if (!ppLoc || !found || !userVerify) return NS_ERROR_NULL_POINTER; + + *found = false; + *ppLoc = nullptr; + *userVerify = false; + // We need to verify here that we can get the mail, if true then + // return a dummy location, otherwise return no location + CMapiApi mapi; + if (!mapi.Initialize()) return NS_OK; + if (!mapi.LogOn()) return NS_OK; + + CMapiFolderList store; + if (!mapi.IterateStores(store)) return NS_OK; + + if (store.GetSize() == 0) return NS_OK; + + nsresult rv; + nsCOMPtr resultFile = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + *found = true; + resultFile.forget(ppLoc); + *userVerify = false; + + return NS_OK; +} + +NS_IMETHODIMP ImportOutlookMailImpl::FindMailboxes( + nsIFile* pLoc, nsTArray>& boxes) { + NS_ASSERTION(pLoc != nullptr, "null ptr"); + if (!pLoc) return NS_ERROR_NULL_POINTER; + return m_mail.GetMailFolders(boxes); +} + +void ImportOutlookMailImpl::AddLinebreak(nsString* pStream) { + if (pStream) pStream->Append(char16_t('\n')); +} + +void ImportOutlookMailImpl::ReportSuccess(nsString& name, int32_t count, + nsString* pStream) { + if (!pStream) return; + // load the success string + char16_t* pFmt = + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_MAILBOX_SUCCESS); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get(), count); + pStream->Append(pText); + nsOutlookStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportOutlookMailImpl::ReportError(int32_t errorNum, nsString& name, + nsString* pStream) { + if (!pStream) return; + // load the error string + char16_t* pFmt = nsOutlookStringBundle::GetStringByID(errorNum); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + nsOutlookStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportOutlookMailImpl::SetLogs(nsString& success, nsString& error, + char16_t** pError, char16_t** pSuccess) { + if (pError) *pError = ToNewUnicode(error); + if (pSuccess) *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP +ImportOutlookMailImpl::ImportMailbox(nsIImportMailboxDescriptor* pSource, + nsIMsgFolder* dstFolder, + char16_t** pErrorLog, + char16_t** pSuccessLog, bool* fatalError) { + NS_ENSURE_ARG_POINTER(pSource); + NS_ENSURE_ARG_POINTER(dstFolder); + NS_ENSURE_ARG_POINTER(fatalError); + + nsString success; + nsString error; + bool abort = false; + nsString name; + char16_t* pName; + if (NS_SUCCEEDED(pSource->GetDisplayName(&pName))) { + name = pName; + free(pName); + } + + uint32_t mailSize = 0; + pSource->GetSize(&mailSize); + if (mailSize == 0) { + ReportSuccess(name, 0, &success); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + uint32_t index = 0; + pSource->GetIdentifier(&index); + int32_t msgCount = 0; + nsresult rv = NS_OK; + + m_bytesDone = 0; + + rv = m_mail.ImportMailbox(&m_bytesDone, &abort, (int32_t)index, name.get(), + dstFolder, &msgCount); + + if (NS_SUCCEEDED(rv)) + ReportSuccess(name, msgCount, &success); + else + ReportError(OUTLOOKIMPORT_MAILBOX_CONVERTERROR, name, &error); + + SetLogs(success, error, pErrorLog, pSuccessLog); + + return rv; +} + +NS_IMETHODIMP ImportOutlookMailImpl::GetImportProgress(uint32_t* pDoneSoFar) { + NS_ASSERTION(pDoneSoFar != nullptr, "null ptr"); + if (!pDoneSoFar) return NS_ERROR_NULL_POINTER; + + *pDoneSoFar = m_bytesDone; + return NS_OK; +} + +NS_IMETHODIMP ImportOutlookMailImpl::TranslateFolderName( + const nsAString& aFolderName, nsAString& _retval) { + if (aFolderName.LowerCaseEqualsLiteral("deleted items")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestTrashFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("sent items")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestSentFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("outbox")) + _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestUnsentMessagesFolderName); + else + _retval = aFolderName; + return NS_OK; +} + +nsresult ImportOutlookAddressImpl::Create(nsIImportAddressBooks** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new ImportOutlookAddressImpl()); + return NS_OK; +} + +ImportOutlookAddressImpl::ImportOutlookAddressImpl() { + m_msgCount = 0; + m_msgTotal = 0; +} + +ImportOutlookAddressImpl::~ImportOutlookAddressImpl() {} + +NS_IMPL_ISUPPORTS(ImportOutlookAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP ImportOutlookAddressImpl::GetAutoFind(char16_t** description, + bool* _retval) { + NS_ASSERTION(description != nullptr, "null ptr"); + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!description || !_retval) return NS_ERROR_NULL_POINTER; + + *_retval = true; + nsString str; + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRNAME, str); + *description = ToNewUnicode(str); + return NS_OK; +} + +NS_IMETHODIMP ImportOutlookAddressImpl::FindAddressBooks( + nsIFile* location, nsTArray>& books) { + return m_address.GetAddressBooks(books); +} + +NS_IMETHODIMP ImportOutlookAddressImpl::ImportAddressBook( + nsIImportABDescriptor* source, nsIAbDirectory* destination, + nsIImportFieldMap* fieldMap, nsISupports* aSupportService, + char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) { + m_msgCount = 0; + m_msgTotal = 0; + NS_ASSERTION(source != nullptr, "null ptr"); + NS_ASSERTION(destination != nullptr, "null ptr"); + NS_ASSERTION(fatalError != nullptr, "null ptr"); + + nsString success; + nsString error; + if (!source || !destination || !fatalError) { + IMPORT_LOG0("*** Bad param passed to outlook address import\n"); + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_BADPARAM, error); + if (fatalError) *fatalError = true; + ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_NULL_POINTER; + } + + nsString name; + source->GetPreferredName(name); + + uint32_t id; + if (NS_FAILED(source->GetIdentifier(&id))) { + ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE, + name, &error); + ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + rv = m_address.ImportAddresses(&m_msgCount, &m_msgTotal, name.get(), id, + destination, error); + if (NS_SUCCEEDED(rv) && error.IsEmpty()) + ReportSuccess(name, &success); + else + ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_CONVERTERROR, name, + &error); + + ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog); + IMPORT_LOG0("*** Returning from outlook address import\n"); + return NS_OK; +} + +NS_IMETHODIMP ImportOutlookAddressImpl::GetImportProgress(uint32_t* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!_retval) return NS_ERROR_NULL_POINTER; + + uint32_t result = m_msgCount; + if (m_msgTotal) { + result *= 100; + result /= m_msgTotal; + } else + result = 0; + + if (result > 100) result = 100; + + *_retval = result; + + return NS_OK; +} + +void ImportOutlookAddressImpl::ReportSuccess(nsString& name, + nsString* pStream) { + if (!pStream) return; + // load the success string + char16_t* pFmt = + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_SUCCESS); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + nsOutlookStringBundle::FreeString(pFmt); + ImportOutlookMailImpl::AddLinebreak(pStream); +} diff --git a/comm/mailnews/import/src/nsOutlookImport.h b/comm/mailnews/import/src/nsOutlookImport.h new file mode 100644 index 0000000000..fb94b31502 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookImport.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsOutlookImport_h___ +#define nsOutlookImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" + +#define NS_OUTLOOKIMPORT_CID \ + { /* 1DB469A0-8B00-11d3-A206-00A0CC26DA63 */ \ + 0x1db469a0, 0x8b00, 0x11d3, { \ + 0xa2, 0x6, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 \ + } \ + } + +#define kOutlookSupportsString \ + NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR + +class nsOutlookImport : public nsIImportModule { + public: + nsOutlookImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we support the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + + protected: + virtual ~nsOutlookImport(); +}; + +#endif /* nsOutlookImport_h___ */ diff --git a/comm/mailnews/import/src/nsOutlookMail.cpp b/comm/mailnews/import/src/nsOutlookMail.cpp new file mode 100644 index 0000000000..3569c9096d --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookMail.cpp @@ -0,0 +1,830 @@ +/* -*- 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/. */ + +/* + Outlook mail import +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsIImportService.h" +#include "nsIImportFieldMap.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportABDescriptor.h" +#include "nsOutlookStringBundle.h" +#include "nsIAbCard.h" +#include "mdb.h" +#include "ImportDebug.h" +#include "nsOutlookMail.h" +#include "nsUnicharUtils.h" +#include "nsIOutputStream.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsMsgI18N.h" +#include "nsNetUtil.h" + +/* ------------ Address book stuff ----------------- */ +typedef struct { + int32_t mozField; + int32_t multiLine; + ULONG mapiTag; +} MAPIFields; + +/* + Fields in MAPI, not in Mozilla + PR_OFFICE_LOCATION + FIX - PR_BIRTHDAY - stored as PT_SYSTIME - FIX to extract for moz address book + birthday PR_DISPLAY_NAME_PREFIX - Mr., Mrs. Dr., etc. PR_SPOUSE_NAME PR_GENDER + - integer, not text FIX - PR_CONTACT_EMAIL_ADDRESSES - multiuline strings for + email addresses, needs parsing to get secondary email address for mozilla +*/ + +#define kIsMultiLine -2 +#define kNoMultiLine -1 + +static MAPIFields gMapiFields[] = { + {35, kIsMultiLine, PR_BODY}, + {6, kNoMultiLine, PR_BUSINESS_TELEPHONE_NUMBER}, + {7, kNoMultiLine, PR_HOME_TELEPHONE_NUMBER}, + {25, kNoMultiLine, PR_COMPANY_NAME}, + {23, kNoMultiLine, PR_TITLE}, + {10, kNoMultiLine, PR_CELLULAR_TELEPHONE_NUMBER}, + {9, kNoMultiLine, PR_PAGER_TELEPHONE_NUMBER}, + {8, kNoMultiLine, PR_BUSINESS_FAX_NUMBER}, + {8, kNoMultiLine, PR_HOME_FAX_NUMBER}, + {22, kNoMultiLine, PR_COUNTRY}, + {19, kNoMultiLine, PR_LOCALITY}, + {20, kNoMultiLine, PR_STATE_OR_PROVINCE}, + {17, 18, PR_STREET_ADDRESS}, + {21, kNoMultiLine, PR_POSTAL_CODE}, + {27, kNoMultiLine, PR_PERSONAL_HOME_PAGE}, + {26, kNoMultiLine, PR_BUSINESS_HOME_PAGE}, + {13, kNoMultiLine, PR_HOME_ADDRESS_CITY}, + {16, kNoMultiLine, PR_HOME_ADDRESS_COUNTRY}, + {15, kNoMultiLine, PR_HOME_ADDRESS_POSTAL_CODE}, + {14, kNoMultiLine, PR_HOME_ADDRESS_STATE_OR_PROVINCE}, + {11, 12, PR_HOME_ADDRESS_STREET}, + {24, kNoMultiLine, PR_DEPARTMENT_NAME}}; +/* ---------------------------------------------------- */ + +#define kCopyBufferSize (16 * 1024) + +// The email address in Outlook Contacts doesn't have a named +// property, we need to use this mapi name ID to access the email +// The MAPINAMEID for email address has ulKind=MNID_ID +// Outlook stores each email address in two IDs, 32899/32900 for Email1 +// 32915/32916 for Email2, 32931/32932 for Email3 +// Current we use OUTLOOK_EMAIL1_MAPI_ID1 for primary email +// OUTLOOK_EMAIL2_MAPI_ID1 for secondary email +#define OUTLOOK_EMAIL1_MAPI_ID1 32899 +#define OUTLOOK_EMAIL1_MAPI_ID2 32900 +#define OUTLOOK_EMAIL2_MAPI_ID1 32915 +#define OUTLOOK_EMAIL2_MAPI_ID2 32916 +#define OUTLOOK_EMAIL3_MAPI_ID1 32931 +#define OUTLOOK_EMAIL3_MAPI_ID2 32932 + +nsOutlookMail::nsOutlookMail() { + m_gotAddresses = false; + m_gotFolders = false; + m_haveMapi = CMapiApi::LoadMapi(); + m_lpMdb = NULL; +} + +nsOutlookMail::~nsOutlookMail() { + // EmptyAttachments(); +} + +nsresult nsOutlookMail::GetMailFolders( + nsTArray>& boxes) { + if (!m_haveMapi) { + IMPORT_LOG0("GetMailFolders called before Mapi is initialized\n"); + return NS_ERROR_FAILURE; + } + nsresult rv; + boxes.Clear(); + + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + m_gotFolders = true; + + m_folderList.ClearAll(); + + m_mapi.Initialize(); + m_mapi.LogOn(); + + if (m_storeList.GetSize() == 0) m_mapi.IterateStores(m_storeList); + + int i = 0; + CMapiFolder* pFolder; + if (m_storeList.GetSize() > 1) { + while ((pFolder = m_storeList.GetItem(i))) { + CMapiFolder* pItem = new CMapiFolder(pFolder); + pItem->SetDepth(1); + m_folderList.AddItem(pItem); + if (!m_mapi.GetStoreFolders(pItem->GetCBEntryID(), pItem->GetEntryID(), + m_folderList, 2)) { + IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i); + } + i++; + } + } else { + if ((pFolder = m_storeList.GetItem(i))) { + if (!m_mapi.GetStoreFolders(pFolder->GetCBEntryID(), + pFolder->GetEntryID(), m_folderList, 1)) { + IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i); + } + } + } + + // Create the mailbox descriptors for the list of folders + nsCOMPtr pID; + nsString name; + nsString uniName; + + for (i = 0; i < m_folderList.GetSize(); i++) { + pFolder = m_folderList.GetItem(i); + rv = impSvc->CreateNewMailboxDescriptor(getter_AddRefs(pID)); + if (NS_SUCCEEDED(rv)) { + pID->SetDepth(pFolder->GetDepth()); + pID->SetIdentifier(i); + + pFolder->GetDisplayName(name); + pID->SetDisplayName(name.get()); + + pID->SetSize(1000); + boxes.AppendElement(pID); + } + } + return NS_OK; +} + +bool nsOutlookMail::IsAddressBookNameUnique(nsString& name, nsString& list) { + nsString usedName; + usedName.Append('['); + usedName.Append(name); + usedName.AppendLiteral("],"); + + return list.Find(usedName) == -1; +} + +void nsOutlookMail::MakeAddressBookNameUnique(nsString& name, nsString& list) { + nsString newName; + int idx = 1; + + newName = name; + while (!IsAddressBookNameUnique(newName, list)) { + newName = name; + newName.Append(char16_t(' ')); + newName.AppendInt((int32_t)idx); + idx++; + } + + name = newName; + list.Append('['); + list.Append(name); + list.AppendLiteral("],"); +} + +nsresult nsOutlookMail::GetAddressBooks( + nsTArray>& books) { + books.Clear(); + if (!m_haveMapi) { + IMPORT_LOG0("GetAddressBooks called before Mapi is initialized\n"); + return NS_ERROR_FAILURE; + } + nsresult rv; + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + m_gotAddresses = true; + + m_addressList.ClearAll(); + m_mapi.Initialize(); + m_mapi.LogOn(); + if (m_storeList.GetSize() == 0) m_mapi.IterateStores(m_storeList); + + int i = 0; + CMapiFolder* pFolder; + if (m_storeList.GetSize() > 1) { + while ((pFolder = m_storeList.GetItem(i))) { + CMapiFolder* pItem = new CMapiFolder(pFolder); + pItem->SetDepth(1); + m_addressList.AddItem(pItem); + if (!m_mapi.GetStoreAddressFolders(pItem->GetCBEntryID(), + pItem->GetEntryID(), m_addressList)) { + IMPORT_LOG1("GetStoreAddressFolders for index %d failed.\n", i); + } + i++; + } + } else { + if ((pFolder = m_storeList.GetItem(i))) { + if (!m_mapi.GetStoreAddressFolders( + pFolder->GetCBEntryID(), pFolder->GetEntryID(), m_addressList)) { + IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i); + } + } + } + + // Create the mailbox descriptors for the list of folders + nsCOMPtr pID; + nsString name; + nsString list; + + for (i = 0; i < m_addressList.GetSize(); i++) { + pFolder = m_addressList.GetItem(i); + if (!pFolder->IsStore()) { + rv = impSvc->CreateNewABDescriptor(getter_AddRefs(pID)); + if (NS_SUCCEEDED(rv)) { + pID->SetIdentifier(i); + pFolder->GetDisplayName(name); + MakeAddressBookNameUnique(name, list); + pID->SetPreferredName(name); + pID->SetSize(100); + books.AppendElement(pID); + } + } + } + return NS_OK; +} + +void nsOutlookMail::OpenMessageStore(CMapiFolder* pNextFolder) { + // Open the store specified + if (pNextFolder->IsStore()) { + if (!m_mapi.OpenStore(pNextFolder->GetCBEntryID(), + pNextFolder->GetEntryID(), &m_lpMdb)) { + m_lpMdb = NULL; + IMPORT_LOG0("CMapiApi::OpenStore failed\n"); + } + + return; + } + + // Check to see if we should open the one and only store + if (!m_lpMdb) { + if (m_storeList.GetSize() == 1) { + CMapiFolder* pFolder = m_storeList.GetItem(0); + if (pFolder) { + if (!m_mapi.OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), + &m_lpMdb)) { + m_lpMdb = NULL; + IMPORT_LOG0("CMapiApi::OpenStore failed\n"); + } + } else { + IMPORT_LOG0("Error retrieving the one & only message store\n"); + } + } else { + IMPORT_LOG0( + "*** Error importing a folder without a valid message store\n"); + } + } +} + +// Roles and responsibilities: +// nsOutlookMail +// - Connect to Outlook +// - Enumerate the mailboxes +// - Iterate the mailboxes +// - For each mail, create one nsOutlookCompose object +// - For each mail, create one CMapiMessage object +// +// nsOutlookCompose +// - Establish a TB session +// - Connect to all required services +// - Perform the composition of the RC822 document from the data gathered by +// CMapiMessage +// - Save the composed message to the TB mailbox +// - Ensure the proper cleanup +// +// CMapiMessage +// - Encapsulate the MAPI message interface +// - Gather the information required to (re)compose the message + +ImportMailboxRunnable::ImportMailboxRunnable( + uint32_t* pDoneSoFar, bool* pAbort, int32_t index, const char16_t* pName, + nsIMsgFolder* dstFolder, int32_t* pMsgCount, nsOutlookMail* aCaller) + : mozilla::Runnable("ImportMailboxRunnable"), + mResult(NS_OK), + mCaller(aCaller), + mDoneSoFar(pDoneSoFar), + mAbort(pAbort), + mIndex(index), + mName(pName), + mDstFolder(dstFolder), + mMsgCount(pMsgCount) {} +NS_IMETHODIMP ImportMailboxRunnable::Run() { + if ((mIndex < 0) || (mIndex >= mCaller->m_folderList.GetSize())) { + IMPORT_LOG0("*** Bad mailbox identifier, unable to import\n"); + *mAbort = true; + mResult = NS_ERROR_FAILURE; + return NS_OK; // Sync runnable must return OK. + } + + int32_t dummyMsgCount = 0; + if (mMsgCount) + *mMsgCount = 0; + else + mMsgCount = &dummyMsgCount; + + CMapiFolder* pFolder = mCaller->m_folderList.GetItem(mIndex); + mCaller->OpenMessageStore(pFolder); + if (!mCaller->m_lpMdb) { + IMPORT_LOG1("*** Unable to obtain mapi message store for mailbox: %S\n", + (const wchar_t*)mName); + mResult = NS_ERROR_FAILURE; + return NS_OK; // Sync runnable must return OK. + } + + if (pFolder->IsStore()) return NS_OK; + + // now what? + CMapiFolderContents contents(mCaller->m_lpMdb, pFolder->GetCBEntryID(), + pFolder->GetEntryID()); + + BOOL done = FALSE; + ULONG cbEid; + LPENTRYID lpEid; + ULONG oType; + LPMESSAGE lpMsg = nullptr; + ULONG totalCount; + double doneCalc; + + nsCOMPtr outputStream; + nsCOMPtr msgStore; + nsresult rv = mDstFolder->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + while (!done) { + if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) { + IMPORT_LOG1("*** Error iterating mailbox: %S\n", (const wchar_t*)mName); + mResult = NS_ERROR_FAILURE; + return NS_OK; // Sync runnable must return OK. + } + + nsCOMPtr msgHdr; + rv = msgStore->GetNewMsgOutputStream(mDstFolder, getter_AddRefs(msgHdr), + getter_AddRefs(outputStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** Error getting nsIOutputStream of mailbox: %S\n", + (const wchar_t*)mName); + mResult = rv; + return NS_OK; // Sync runnable must return OK. + } + totalCount = contents.GetCount(); + doneCalc = *mMsgCount; + doneCalc /= totalCount; + doneCalc *= 1000; + if (mDoneSoFar) { + *mDoneSoFar = (uint32_t)doneCalc; + if (*mDoneSoFar > 1000) *mDoneSoFar = 1000; + } + + if (!done && (oType == MAPI_MESSAGE)) { + if (!mCaller->m_mapi.OpenMdbEntry(mCaller->m_lpMdb, cbEid, lpEid, + (LPUNKNOWN*)&lpMsg)) { + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", + (const wchar_t*)mName); + mResult = NS_ERROR_FAILURE; + return NS_OK; // Sync runnable must return OK. + } + + // See if it's a drafts folder. Outlook doesn't allow drafts + // folder to be configured so it's ok to hard code it here. + nsAutoString folderName(mName); + nsMsgDeliverMode mode = nsIMsgSend::nsMsgDeliverNow; + mode = nsIMsgSend::nsMsgSaveAsDraft; + if (folderName.LowerCaseEqualsLiteral("drafts")) + mode = nsIMsgSend::nsMsgSaveAsDraft; + + rv = ImportMessage(lpMsg, outputStream, mode); + if (NS_SUCCEEDED(rv)) { // No errors & really imported + (*mMsgCount)++; + msgStore->FinishNewMessage(outputStream, msgHdr); + outputStream = nullptr; + } else { + IMPORT_LOG1("*** Error reading message from mailbox: %S\n", + (const wchar_t*)mName); + msgStore->DiscardNewMessage(outputStream, msgHdr); + outputStream = nullptr; + } + } + } + return NS_OK; +} + +nsresult ProxyImportMailbox(uint32_t* pDoneSoFar, bool* pAbort, int32_t index, + const char16_t* pName, nsIMsgFolder* dstFolder, + int32_t* pMsgCount, nsOutlookMail* aCaller) { + RefPtr importMailbox = new ImportMailboxRunnable( + pDoneSoFar, pAbort, index, pName, dstFolder, pMsgCount, aCaller); + nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( + "ProxyImportMailbox"_ns, mozilla::GetMainThreadSerialEventTarget(), + do_AddRef(importMailbox)); + NS_ENSURE_SUCCESS(rv, rv); + + return importMailbox->mResult; +} + +nsresult nsOutlookMail::ImportMailbox(uint32_t* pDoneSoFar, bool* pAbort, + int32_t index, const char16_t* pName, + nsIMsgFolder* dstFolder, + int32_t* pMsgCount) { + return ProxyImportMailbox(pDoneSoFar, pAbort, index, pName, dstFolder, + pMsgCount, this); +} + +nsresult ImportMailboxRunnable::ImportMessage(LPMESSAGE lpMsg, + nsIOutputStream* pDest, + nsMsgDeliverMode mode) { + CMapiMessage msg(lpMsg); + // If we wanted to skip messages that were downloaded in header only mode, we + // would return NS_ERROR_FAILURE if !msg.FullMessageDownloaded. However, we + // don't do this because it may cause seemingly wrong import results. + // A user will get less mails in his imported folder than were in the original + // folder, and this may make user feel like TB import is bad. In reality, the + // skipped messages are those that have not been downloaded yet, because they + // were downloaded in the "headers-only" mode. This is different from the case + // when the message is downloaded completely, but consists only of headers - + // in this case the message will be imported anyway. + + if (!msg.ValidState()) return NS_ERROR_FAILURE; + + // I have to create a composer for each message, since it turns out that if we + // create one composer for several messages, the Send Proxy object that is + // shared between those messages isn't reset properly (at least in the current + // implementation), which leads to crash. If there's a proper way to + // reinitialize the Send Proxy object, then we could slightly optimize the + // send process. + nsOutlookCompose compose; + nsresult rv = compose.ProcessMessage(mode, msg, pDest); + + // Just for YUCKS, let's try an extra endline + nsOutlookMail::WriteData(pDest, "\x0D\x0A", 2); + + return rv; +} + +BOOL nsOutlookMail::WriteData(nsIOutputStream* pDest, const char* pData, + uint32_t len) { + uint32_t written; + nsresult rv = pDest->Write(pData, len, &written); + return NS_SUCCEEDED(rv) && written == len; +} + +nsresult nsOutlookMail::ImportAddresses(uint32_t* pCount, uint32_t* pTotal, + const char16_t* pName, uint32_t id, + nsIAbDirectory* pDirectory, + nsString& errors) { + if (id >= (uint32_t)(m_addressList.GetSize())) { + IMPORT_LOG0("*** Bad address identifier, unable to import\n"); + return NS_ERROR_FAILURE; + } + + uint32_t dummyCount = 0; + if (pCount) + *pCount = 0; + else + pCount = &dummyCount; + + CMapiFolder* pFolder; + if (id > 0) { + int32_t idx = (int32_t)id; + idx--; + while (idx >= 0) { + pFolder = m_addressList.GetItem(idx); + if (pFolder->IsStore()) { + OpenMessageStore(pFolder); + break; + } + idx--; + } + } + + pFolder = m_addressList.GetItem(id); + OpenMessageStore(pFolder); + if (!m_lpMdb) { + IMPORT_LOG1( + "*** Unable to obtain mapi message store for address book: %S\n", + (const wchar_t*)pName); + return NS_ERROR_FAILURE; + } + + if (pFolder->IsStore()) return NS_OK; + + nsresult rv; + + nsCOMPtr pFieldMap; + + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewFieldMap(getter_AddRefs(pFieldMap)); + } + + CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(), + pFolder->GetEntryID()); + + BOOL done = FALSE; + ULONG cbEid; + LPENTRYID lpEid; + ULONG oType; + LPMESSAGE lpMsg; + nsCString type; + LPSPropValue pVal; + nsString subject; + + while (!done) { + (*pCount)++; + + if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) { + IMPORT_LOG1("*** Error iterating address book: %S\n", + (const wchar_t*)pName); + return NS_ERROR_FAILURE; + } + + if (pTotal && (*pTotal == 0)) *pTotal = contents.GetCount(); + + if (!done && (oType == MAPI_MESSAGE)) { + if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN*)&lpMsg)) { + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", + (const wchar_t*)pName); + return NS_ERROR_FAILURE; + } + + // Get the PR_MESSAGE_CLASS attribute, + // ensure that it is IPM.Contact + pVal = m_mapi.GetMapiProperty(lpMsg, PR_MESSAGE_CLASS); + if (pVal) { + type.Truncate(); + m_mapi.GetStringFromProp(pVal, type); + if (type.EqualsLiteral("IPM.Contact")) { + // This is a contact, add it to the address book! + subject.Truncate(); + pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT); + if (pVal) m_mapi.GetStringFromProp(pVal, subject); + + nsCOMPtr newCard = + do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv); + if (newCard) { + if (BuildCard(subject.get(), pDirectory, newCard, lpMsg, + pFieldMap)) { + nsIAbCard* outCard; + pDirectory->AddCard(newCard, &outCard); + } + } + } else if (type.EqualsLiteral("IPM.DistList")) { + // This is a list/group, add it to the address book! + subject.Truncate(); + pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT); + if (pVal) m_mapi.GetStringFromProp(pVal, subject); + CreateList(subject, pDirectory, lpMsg, pFieldMap); + } + } + + lpMsg->Release(); + } + } + + return rv; +} +nsresult nsOutlookMail::CreateList(const nsString& pName, + nsIAbDirectory* pDirectory, + LPMAPIPROP pUserList, + nsIImportFieldMap* pFieldMap) { + // If no name provided then we're done. + if (pName.IsEmpty()) return NS_OK; + + nsresult rv = NS_ERROR_FAILURE; + // Make sure we have db to work with. + if (!pDirectory) return rv; + + nsCOMPtr newList = + do_CreateInstance("@mozilla.org/addressbook/directoryproperty;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = newList->SetDirName(pName); + NS_ENSURE_SUCCESS(rv, rv); + + HRESULT hr; + LPSPropValue value = NULL; + ULONG valueCount = 0; + + LPSPropTagArray properties = NULL; + m_mapi.MAPIAllocateBuffer(CbNewSPropTagArray(1), (void**)&properties); + properties->cValues = 1; + properties->aulPropTag[0] = m_mapi.GetEmailPropertyTag(pUserList, 0x8054); + hr = pUserList->GetProps(properties, 0, &valueCount, &value); + m_mapi.MAPIFreeBuffer(properties); + if (HR_FAILED(hr)) return NS_ERROR_FAILURE; + if (!value) return NS_ERROR_NOT_AVAILABLE; + // XXX from here out, value must be freed with MAPIFreeBuffer + + SBinaryArray* sa = (SBinaryArray*)&value->Value.bin; + if (!sa || !sa->lpbin) { + m_mapi.MAPIFreeBuffer(value); + return NS_ERROR_NULL_POINTER; + } + + LPENTRYID lpEid; + ULONG cbEid; + ULONG idx; + LPMESSAGE lpMsg; + nsCString type; + LPSPropValue pVal; + nsString subject; + ULONG total; + + total = sa->cValues; + for (idx = 0; idx < total; idx++) { + lpEid = (LPENTRYID)sa->lpbin[idx].lpb; + cbEid = sa->lpbin[idx].cb; + + if (!m_mapi.OpenEntry(cbEid, lpEid, (LPUNKNOWN*)&lpMsg)) { + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", + static_cast(pName.get())); + m_mapi.MAPIFreeBuffer(value); + return NS_ERROR_FAILURE; + } + // This is a contact, add it to the address book! + subject.Truncate(); + pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT); + if (pVal) m_mapi.GetStringFromProp(pVal, subject); + + nsCOMPtr newCard = + do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv); + if (newCard) { + if (BuildCard(subject.get(), pDirectory, newCard, lpMsg, pFieldMap)) { + nsIAbCard* outCard; + newList->AddCard(newCard, &outCard); + } + } + } + m_mapi.MAPIFreeBuffer(value); + + nsIAbDirectory* outList; + rv = pDirectory->AddMailList(newList, &outList); + return rv; +} + +void nsOutlookMail::SanitizeValue(nsString& val) { + val.ReplaceSubstring(u"\r\n"_ns, u", "_ns); + val.ReplaceChar(u"\r\n", u','); +} + +void nsOutlookMail::SplitString(nsString& val1, nsString& val2) { + // Find the last line if there is more than one! + int32_t idx = val1.RFind(u"\x0D\x0A"); + int32_t cnt = 2; + if (idx == -1) { + cnt = 1; + idx = val1.RFindChar(13); + } + if (idx == -1) idx = val1.RFindChar(10); + if (idx != -1) { + val2 = Substring(val1, idx + cnt); + val1.SetLength(idx); + SanitizeValue(val1); + } +} + +bool nsOutlookMail::BuildCard(const char16_t* pName, nsIAbDirectory* pDirectory, + nsIAbCard* newCard, LPMAPIPROP pUser, + nsIImportFieldMap* pFieldMap) { + nsString lastName; + nsString firstName; + nsString eMail; + nsString nickName; + nsString middleName; + nsString secondEMail; + ULONG emailTag; + + LPSPropValue pProp = m_mapi.GetMapiProperty(pUser, PR_EMAIL_ADDRESS); + if (!pProp) { + emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL1_MAPI_ID1); + if (emailTag) { + pProp = m_mapi.GetMapiProperty(pUser, emailTag); + } + } + if (pProp) { + m_mapi.GetStringFromProp(pProp, eMail); + SanitizeValue(eMail); + } + + // for secondary email + emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL2_MAPI_ID1); + if (emailTag) { + pProp = m_mapi.GetMapiProperty(pUser, emailTag); + if (pProp) { + m_mapi.GetStringFromProp(pProp, secondEMail); + SanitizeValue(secondEMail); + } + } + + pProp = m_mapi.GetMapiProperty(pUser, PR_GIVEN_NAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, firstName); + SanitizeValue(firstName); + } + pProp = m_mapi.GetMapiProperty(pUser, PR_SURNAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, lastName); + SanitizeValue(lastName); + } + pProp = m_mapi.GetMapiProperty(pUser, PR_MIDDLE_NAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, middleName); + SanitizeValue(middleName); + } + pProp = m_mapi.GetMapiProperty(pUser, PR_NICKNAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, nickName); + SanitizeValue(nickName); + } + if (firstName.IsEmpty() && lastName.IsEmpty()) { + firstName = pName; + } + + nsString displayName; + pProp = m_mapi.GetMapiProperty(pUser, PR_DISPLAY_NAME); + if (pProp) { + m_mapi.GetStringFromProp(pProp, displayName); + SanitizeValue(displayName); + } + if (displayName.IsEmpty()) { + if (firstName.IsEmpty()) + displayName = pName; + else { + displayName = firstName; + if (!middleName.IsEmpty()) { + displayName.Append(char16_t(' ')); + displayName.Append(middleName); + } + if (!lastName.IsEmpty()) { + displayName.Append(char16_t(' ')); + displayName.Append(lastName); + } + } + } + + // We now have the required fields + // write them out followed by any optional fields! + if (!displayName.IsEmpty()) { + newCard->SetDisplayName(displayName); + } + if (!firstName.IsEmpty()) { + newCard->SetFirstName(firstName); + } + if (!lastName.IsEmpty()) { + newCard->SetLastName(lastName); + } + if (!nickName.IsEmpty()) { + newCard->SetPropertyAsAString(kNicknameProperty, nickName); + } + if (!eMail.IsEmpty()) { + newCard->SetPrimaryEmail(eMail); + } + if (!secondEMail.IsEmpty()) { + newCard->SetPropertyAsAString(k2ndEmailProperty, secondEMail); + } + + // Do all of the extra fields! + + nsString value; + nsString line2; + + if (pFieldMap) { + int max = sizeof(gMapiFields) / sizeof(MAPIFields); + for (int i = 0; i < max; i++) { + pProp = m_mapi.GetMapiProperty(pUser, gMapiFields[i].mapiTag); + if (pProp) { + m_mapi.GetStringFromProp(pProp, value); + if (!value.IsEmpty()) { + if (gMapiFields[i].multiLine == kNoMultiLine) { + SanitizeValue(value); + pFieldMap->SetFieldValue(pDirectory, newCard, + gMapiFields[i].mozField, value); + } else if (gMapiFields[i].multiLine == kIsMultiLine) { + pFieldMap->SetFieldValue(pDirectory, newCard, + gMapiFields[i].mozField, value); + } else { + line2.Truncate(); + SplitString(value, line2); + if (!value.IsEmpty()) + pFieldMap->SetFieldValue(pDirectory, newCard, + gMapiFields[i].mozField, value); + if (!line2.IsEmpty()) + pFieldMap->SetFieldValue(pDirectory, newCard, + gMapiFields[i].multiLine, line2); + } + } + } + } + } + + return true; +} diff --git a/comm/mailnews/import/src/nsOutlookMail.h b/comm/mailnews/import/src/nsOutlookMail.h new file mode 100644 index 0000000000..8f28d6425d --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookMail.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsOutlookMail_h___ +#define nsOutlookMail_h___ + +#include "nsIArray.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsOutlookCompose.h" +#include "nsIFile.h" +#include "MapiApi.h" +#include "MapiMessage.h" +#include "nsIAbDirectory.h" +#include "nsThreadUtils.h" + +class nsIImportFieldMap; + +class nsOutlookMail { + public: + nsOutlookMail(); + ~nsOutlookMail(); + + nsresult GetMailFolders(nsTArray>& boxes); + nsresult GetAddressBooks(nsTArray>& books); + nsresult ImportMailbox(uint32_t* pDoneSoFar, bool* pAbort, int32_t index, + const char16_t* pName, nsIMsgFolder* pDest, + int32_t* pMsgCount); + nsresult ImportAddresses(uint32_t* pCount, uint32_t* pTotal, + const char16_t* pName, uint32_t id, + nsIAbDirectory* pDirectory, nsString& errors); + void OpenMessageStore(CMapiFolder* pNextFolder); + static BOOL WriteData(nsIOutputStream* pDest, const char* pData, + uint32_t len); + + private: + bool IsAddressBookNameUnique(nsString& name, nsString& list); + void MakeAddressBookNameUnique(nsString& name, nsString& list); + void SanitizeValue(nsString& val); + void SplitString(nsString& val1, nsString& val2); + bool BuildCard(const char16_t* pName, nsIAbDirectory* pDirectory, + nsIAbCard* newCard, LPMAPIPROP pUser, + nsIImportFieldMap* pFieldMap); + nsresult CreateList(const nsString& pName, nsIAbDirectory* pDirectory, + LPMAPIPROP pUserList, nsIImportFieldMap* pFieldMap); + + private: + bool m_gotFolders; + bool m_gotAddresses; + bool m_haveMapi; + CMapiFolderList m_addressList; + CMapiFolderList m_storeList; + + public: + // Needed for the proxy class. + CMapiApi m_mapi; + CMapiFolderList m_folderList; + LPMDB m_lpMdb; +}; + +class ImportMailboxRunnable : public mozilla::Runnable { + public: + ImportMailboxRunnable(uint32_t* pDoneSoFar, bool* pAbort, int32_t index, + const char16_t* pName, nsIMsgFolder* dstFolder, + int32_t* pMsgCount, nsOutlookMail* aCaller); + NS_DECL_NSIRUNNABLE + static nsresult ImportMessage(LPMESSAGE lpMsg, nsIOutputStream* pDest, + nsMsgDeliverMode mode); + nsresult mResult; + + private: + nsOutlookMail* mCaller; + uint32_t* mDoneSoFar; + bool* mAbort; + int32_t mIndex; + const char16_t* mName; + nsCOMPtr mMessageFile; + nsCOMPtr mDstFolder; + int32_t* mMsgCount; +}; + +#endif /* nsOutlookMail_h___ */ diff --git a/comm/mailnews/import/src/nsOutlookSettings.cpp b/comm/mailnews/import/src/nsOutlookSettings.cpp new file mode 100644 index 0000000000..2e202a5600 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookSettings.cpp @@ -0,0 +1,500 @@ +/* -*- 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/. */ + +/* + Outlook (Win32) settings +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsOutlookImport.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIImportSettings.h" +#include "nsOutlookSettings.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsOutlookStringBundle.h" +#include "ImportDebug.h" +#include "nsIPop3IncomingServer.h" +#include "nsMsgI18N.h" +#include +#include "nsIWindowsRegKey.h" +#include "nsComponentManagerUtils.h" +#include "nsNativeCharsetUtils.h" + +class OutlookSettings { + public: + static nsresult FindAccountsKey(nsIWindowsRegKey** aKey); + static nsresult QueryAccountSubKey(nsIWindowsRegKey** aKey); + static nsresult GetDefaultMailAccountName(nsAString& aName); + + static bool DoImport(nsIMsgAccount** aAccount); + + static bool DoIMAPServer(nsIMsgAccountManager* aMgr, nsIWindowsRegKey* aKey, + const nsString& aServerName, + nsIMsgAccount** aAccount); + static bool DoPOP3Server(nsIMsgAccountManager* aMgr, nsIWindowsRegKey* aKey, + const nsString& aServerName, + nsIMsgAccount** aAccount); + + static void SetIdentities(nsIMsgAccountManager* pMgr, nsIMsgAccount* pAcc, + nsIWindowsRegKey* aKey); + + static nsresult SetSmtpServer(nsIMsgAccountManager* aMgr, nsIMsgAccount* aAcc, + nsIMsgIdentity* aId, const nsString& aServer, + const nsString& aUser); + static nsresult SetSmtpServerKey(nsIMsgIdentity* aId, nsISmtpServer* aServer); + static nsresult GetAccountName(nsIWindowsRegKey* aKey, + const nsString& aDefaultName, + nsAString& aAccountName); +}; + +#define OUTLOOK2003_REGISTRY_KEY \ + "Software\\Microsoft\\Office\\Outlook\\OMI Account Manager" +#define OUTLOOK98_REGISTRY_KEY \ + "Software\\Microsoft\\Office\\8.0\\Outlook\\OMI Account Manager" + +//////////////////////////////////////////////////////////////////////// +nsresult nsOutlookSettings::Create(nsIImportSettings** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new nsOutlookSettings()); + return NS_OK; +} + +nsOutlookSettings::nsOutlookSettings() {} + +nsOutlookSettings::~nsOutlookSettings() {} + +NS_IMPL_ISUPPORTS(nsOutlookSettings, nsIImportSettings) + +NS_IMETHODIMP nsOutlookSettings::AutoLocate(char16_t** description, + nsIFile** location, bool* _retval) { + NS_ASSERTION(description != nullptr, "null ptr"); + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!description || !_retval) return NS_ERROR_NULL_POINTER; + + *description = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME); + *_retval = false; + + if (location) *location = nullptr; + + // look for the registry key for the accounts + nsCOMPtr key; + *_retval = + NS_SUCCEEDED(OutlookSettings::FindAccountsKey(getter_AddRefs(key))); + + return NS_OK; +} + +NS_IMETHODIMP nsOutlookSettings::SetLocation(nsIFile* location) { + return NS_OK; +} + +NS_IMETHODIMP nsOutlookSettings::Import(nsIMsgAccount** localMailAccount, + bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + + if (OutlookSettings::DoImport(localMailAccount)) { + *_retval = true; + IMPORT_LOG0("Settings import appears successful\n"); + } else { + *_retval = false; + IMPORT_LOG0("Settings import returned FALSE\n"); + } + + return NS_OK; +} + +nsresult OutlookSettings::FindAccountsKey(nsIWindowsRegKey** aKey) { + nsresult rv; + nsCOMPtr key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK2003_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + + if (NS_FAILED(rv)) { + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK98_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + } + + if (NS_SUCCEEDED(rv)) key.forget(aKey); + + return rv; +} + +nsresult OutlookSettings::QueryAccountSubKey(nsIWindowsRegKey** aKey) { + nsresult rv; + nsCOMPtr key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK2003_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + if (NS_SUCCEEDED(rv)) { + key.forget(aKey); + return rv; + } + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK98_REGISTRY_KEY), + nsIWindowsRegKey::ACCESS_QUERY_VALUE | + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS); + if (NS_SUCCEEDED(rv)) { + key.forget(aKey); + return rv; + } + + return NS_ERROR_FAILURE; +} + +nsresult OutlookSettings::GetDefaultMailAccountName(nsAString& aName) { + nsCOMPtr key; + nsresult rv = QueryAccountSubKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) return rv; + + return key->ReadStringValue(u"Default Mail Account"_ns, aName); +} + +bool OutlookSettings::DoImport(nsIMsgAccount** aAccount) { + nsCOMPtr key; + nsresult rv = OutlookSettings::FindAccountsKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error finding Outlook registry account keys\n"); + return false; + } + + nsCOMPtr accMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create a account manager!\n"); + return false; + } + + nsAutoString defMailName; + rv = GetDefaultMailAccountName(defMailName); + + uint32_t childCount; + key->GetChildCount(&childCount); + + uint32_t accounts = 0; + uint32_t popCount = 0; + for (uint32_t i = 0; i < childCount; i++) { + nsAutoString keyName; + key->GetChildName(i, keyName); + nsCOMPtr subKey; + rv = key->OpenChild(keyName, nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(subKey)); + if (NS_FAILED(rv)) continue; + + // Get the values for this account. + nsAutoCString nativeKeyName; + NS_CopyUnicodeToNative(keyName, nativeKeyName); + IMPORT_LOG1("Opened Outlook account: %s\n", nativeKeyName.get()); + + nsCOMPtr account; + nsAutoString value; + rv = subKey->ReadStringValue(u"IMAP Server"_ns, value); + if (NS_SUCCEEDED(rv) && + DoIMAPServer(accMgr, subKey, value, getter_AddRefs(account))) + accounts++; + + rv = subKey->ReadStringValue(u"POP3 Server"_ns, value); + if (NS_SUCCEEDED(rv) && + DoPOP3Server(accMgr, subKey, value, getter_AddRefs(account))) { + popCount++; + accounts++; + if (aAccount && account) { + // If we created a mail account, get rid of it since + // we have 2 POP accounts! + if (popCount > 1) + NS_RELEASE(*aAccount); + else + NS_ADDREF(*aAccount = account); + } + } + + // Is this the default account? + if (account && keyName.Equals(defMailName)) + accMgr->SetDefaultAccount(account); + } + + // Now save the new acct info to pref file. + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + + return accounts != 0; +} + +nsresult OutlookSettings::GetAccountName(nsIWindowsRegKey* aKey, + const nsString& aDefaultName, + nsAString& aAccountName) { + nsresult rv; + rv = aKey->ReadStringValue(u"Account Name"_ns, aAccountName); + if (NS_FAILED(rv)) aAccountName.Assign(aDefaultName); + + return NS_OK; +} + +bool OutlookSettings::DoIMAPServer(nsIMsgAccountManager* aMgr, + nsIWindowsRegKey* aKey, + const nsString& aServerName, + nsIMsgAccount** aAccount) { + nsAutoString userName; + nsresult rv; + rv = aKey->ReadStringValue(u"IMAP User Name"_ns, userName); + if (NS_FAILED(rv)) return false; + + bool result = false; + + // I now have a user name/server name pair, find out if it already exists? + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + nsCOMPtr in; + aMgr->FindServer(nativeUserName, nativeServerName, "imap"_ns, 0, + getter_AddRefs(in)); + if (!in) { + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, nativeServerName, "imap"_ns, + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + rv = in->SetType("imap"_ns); + // TODO SSL, auth method + + IMPORT_LOG2("Created IMAP server named: %s, userName: %s\n", + nativeServerName.get(), nativeUserName.get()); + + nsAutoString prettyName; + if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName))) + rv = in->SetPrettyName(prettyName); + // We have a server, create an account. + nsCOMPtr account; + rv = aMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0( + "Created an account and set the IMAP server as the incoming " + "server\n"); + + // Fiddle with the identities + SetIdentities(aMgr, account, aKey); + result = true; + if (aAccount) account.forget(aAccount); + } + } + } else + result = true; + + return result; +} + +bool OutlookSettings::DoPOP3Server(nsIMsgAccountManager* aMgr, + nsIWindowsRegKey* aKey, + const nsString& aServerName, + nsIMsgAccount** aAccount) { + nsAutoString userName; + nsresult rv; + rv = aKey->ReadStringValue(u"POP3 User Name"_ns, userName); + if (NS_FAILED(rv)) return false; + + // I now have a user name/server name pair, find out if it already exists? + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(userName, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServerName, nativeServerName); + nsCOMPtr in; + aMgr->FindServer(nativeUserName, nativeServerName, "pop3"_ns, 0, + getter_AddRefs(in)); + if (in) return true; + + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, nativeServerName, "pop3"_ns, + getter_AddRefs(in)); + rv = in->SetType("pop3"_ns); + + // TODO SSL, auth method + + nsCOMPtr pop3Server = do_QueryInterface(in); + NS_ENSURE_SUCCESS(rv, false); + + // set local folders as the Inbox to use for this POP3 server + nsCOMPtr localFoldersServer; + aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + + if (!localFoldersServer) { + // XXX: We may need to move this local folder creation code to the generic + // nsImportSettings code if the other import modules end up needing to do + // this too. if Local Folders does not exist already, create it + rv = aMgr->CreateLocalMailAccount(); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + } + + // now get the account for this server + nsCOMPtr localFoldersAccount; + aMgr->FindAccountForServer(localFoldersServer, + getter_AddRefs(localFoldersAccount)); + if (localFoldersAccount) { + nsCString localFoldersAcctKey; + localFoldersAccount->GetKey(localFoldersAcctKey); + pop3Server->SetDeferredToAccount(localFoldersAcctKey); + pop3Server->SetDeferGetNewMail(true); + } + + IMPORT_LOG2("Created POP3 server named: %s, userName: %s\n", + nativeServerName.get(), nativeUserName.get()); + + nsString prettyName; + rv = GetAccountName(aKey, aServerName, prettyName); + if (NS_FAILED(rv)) return false; + + rv = in->SetPrettyName(prettyName); + // We have a server, create an account. + nsCOMPtr account; + rv = aMgr->CreateAccount(getter_AddRefs(account)); + if (NS_FAILED(rv)) return false; + + rv = account->SetIncomingServer(in); + + IMPORT_LOG0( + "Created a new account and set the incoming server to the POP3 " + "server.\n"); + + uint32_t leaveOnServer; + rv = aKey->ReadIntValue(u"Leave Mail On Server"_ns, &leaveOnServer); + if (NS_SUCCEEDED(rv)) + pop3Server->SetLeaveMessagesOnServer(leaveOnServer == 1 ? true : false); + + // Fiddle with the identities + SetIdentities(aMgr, account, aKey); + + if (aAccount) account.forget(aAccount); + + return true; +} + +void OutlookSettings::SetIdentities(nsIMsgAccountManager* aMgr, + nsIMsgAccount* aAcc, + nsIWindowsRegKey* aKey) { + // Get the relevant information for an identity + nsAutoString name; + aKey->ReadStringValue(u"SMTP Display Name"_ns, name); + + nsAutoString server; + aKey->ReadStringValue(u"SMTP Server"_ns, server); + + nsAutoString email; + aKey->ReadStringValue(u"SMTP Email Address"_ns, email); + + nsAutoString reply; + aKey->ReadStringValue(u"SMTP Reply To Email Address"_ns, reply); + + nsAutoString userName; + aKey->ReadStringValue(u"SMTP User Name"_ns, userName); + + nsAutoString orgName; + aKey->ReadStringValue(u"SMTP Organization Name"_ns, orgName); + + nsresult rv; + nsCOMPtr id; + if (!email.IsEmpty() && !name.IsEmpty() && !server.IsEmpty()) { + // The default identity, nor any other identities matched, + // create a new one and add it to the account. + rv = aMgr->CreateIdentity(getter_AddRefs(id)); + if (id) { + id->SetFullName(name); + id->SetOrganization(orgName); + + nsAutoCString nativeEmail; + NS_CopyUnicodeToNative(email, nativeEmail); + id->SetEmail(nativeEmail); + if (!reply.IsEmpty()) { + nsAutoCString nativeReply; + NS_CopyUnicodeToNative(reply, nativeReply); + id->SetReplyTo(nativeReply); + } + aAcc->AddIdentity(id); + + nsAutoCString nativeName; + NS_CopyUnicodeToNative(name, nativeName); + IMPORT_LOG0("Created identity and added to the account\n"); + IMPORT_LOG1("\tname: %s\n", nativeName.get()); + IMPORT_LOG1("\temail: %s\n", nativeEmail.get()); + } + } + + if (userName.IsEmpty()) { + nsCOMPtr incomingServer; + rv = aAcc->GetIncomingServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv) && incomingServer) { + nsAutoCString nativeUserName; + rv = incomingServer->GetUsername(nativeUserName); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Unable to get UserName from incomingServer"); + NS_CopyNativeToUnicode(nativeUserName, userName); + } + } + + SetSmtpServer(aMgr, aAcc, id, server, userName); +} + +nsresult OutlookSettings::SetSmtpServerKey(nsIMsgIdentity* aId, + nsISmtpServer* aServer) { + nsAutoCString smtpServerKey; + aServer->GetKey(getter_Copies(smtpServerKey)); + return aId->SetSmtpServerKey(smtpServerKey); +} + +nsresult OutlookSettings::SetSmtpServer(nsIMsgAccountManager* aMgr, + nsIMsgAccount* aAcc, + nsIMsgIdentity* aId, + const nsString& aServer, + const nsString& aUser) { + nsresult rv; + nsCOMPtr smtpService( + do_GetService("@mozilla.org/messengercompose/smtp;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(aUser, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServer, nativeServerName); + nsCOMPtr foundServer; + rv = smtpService->FindServer(nativeUserName.get(), nativeServerName.get(), + getter_AddRefs(foundServer)); + if (NS_SUCCEEDED(rv) && foundServer) { + if (aId) SetSmtpServerKey(aId, foundServer); + IMPORT_LOG1("SMTP server already exists: %s\n", nativeServerName.get()); + return rv; + } + + nsCOMPtr smtpServer; + rv = smtpService->CreateServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv, rv); + + smtpServer->SetHostname(nativeServerName); + if (!aUser.IsEmpty()) smtpServer->SetUsername(nativeUserName); + + if (aId) SetSmtpServerKey(aId, smtpServer); + + // TODO SSL, auth method + IMPORT_LOG1("Created new SMTP server: %s\n", nativeServerName.get()); + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsOutlookSettings.h b/comm/mailnews/import/src/nsOutlookSettings.h new file mode 100644 index 0000000000..7410200c3d --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookSettings.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsOutlookSettings_h___ +#define nsOutlookSettings_h___ + +#include "nsIImportSettings.h" + +class nsOutlookSettings : public nsIImportSettings { + public: + nsOutlookSettings(); + + static nsresult Create(nsIImportSettings** aImport); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIImportSettings interface + NS_DECL_NSIIMPORTSETTINGS + + private: + virtual ~nsOutlookSettings(); +}; + +#endif /* nsOutlookSettings_h___ */ diff --git a/comm/mailnews/import/src/nsOutlookStringBundle.cpp b/comm/mailnews/import/src/nsOutlookStringBundle.cpp new file mode 100644 index 0000000000..140eb9a541 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookStringBundle.cpp @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#include "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIStringBundle.h" +#include "nsOutlookStringBundle.h" +#include "mozilla/Components.h" + +#define OUTLOOK_MSGS_URL \ + "chrome://messenger/locale/outlookImportMsgs.properties" + +nsCOMPtr nsOutlookStringBundle::m_pBundle = nullptr; + +void nsOutlookStringBundle::GetStringBundle(void) { + if (m_pBundle) return; + + nsCOMPtr sBundleService = + mozilla::components::StringBundle::Service(); + if (sBundleService) { + sBundleService->CreateBundle(OUTLOOK_MSGS_URL, getter_AddRefs(m_pBundle)); + } +} + +void nsOutlookStringBundle::GetStringByID(int32_t stringID, nsString& result) { + char16_t* ptrv = GetStringByID(stringID); + result = ptrv; + FreeString(ptrv); +} + +char16_t* nsOutlookStringBundle::GetStringByID(int32_t stringID) { + if (m_pBundle) GetStringBundle(); + + if (m_pBundle) { + nsAutoString str; + nsresult rv = m_pBundle->GetStringFromID(stringID, str); + + if (NS_SUCCEEDED(rv)) return ToNewUnicode(str); + } + + nsString resultString; + resultString.AppendLiteral("[StringID "); + resultString.AppendInt(stringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsOutlookStringBundle::Cleanup(void) { m_pBundle = nullptr; } diff --git a/comm/mailnews/import/src/nsOutlookStringBundle.h b/comm/mailnews/import/src/nsOutlookStringBundle.h new file mode 100644 index 0000000000..491bbfaa64 --- /dev/null +++ b/comm/mailnews/import/src/nsOutlookStringBundle.h @@ -0,0 +1,36 @@ +/* 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 _nsOutlookStringBundle_H__ +#define _nsOutlookStringBundle_H__ + +#include "nsCRTGlue.h" +#include "nsString.h" + +class nsIStringBundle; + +class nsOutlookStringBundle { + public: + static char16_t* GetStringByID(int32_t stringID); + static void GetStringByID(int32_t stringID, nsString& result); + static void GetStringBundle(void); + static void FreeString(char16_t* pStr) { free(pStr); } + static void Cleanup(void); + + private: + static nsCOMPtr m_pBundle; +}; + +#define OUTLOOKIMPORT_NAME 2000 +#define OUTLOOKIMPORT_DESCRIPTION 2010 +#define OUTLOOKIMPORT_MAILBOX_SUCCESS 2002 +#define OUTLOOKIMPORT_MAILBOX_BADPARAM 2003 +#define OUTLOOKIMPORT_MAILBOX_CONVERTERROR 2004 +#define OUTLOOKIMPORT_ADDRNAME 2005 +#define OUTLOOKIMPORT_ADDRESS_SUCCESS 2006 +#define OUTLOOKIMPORT_ADDRESS_BADPARAM 2007 +#define OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE 2008 +#define OUTLOOKIMPORT_ADDRESS_CONVERTERROR 2009 + +#endif /* _nsOutlookStringBundle_H__ */ diff --git a/comm/mailnews/import/src/nsTextAddress.cpp b/comm/mailnews/import/src/nsTextAddress.cpp new file mode 100644 index 0000000000..497bd5b398 --- /dev/null +++ b/comm/mailnews/import/src/nsTextAddress.cpp @@ -0,0 +1,426 @@ +/* -*- 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/. */ + +#include "nsTextAddress.h" +#include "nsIAbCard.h" +#include "nsIAbDirectory.h" +#include "nsNativeCharsetUtils.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsNetUtil.h" +#include "nsMsgI18N.h" +#include "nsMsgUtils.h" +#include "nsIConverterInputStream.h" +#include "nsIUnicharLineInputStream.h" +#include "nsMsgUtils.h" + +#include "ImportDebug.h" +#include "plstr.h" +#include "msgCore.h" + +#define kWhitespace " \t\b\r\n" + +nsTextAddress::nsTextAddress() { + m_LFCount = 0; + m_CRCount = 0; +} + +nsTextAddress::~nsTextAddress() {} + +nsresult nsTextAddress::GetUnicharLineStreamForFile( + nsIFile* aFile, nsIInputStream* aInputStream, + nsIUnicharLineInputStream** aStream) { + nsAutoCString charset; + nsresult rv = MsgDetectCharsetFromFile(aFile, charset); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr converterStream = + do_CreateInstance("@mozilla.org/intl/converter-input-stream;1", &rv); + if (NS_SUCCEEDED(rv)) { + rv = converterStream->Init( + aInputStream, charset.get(), 8192, + nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER); + } + + return CallQueryInterface(converterStream, aStream); +} + +nsresult nsTextAddress::ImportAddresses(bool* pAbort, const char16_t* pName, + nsIFile* pSrc, + nsIAbDirectory* pDirectory, + nsIImportFieldMap* fieldMap, + nsString& errors, uint32_t* pProgress) { + // Open the source file for reading, read each line and process it! + m_directory = pDirectory; + m_fieldMap = fieldMap; + + nsCOMPtr inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + // Here we use this to work out the size of the file, so we can update + // an integer as we go through the file which will update a progress + // bar if required by the caller. + uint64_t bytesLeft = 0; + rv = inputStream->Available(&bytesLeft); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking address file for size\n"); + inputStream->Close(); + return rv; + } + + uint64_t totalBytes = bytesLeft; + bool skipRecord = false; + + rv = m_fieldMap->GetSkipFirstRecord(&skipRecord); + if (NS_FAILED(rv)) { + IMPORT_LOG0( + "*** Error checking to see if we should skip the first record\n"); + return rv; + } + + nsCOMPtr lineStream; + rv = GetUnicharLineStreamForFile(pSrc, inputStream, + getter_AddRefs(lineStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening converter stream for importer\n"); + return rv; + } + + bool more = true; + nsAutoString line; + + // Skip the first record if the user has requested it. + if (skipRecord) rv = ReadRecord(lineStream, line, &more); + + while (!(*pAbort) && more && NS_SUCCEEDED(rv)) { + // Read the line in + rv = ReadRecord(lineStream, line, &more); + if (NS_SUCCEEDED(rv)) { + // Now process it to add it to the database + rv = ProcessLine(line, errors); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error processing text record.\n"); + } + } + if (NS_SUCCEEDED(rv) && pProgress) { + // This won't be totally accurate, but its the best we can do + // considering that lineStream won't give us how many bytes + // are actually left. + bytesLeft -= line.Length(); + *pProgress = std::min(totalBytes - bytesLeft, uint64_t(PR_UINT32_MAX)); + } + } + + inputStream->Close(); + + if (NS_FAILED(rv)) { + IMPORT_LOG0( + "*** Error reading the address book - probably incorrect ending\n"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsTextAddress::ReadRecord(nsIUnicharLineInputStream* aLineStream, + nsAString& aLine, bool* aMore) { + bool more = true; + uint32_t numQuotes = 0; + nsresult rv; + nsAutoString line; + + // ensure aLine is empty + aLine.Truncate(); + + do { + if (!more) { + // No more, so we must have an incorrect file. + rv = NS_ERROR_FAILURE; + } else { + // Read the line and append it + rv = aLineStream->ReadLine(line, &more); + if (NS_SUCCEEDED(rv)) { + if (!aLine.IsEmpty()) aLine.AppendLiteral(MSG_LINEBREAK); + aLine.Append(line); + + numQuotes += line.CountChar(char16_t('"')); + } + } + // Continue whilst everything is ok, and we have an odd number of quotes. + } while (NS_SUCCEEDED(rv) && (numQuotes % 2 != 0)); + + *aMore = more; + return rv; +} + +nsresult nsTextAddress::ReadRecordNumber(nsIFile* aSrc, nsAString& aLine, + int32_t rNum) { + nsCOMPtr inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + int32_t rIndex = 0; + uint64_t bytesLeft = 0; + + rv = inputStream->Available(&bytesLeft); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking address file for eof\n"); + inputStream->Close(); + return rv; + } + + nsCOMPtr lineStream; + rv = GetUnicharLineStreamForFile(aSrc, inputStream, + getter_AddRefs(lineStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening converter stream for importer\n"); + return rv; + } + + bool more = true; + + while (more && (rIndex <= rNum)) { + rv = ReadRecord(lineStream, aLine, &more); + if (NS_FAILED(rv)) { + inputStream->Close(); + return rv; + } + if (rIndex == rNum) { + inputStream->Close(); + return NS_OK; + } + + rIndex++; + } + + return NS_ERROR_FAILURE; +} + +int32_t nsTextAddress::CountFields(const nsAString& aLine, char16_t delim) { + int32_t pos = 0; + int32_t maxLen = aLine.Length(); + int32_t count = 0; + char16_t tab = char16_t('\t'); + char16_t doubleQuote = char16_t('"'); + + if (delim == tab) tab = char16_t('\0'); + + while (pos < maxLen) { + while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) && + (pos < maxLen)) { + pos++; + } + if ((pos < maxLen) && (aLine[pos] == doubleQuote)) { + pos++; + while ((pos < maxLen) && (aLine[pos] != doubleQuote)) { + pos++; + if (((pos + 1) < maxLen) && (aLine[pos] == doubleQuote) && + (aLine[pos + 1] == doubleQuote)) { + pos += 2; + } + } + if (pos < maxLen) pos++; + } + while ((pos < maxLen) && (aLine[pos] != delim)) pos++; + + count++; + pos++; + } + + return count; +} + +bool nsTextAddress::GetField(const nsAString& aLine, int32_t index, + nsString& field, char16_t delim) { + bool result = false; + int32_t pos = 0; + int32_t maxLen = aLine.Length(); + char16_t tab = char16_t('\t'); + char16_t doubleQuote = char16_t('"'); + + field.Truncate(); + + if (delim == tab) tab = 0; + + while (index && (pos < maxLen)) { + while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) && + (pos < maxLen)) { + pos++; + } + if (pos >= maxLen) break; + if (aLine[pos] == doubleQuote) { + do { + pos++; + if (((pos + 1) < maxLen) && (aLine[pos] == doubleQuote) && + (aLine[pos + 1] == doubleQuote)) { + pos += 2; + } + } while ((pos < maxLen) && (aLine[pos] != doubleQuote)); + if (pos < maxLen) pos++; + } + if (pos >= maxLen) break; + + while ((pos < maxLen) && (aLine[pos] != delim)) pos++; + + if (pos >= maxLen) break; + + index--; + pos++; + } + + if (pos >= maxLen) return result; + + result = true; + + while ((pos < maxLen) && ((aLine[pos] == ' ') || (aLine[pos] == tab))) pos++; + + int32_t fLen = 0; + int32_t startPos = pos; + bool quoted = false; + if (aLine[pos] == '"') { + startPos++; + fLen = -1; + do { + pos++; + fLen++; + if (((pos + 1) < maxLen) && (aLine[pos] == doubleQuote) && + (aLine[pos + 1] == doubleQuote)) { + quoted = true; + pos += 2; + fLen += 2; + } + } while ((pos < maxLen) && (aLine[pos] != doubleQuote)); + } else { + while ((pos < maxLen) && (aLine[pos] != delim)) { + pos++; + fLen++; + } + } + + if (!fLen) { + return result; + } + + field.Append(nsDependentSubstring(aLine, startPos, fLen)); + field.Trim(kWhitespace); + + if (quoted) { + int32_t offset = field.Find(u"\"\""); + while (offset != -1) { + field.Cut(offset, 1); + offset = field.Find(u"\"\"", offset + 1); + } + } + + return result; +} + +nsresult nsTextAddress::DetermineDelim(nsIFile* aSrc) { + nsCOMPtr inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + int32_t lineCount = 0; + int32_t tabCount = 0; + int32_t commaCount = 0; + int32_t tabLines = 0; + int32_t commaLines = 0; + nsAutoString line; + bool more = true; + + nsCOMPtr lineStream; + rv = GetUnicharLineStreamForFile(aSrc, inputStream, + getter_AddRefs(lineStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening converter stream for importer\n"); + return rv; + } + + while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) { + rv = lineStream->ReadLine(line, &more); + if (NS_SUCCEEDED(rv)) { + tabCount = CountFields(line, char16_t('\t')); + commaCount = CountFields(line, char16_t(',')); + if (tabCount > commaCount) + tabLines++; + else if (commaCount) + commaLines++; + } + lineCount++; + } + + rv = inputStream->Close(); + + if (tabLines > commaLines) + m_delim = char16_t('\t'); + else + m_delim = char16_t(','); + + IMPORT_LOG2("Tab count = %d, Comma count = %d\n", tabLines, commaLines); + + return rv; +} + +/* + This is where the real work happens! + Go through the field map and set the data in a new database row +*/ +nsresult nsTextAddress::ProcessLine(const nsAString& aLine, nsString& errors) { + if (!m_fieldMap) { + IMPORT_LOG0("*** Error, text import needs a field map\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + + // Wait until we get our first non-empty field, then create a new row, + // fill in the data, then add the row to the database. + nsCOMPtr newCard; + nsAutoString fieldVal; + int32_t fieldNum; + int32_t numFields = 0; + bool active; + rv = m_fieldMap->GetMapSize(&numFields); + for (int32_t i = 0; (i < numFields) && NS_SUCCEEDED(rv); i++) { + active = false; + rv = m_fieldMap->GetFieldMap(i, &fieldNum); + if (NS_SUCCEEDED(rv)) rv = m_fieldMap->GetFieldActive(i, &active); + if (NS_SUCCEEDED(rv) && active) { + if (GetField(aLine, i, fieldVal, m_delim)) { + if (!fieldVal.IsEmpty()) { + if (!newCard) { + newCard = do_CreateInstance( + "@mozilla.org/addressbook/cardproperty;1", &rv); + } + if (newCard) { + rv = m_fieldMap->SetFieldValue(m_directory, newCard, fieldNum, + fieldVal); + } + } + } else { + break; + } + } else if (active) { + IMPORT_LOG1("*** Error getting field map for index %ld\n", (long)i); + } + } + + nsIAbCard* outCard; + rv = m_directory->AddCard(newCard, &outCard); + + return rv; +} diff --git a/comm/mailnews/import/src/nsTextAddress.h b/comm/mailnews/import/src/nsTextAddress.h new file mode 100644 index 0000000000..58b37755b3 --- /dev/null +++ b/comm/mailnews/import/src/nsTextAddress.h @@ -0,0 +1,60 @@ +/* -*- 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 nsTextAddress_h__ +#define nsTextAddress_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIImportFieldMap.h" +#include "nsIImportService.h" + +class nsIAbDirectory; +class nsIFile; +class nsIInputStream; +class nsIUnicharLineInputStream; + +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// + +class nsTextAddress { + public: + nsTextAddress(); + virtual ~nsTextAddress(); + + nsresult ImportAddresses(bool* pAbort, const char16_t* pName, nsIFile* pSrc, + nsIAbDirectory* pDirectory, + nsIImportFieldMap* fieldMap, nsString& errors, + uint32_t* pProgress); + + nsresult DetermineDelim(nsIFile* pSrc); + char16_t GetDelim(void) { return m_delim; } + + static nsresult ReadRecordNumber(nsIFile* pSrc, nsAString& aLine, + int32_t rNum); + static bool GetField(const nsAString& aLine, int32_t index, nsString& field, + char16_t delim); + + private: + nsresult ProcessLine(const nsAString& aLine, nsString& errors); + + static int32_t CountFields(const nsAString& aLine, char16_t delim); + static nsresult ReadRecord(nsIUnicharLineInputStream* pSrc, nsAString& aLine, + bool* aMore); + static nsresult GetUnicharLineStreamForFile( + nsIFile* aFile, nsIInputStream* aInputStream, + nsIUnicharLineInputStream** aStream); + + char16_t m_delim; + int32_t m_LFCount; + int32_t m_CRCount; + nsCOMPtr m_directory; + nsCOMPtr m_fieldMap; + nsCOMPtr m_pService; +}; + +#endif /* nsTextAddress_h__ */ diff --git a/comm/mailnews/import/src/nsTextImport.cpp b/comm/mailnews/import/src/nsTextImport.cpp new file mode 100644 index 0000000000..961f1631ae --- /dev/null +++ b/comm/mailnews/import/src/nsTextImport.cpp @@ -0,0 +1,646 @@ +/* -*- 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/. */ + +/* + * Text import addressbook interfaces + */ + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsIImportService.h" +#include "nsMsgI18N.h" +#include "nsTextImport.h" +#include "nsIImportGeneric.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportFieldMap.h" +#include "nsIAbLDIFService.h" +#include "nsTextFormatter.h" +#include "nsImportStringBundle.h" +#include "nsTextAddress.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "ImportDebug.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" + +#define TEXT_MSGS_URL "chrome://messenger/locale/textImportMsgs.properties" +#define TEXTIMPORT_NAME 2000 +#define TEXTIMPORT_DESCRIPTION 2001 +#define TEXTIMPORT_ADDRESS_NAME 2002 +#define TEXTIMPORT_ADDRESS_SUCCESS 2003 +#define TEXTIMPORT_ADDRESS_BADPARAM 2004 +#define TEXTIMPORT_ADDRESS_BADSOURCEFILE 2005 +#define TEXTIMPORT_ADDRESS_CONVERTERROR 2006 + +class ImportAddressImpl final : public nsIImportAddressBooks { + public: + explicit ImportAddressImpl(nsIStringBundle* aStringBundle); + + static nsresult Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + NS_IMETHOD GetSupportsMultiple(bool* _retval) override { + *_retval = false; + return NS_OK; + } + + NS_IMETHOD GetAutoFind(char16_t** description, bool* _retval) override; + + NS_IMETHOD GetNeedsFieldMap(nsIFile* location, bool* _retval) override; + + NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found, + bool* userVerify) override; + + NS_IMETHOD FindAddressBooks( + nsIFile* location, + nsTArray>& books) override; + + NS_IMETHOD InitFieldMap(nsIImportFieldMap* fieldMap) override; + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor* source, + nsIAbDirectory* destination, + nsIImportFieldMap* fieldMap, + nsISupports* aSupportService, + char16_t** errorLog, char16_t** successLog, + bool* fatalError) override; + + NS_IMETHOD GetImportProgress(uint32_t* _retval) override; + + NS_IMETHOD GetSampleData(int32_t index, bool* pFound, + char16_t** pStr) override; + + NS_IMETHOD SetSampleLocation(nsIFile*) override; + + private: + void ClearSampleFile(void); + void SaveFieldMap(nsIImportFieldMap* pMap); + + static void ReportSuccess(nsString& name, nsString* pStream, + nsIStringBundle* pBundle); + static void SetLogs(nsString& success, nsString& error, char16_t** pError, + char16_t** pSuccess); + static void ReportError(int32_t errorNum, nsString& name, nsString* pStream, + nsIStringBundle* pBundle); + static void SanitizeSampleData(nsString& val); + + private: + ~ImportAddressImpl() {} + nsTextAddress m_text; + bool m_haveDelim; + nsCOMPtr m_fileLoc; + nsCOMPtr m_notProxyBundle; + char16_t m_delim; + uint32_t m_bytesImported; +}; + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +nsTextImport::nsTextImport() { + IMPORT_LOG0("nsTextImport Module Created\n"); + + nsImportStringBundle::GetStringBundle(TEXT_MSGS_URL, + getter_AddRefs(m_stringBundle)); +} + +nsTextImport::~nsTextImport() { IMPORT_LOG0("nsTextImport Module Deleted\n"); } + +NS_IMPL_ISUPPORTS(nsTextImport, nsIImportModule) + +NS_IMETHODIMP nsTextImport::GetName(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_NAME, m_stringBundle); + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetDescription(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_DESCRIPTION, + m_stringBundle); + + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetSupports(char** supports) { + NS_ENSURE_ARG_POINTER(supports); + *supports = strdup(kTextSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetSupportsUpgrade(bool* pUpgrade) { + NS_ASSERTION(pUpgrade != nullptr, "null ptr"); + if (!pUpgrade) return NS_ERROR_NULL_POINTER; + + *pUpgrade = false; + return NS_OK; +} + +NS_IMETHODIMP nsTextImport::GetImportInterface(const char* pImportType, + nsISupports** ppInterface) { + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + + *ppInterface = nullptr; + nsresult rv; + + if (!strcmp(pImportType, "addressbook")) { + // create the nsIImportMail interface and return it! + nsCOMPtr pAddress; + nsCOMPtr pGeneric; + rv = ImportAddressImpl::Create(getter_AddRefs(pAddress), m_stringBundle); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric)); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + nsCOMPtr pInterface(do_QueryInterface(pGeneric)); + pInterface.forget(ppInterface); + } + } + } + return rv; + } + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// + +nsresult ImportAddressImpl::Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new ImportAddressImpl(aStringBundle)); + return NS_OK; +} + +ImportAddressImpl::ImportAddressImpl(nsIStringBundle* aStringBundle) + : m_notProxyBundle(aStringBundle) { + m_haveDelim = false; +} + +NS_IMPL_ISUPPORTS(ImportAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP ImportAddressImpl::GetAutoFind(char16_t** addrDescription, + bool* _retval) { + NS_ASSERTION(addrDescription != nullptr, "null ptr"); + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!addrDescription || !_retval) return NS_ERROR_NULL_POINTER; + + nsString str; + *_retval = false; + + if (!m_notProxyBundle) return NS_ERROR_FAILURE; + + nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_NAME, m_notProxyBundle, + str); + *addrDescription = ToNewUnicode(str); + return NS_OK; +} + +NS_IMETHODIMP ImportAddressImpl::GetDefaultLocation(nsIFile** ppLoc, + bool* found, + bool* userVerify) { + NS_ASSERTION(found != nullptr, "null ptr"); + NS_ASSERTION(ppLoc != nullptr, "null ptr"); + NS_ASSERTION(userVerify != nullptr, "null ptr"); + if (!found || !userVerify || !ppLoc) return NS_ERROR_NULL_POINTER; + + *ppLoc = nullptr; + *found = false; + *userVerify = true; + return NS_OK; +} + +NS_IMETHODIMP ImportAddressImpl::FindAddressBooks( + nsIFile* pLoc, nsTArray>& books) { + NS_ASSERTION(pLoc != nullptr, "null ptr"); + if (!pLoc) return NS_ERROR_NULL_POINTER; + + books.Clear(); + ClearSampleFile(); + + bool exists = false; + nsresult rv = pLoc->Exists(&exists); + if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE; + + bool isFile = false; + rv = pLoc->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) return NS_ERROR_FAILURE; + + rv = m_text.DetermineDelim(pLoc); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error determining delimitter\n"); + return rv; + } + m_haveDelim = true; + m_delim = m_text.GetDelim(); + + m_fileLoc = pLoc; + + /* Build an address book descriptor based on the file passed in! */ + nsString name; + m_fileLoc->GetLeafName(name); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed getting leaf name of file\n"); + return rv; + } + + int32_t idx = name.RFindChar('.'); + if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) { + name.SetLength(idx); + } + + nsCOMPtr desc; + + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to obtain the import service\n"); + return rv; + } + + rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc)); + if (NS_SUCCEEDED(rv)) { + int64_t sz = 0; + pLoc->GetFileSize(&sz); + desc->SetPreferredName(name); + desc->SetSize((uint32_t)sz); + desc->SetAbFile(m_fileLoc); + books.AppendElement(desc); + } + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error creating address book descriptor for text import\n"); + return rv; + } + return NS_OK; +} + +void ImportAddressImpl::ReportSuccess(nsString& name, nsString* pStream, + nsIStringBundle* pBundle) { + if (!pStream) return; + + // load the success string + char16_t* pFmt = + nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_SUCCESS, pBundle); + + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportAddressImpl::ReportError(int32_t errorNum, nsString& name, + nsString* pStream, + nsIStringBundle* pBundle) { + if (!pStream) return; + + // load the error string + char16_t* pFmt = nsImportStringBundle::GetStringByID(errorNum, pBundle); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportAddressImpl::SetLogs(nsString& success, nsString& error, + char16_t** pError, char16_t** pSuccess) { + if (pError) *pError = ToNewUnicode(error); + if (pSuccess) *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP +ImportAddressImpl::ImportAddressBook(nsIImportABDescriptor* pSource, + nsIAbDirectory* pDestination, + nsIImportFieldMap* fieldMap, + nsISupports* aSupportService, + char16_t** pErrorLog, + char16_t** pSuccessLog, bool* fatalError) { + NS_ASSERTION(pSource != nullptr, "null ptr"); + NS_ASSERTION(pDestination != nullptr, "null ptr"); + NS_ASSERTION(fatalError != nullptr, "null ptr"); + + m_bytesImported = 0; + + nsString success, error; + if (!pSource || !pDestination || !fatalError) { + IMPORT_LOG0("*** Bad param passed to text address import\n"); + nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_BADPARAM, + m_notProxyBundle, error); + + SetLogs(success, error, pErrorLog, pSuccessLog); + + if (fatalError) *fatalError = true; + + return NS_ERROR_NULL_POINTER; + } + + ClearSampleFile(); + + bool addrAbort = false; + nsString name; + pSource->GetPreferredName(name); + + uint32_t addressSize = 0; + pSource->GetSize(&addressSize); + if (addressSize == 0) { + IMPORT_LOG0("Address book size is 0, skipping import.\n"); + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + nsCOMPtr inFile; + if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) { + ReportError(TEXTIMPORT_ADDRESS_BADSOURCEFILE, name, &error, + m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + if (!aSupportService) { + IMPORT_LOG0("Missing support service to import call"); + return NS_ERROR_FAILURE; + } + + bool isLDIF = false; + nsresult rv; + nsCOMPtr ldifService( + do_QueryInterface(aSupportService, &rv)); + + if (NS_SUCCEEDED(rv)) { + rv = ldifService->IsLDIFFile(inFile, &isLDIF); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error reading address file\n"); + } + } + + if (NS_FAILED(rv)) { + ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error, + m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return rv; + } + + if (isLDIF) { + if (ldifService) + rv = ldifService->ImportLDIFFile(pDestination, inFile, false, + &m_bytesImported); + else + return NS_ERROR_FAILURE; + } else { + rv = m_text.ImportAddresses(&addrAbort, name.get(), inFile, pDestination, + fieldMap, error, &m_bytesImported); + SaveFieldMap(fieldMap); + } + + if (NS_SUCCEEDED(rv) && error.IsEmpty()) { + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } else { + ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error, + m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } + + IMPORT_LOG0("*** Text address import done\n"); + return rv; +} + +NS_IMETHODIMP ImportAddressImpl::GetImportProgress(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_bytesImported; + return NS_OK; +} + +NS_IMETHODIMP ImportAddressImpl::GetNeedsFieldMap(nsIFile* aLocation, + bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_ARG_POINTER(aLocation); + + *_retval = true; + bool exists = false; + bool isFile = false; + + nsresult rv = aLocation->Exists(&exists); + rv = aLocation->IsFile(&isFile); + + if (!exists || !isFile) return NS_ERROR_FAILURE; + + bool isLDIF = false; + nsCOMPtr ldifService = + do_GetService("@mozilla.org/addressbook/abldifservice;1", &rv); + + if (NS_SUCCEEDED(rv)) rv = ldifService->IsLDIFFile(aLocation, &isLDIF); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error determining if file is of type LDIF\n"); + return rv; + } + + if (isLDIF) *_retval = false; + + return NS_OK; +} + +void ImportAddressImpl::SanitizeSampleData(nsString& val) { + // remove any line-feeds... + int32_t offset = val.Find(u"\x0D\x0A"_ns); + while (offset != -1) { + val.Replace(offset, 2, u", "_ns); + offset = val.Find(u"\x0D\x0A"_ns, offset + 2); + } + offset = val.FindChar(13); + while (offset != -1) { + val.Replace(offset, 1, ','); + offset = val.FindChar(13, offset + 2); + } + offset = val.FindChar(10); + while (offset != -1) { + val.Replace(offset, 1, ','); + offset = val.FindChar(10, offset + 2); + } +} + +NS_IMETHODIMP ImportAddressImpl::GetSampleData(int32_t index, bool* pFound, + char16_t** pStr) { + NS_ASSERTION(pFound != nullptr, "null ptr"); + NS_ASSERTION(pStr != nullptr, "null ptr"); + if (!pFound || !pStr) return NS_ERROR_NULL_POINTER; + + if (!m_fileLoc) { + IMPORT_LOG0("*** Error, called GetSampleData before SetSampleLocation\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + *pStr = nullptr; + char16_t term = 0; + + if (!m_haveDelim) { + rv = m_text.DetermineDelim(m_fileLoc); + NS_ENSURE_SUCCESS(rv, rv); + m_haveDelim = true; + m_delim = m_text.GetDelim(); + } + + bool fileExists; + rv = m_fileLoc->Exists(&fileExists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!fileExists) { + *pFound = false; + *pStr = NS_xstrdup(&term); + return NS_OK; + } + + nsAutoString line; + rv = nsTextAddress::ReadRecordNumber(m_fileLoc, line, index); + if (NS_SUCCEEDED(rv)) { + nsString str; + nsString field; + int32_t fNum = 0; + while (nsTextAddress::GetField(line, fNum, field, m_delim)) { + if (fNum) str.Append(char16_t('\n')); + SanitizeSampleData(field); + str.Append(field); + fNum++; + field.Truncate(); + } + + *pStr = ToNewUnicode(str); + *pFound = true; + + /* IMPORT_LOG1("Sample data: %S\n", str.get()); */ + } else { + *pFound = false; + *pStr = NS_xstrdup(&term); + } + + return NS_OK; +} + +NS_IMETHODIMP ImportAddressImpl::SetSampleLocation(nsIFile* pLocation) { + NS_ENSURE_ARG_POINTER(pLocation); + + m_fileLoc = pLocation; + m_haveDelim = false; + return NS_OK; +} + +void ImportAddressImpl::ClearSampleFile(void) { + m_fileLoc = nullptr; + m_haveDelim = false; +} + +NS_IMETHODIMP ImportAddressImpl::InitFieldMap(nsIImportFieldMap* fieldMap) { + // Let's remember the last one the user used! + // This should be normal for someone importing multiple times, it's usually + // from the same file format. + + nsresult rv; + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCString prefStr; + rv = prefs->GetCharPref("mailnews.import.text.fieldmap", prefStr); + if (NS_SUCCEEDED(rv)) { + const char* pStr = prefStr.get(); + if (pStr) { + fieldMap->SetFieldMapSize(0); + long fNum; + bool active; + long fIndex = 0; + while (*pStr) { + while (*pStr && (*pStr != '+') && (*pStr != '-')) pStr++; + if (*pStr == '+') + active = true; + else if (*pStr == '-') + active = false; + else + break; + fNum = 0; + while (*pStr && ((*pStr < '0') || (*pStr > '9'))) pStr++; + if (!(*pStr)) break; + while (*pStr && (*pStr >= '0') && (*pStr <= '9')) { + fNum *= 10; + fNum += (*pStr - '0'); + pStr++; + } + while (*pStr && (*pStr != ',')) pStr++; + if (*pStr == ',') pStr++; + if (!active) { + fNum *= -1; // Re-add the stripped minus sign. + } + fieldMap->SetFieldMap(-1, fNum); + fieldMap->SetFieldActive(fIndex, active); + fIndex++; + } + if (!fIndex) { + int num; + fieldMap->GetNumMozFields(&num); + fieldMap->DefaultFieldMap(num); + } + } + } + + // Now also get the last used skip first record value. + bool skipFirstRecord = false; + rv = prefs->GetBoolPref("mailnews.import.text.skipfirstrecord", + &skipFirstRecord); + if (NS_SUCCEEDED(rv)) fieldMap->SetSkipFirstRecord(skipFirstRecord); + } + + return NS_OK; +} + +void ImportAddressImpl::SaveFieldMap(nsIImportFieldMap* pMap) { + if (!pMap) return; + + int size; + int index; + bool active; + nsCString str; + + pMap->GetMapSize(&size); + for (long i = 0; i < size; i++) { + index = i; + active = false; + pMap->GetFieldMap(i, &index); + pMap->GetFieldActive(i, &active); + if (active) + str.Append('+'); + else + str.Append('-'); + + str.AppendInt(index); + str.Append(','); + } + + nsresult rv; + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) { + nsCString prefStr; + rv = prefs->GetCharPref("mailnews.import.text.fieldmap", prefStr); + if (NS_FAILED(rv) || !str.Equals(prefStr)) + rv = prefs->SetCharPref("mailnews.import.text.fieldmap", str); + } + + // Now also save last used skip first record value. + bool skipFirstRecord = false; + rv = pMap->GetSkipFirstRecord(&skipFirstRecord); + if (NS_SUCCEEDED(rv)) + prefs->SetBoolPref("mailnews.import.text.skipfirstrecord", skipFirstRecord); +} diff --git a/comm/mailnews/import/src/nsTextImport.h b/comm/mailnews/import/src/nsTextImport.h new file mode 100644 index 0000000000..1106a0d098 --- /dev/null +++ b/comm/mailnews/import/src/nsTextImport.h @@ -0,0 +1,40 @@ +/* -*- 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 nsTextImport_h___ +#define nsTextImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" +#include "nsIStringBundle.h" + +#define NS_TEXTIMPORT_CID \ + { /* A5991D01-ADA7-11d3-A9C2-00A0CC26DA63 */ \ + 0xa5991d01, 0xada7, 0x11d3, { \ + 0xa9, 0xc2, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 \ + } \ + } + +#define kTextSupportsString NS_IMPORT_ADDRESS_STR + +class nsTextImport : public nsIImportModule { + public: + nsTextImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we support the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + + protected: + virtual ~nsTextImport(); + nsCOMPtr m_stringBundle; +}; + +#endif /* nsTextImport_h___ */ diff --git a/comm/mailnews/import/src/nsVCardAddress.cpp b/comm/mailnews/import/src/nsVCardAddress.cpp new file mode 100644 index 0000000000..2ced1d7c0b --- /dev/null +++ b/comm/mailnews/import/src/nsVCardAddress.cpp @@ -0,0 +1,151 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNativeCharsetUtils.h" +#include "nsNetUtil.h" +#include "nsVCardAddress.h" + +#include "nsIAbCard.h" +#include "nsIAbDirectory.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIUnicharLineInputStream.h" +#include "nsIConverterInputStream.h" +#include "nsIMsgVCardService.h" + +#include "plstr.h" +#include "msgCore.h" +#include "nsMsgUtils.h" + +nsVCardAddress::nsVCardAddress() {} + +nsVCardAddress::~nsVCardAddress() {} + +nsresult nsVCardAddress::ImportAddresses(bool* pAbort, const char16_t* pName, + nsIFile* pSrc, + nsIAbDirectory* pDirectory, + nsString& errors, + uint32_t* pProgress) { + // Open the source file for reading, read each line and process it! + nsCOMPtr inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error opening address file for reading\n"); + return rv; + } + + // Open the source file for reading, read each line and process it! + // Here we use this to work out the size of the file, so we can update + // an integer as we go through the file which will update a progress + // bar if required by the caller. + uint64_t bytesLeft = 0; + rv = inputStream->Available(&bytesLeft); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error checking address file for size\n"); + inputStream->Close(); + return rv; + } + uint64_t totalBytes = bytesLeft; + + // Try to detect the character set and decode. Only UTF-8 is valid from + // vCard 4.0, but we support older versions, so other charsets are possible. + + nsAutoCString sourceCharset; + rv = MsgDetectCharsetFromFile(pSrc, sourceCharset); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr converterStream = + do_CreateInstance("@mozilla.org/intl/converter-input-stream;1"); + NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE); + + rv = converterStream->Init( + inputStream, sourceCharset.get(), 8192, + nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr lineStream( + do_QueryInterface(converterStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr vCardService = + do_GetService("@mozilla.org/addressbook/msgvcardservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool more = true; + nsAutoString record; + while (!(*pAbort) && more && NS_SUCCEEDED(rv)) { + rv = ReadRecord(lineStream, record, &more); + if (NS_SUCCEEDED(rv) && !record.IsEmpty()) { + // Parse the vCard and build an nsIAbCard from it + nsCOMPtr cardFromVCard; + rv = vCardService->VCardToAbCard(record, getter_AddRefs(cardFromVCard)); + NS_ENSURE_SUCCESS(rv, rv); + + nsIAbCard* outCard; + rv = pDirectory->AddCard(cardFromVCard, &outCard); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error processing vCard record.\n"); + } + } + if (NS_SUCCEEDED(rv) && pProgress) { + // This won't be totally accurate, but its the best we can do + // considering that converterStream won't give us how many bytes + // are actually left. + bytesLeft -= record.Length(); + *pProgress = totalBytes - bytesLeft; + } + } + inputStream->Close(); + + if (NS_FAILED(rv)) { + IMPORT_LOG0( + "*** Error reading the address book - probably incorrect ending\n"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsVCardAddress::ReadRecord(nsIUnicharLineInputStream* aLineStream, + nsString& aRecord, bool* aMore) { + bool more = true; + nsresult rv; + nsAutoString line; + + aRecord.Truncate(); + + // remove the empty lines. + do { + rv = aLineStream->ReadLine(line, aMore); + } while (line.IsEmpty() && *aMore); + if (!*aMore) return rv; + + // read BEGIN:VCARD + if (!line.LowerCaseEqualsLiteral("begin:vcard")) { + IMPORT_LOG0( + "*** Expected case-insensitive BEGIN:VCARD at start of vCard\n"); + rv = NS_ERROR_FAILURE; + *aMore = more; + return rv; + } + aRecord.Append(line); + + // read until END:VCARD + do { + if (!more) { + IMPORT_LOG0( + "*** Expected case-insensitive END:VCARD at start of vCard\n"); + rv = NS_ERROR_FAILURE; + break; + } + rv = aLineStream->ReadLine(line, &more); + aRecord.AppendLiteral(MSG_LINEBREAK); + aRecord.Append(line); + } while (!line.LowerCaseEqualsLiteral("end:vcard")); + + *aMore = more; + return rv; +} diff --git a/comm/mailnews/import/src/nsVCardAddress.h b/comm/mailnews/import/src/nsVCardAddress.h new file mode 100644 index 0000000000..4cdb4de2b4 --- /dev/null +++ b/comm/mailnews/import/src/nsVCardAddress.h @@ -0,0 +1,28 @@ +/* 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 nsVCardAddress_h__ +#define nsVCardAddress_h__ + +#include "ImportDebug.h" + +class nsIAbDirectory; +class nsIFile; +class nsIUnicharLineInputStream; + +class nsVCardAddress { + public: + nsVCardAddress(); + virtual ~nsVCardAddress(); + + nsresult ImportAddresses(bool* pAbort, const char16_t* pName, nsIFile* pSrc, + nsIAbDirectory* pDirectory, nsString& errors, + uint32_t* pProgress); + + private: + static nsresult ReadRecord(nsIUnicharLineInputStream* aLineStream, + nsString& aRecord, bool* aMore); +}; + +#endif /* nsVCardAddress_h__ */ diff --git a/comm/mailnews/import/src/nsVCardImport.cpp b/comm/mailnews/import/src/nsVCardImport.cpp new file mode 100644 index 0000000000..98f7482ddf --- /dev/null +++ b/comm/mailnews/import/src/nsVCardImport.cpp @@ -0,0 +1,352 @@ +/* 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/. */ + +/* + VCard import addressbook interfaces +*/ +#include "nscore.h" +#include "nsIFile.h" +#include "nsIImportABDescriptor.h" +#include "nsIImportAddressBooks.h" +#include "nsIImportFieldMap.h" +#include "nsIImportGeneric.h" +#include "nsCOMPtr.h" +#include "nsIImportService.h" +#include "nsIFile.h" +#include "nsImportStringBundle.h" +#include "nsMsgUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsTextFormatter.h" +#include "nsVCardAddress.h" +#include "nsVCardImport.h" + +class ImportVCardAddressImpl : public nsIImportAddressBooks { + public: + explicit ImportVCardAddressImpl(nsIStringBundle* aStringBundle); + + static nsresult Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportAddressBooks interface + + // TODO: support multiple vCard files in future - shouldn't be too hard, + // since you just import each file in turn. + NS_IMETHOD GetSupportsMultiple(bool* _retval) override { + *_retval = false; + return NS_OK; + } + + NS_IMETHOD GetAutoFind(char16_t** description, bool* _retval) override; + + NS_IMETHOD GetNeedsFieldMap(nsIFile* location, bool* _retval) override { + *_retval = false; + return NS_OK; + } + + NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found, + bool* userVerify) override; + + NS_IMETHOD FindAddressBooks( + nsIFile* location, + nsTArray>& books) override; + + NS_IMETHOD InitFieldMap(nsIImportFieldMap* fieldMap) override { + return NS_ERROR_FAILURE; + } + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor* source, + nsIAbDirectory* destination, + nsIImportFieldMap* fieldMap, + nsISupports* aSupportService, + char16_t** errorLog, char16_t** successLog, + bool* fatalError) override; + + NS_IMETHOD GetImportProgress(uint32_t* _retval) override; + + NS_IMETHOD GetSampleData(int32_t index, bool* pFound, + char16_t** pStr) override { + return NS_ERROR_FAILURE; + } + + NS_IMETHOD SetSampleLocation(nsIFile*) override { return NS_ERROR_FAILURE; } + + private: + virtual ~ImportVCardAddressImpl(); + static void ReportSuccess(nsString& name, nsString* pStream, + nsIStringBundle* pBundle); + static void SetLogs(nsString& success, nsString& error, char16_t** pError, + char16_t** pSuccess); + static void ReportError(const char* errorName, nsString& name, + nsString* pStream, nsIStringBundle* pBundle); + + private: + nsVCardAddress m_vCard; + nsCOMPtr m_fileLoc; + uint32_t m_bytesImported; + nsCOMPtr m_notProxyBundle; +}; + +nsVCardImport::nsVCardImport() { + nsImportStringBundle::GetStringBundle(VCARDIMPORT_MSGS_URL, + getter_AddRefs(m_stringBundle)); + + IMPORT_LOG0("nsVCardImport Module Created\n"); +} + +nsVCardImport::~nsVCardImport() { + IMPORT_LOG0("nsVCardImport Module Deleted\n"); +} + +NS_IMPL_ISUPPORTS(nsVCardImport, nsIImportModule) + +NS_IMETHODIMP nsVCardImport::GetName(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + *name = + nsImportStringBundle::GetStringByName("vCardImportName", m_stringBundle); + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetDescription(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + *name = nsImportStringBundle::GetStringByName("vCardImportDescription", + m_stringBundle); + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetSupports(char** supports) { + NS_ENSURE_ARG_POINTER(supports); + *supports = strdup(NS_IMPORT_ADDRESS_STR); + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetSupportsUpgrade(bool* pUpgrade) { + NS_ENSURE_ARG_POINTER(pUpgrade); + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsVCardImport::GetImportInterface(const char* pImportType, + nsISupports** ppInterface) { + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + *ppInterface = nullptr; + if (!strcmp(pImportType, "addressbook")) { + nsresult rv; + // create the nsIImportMail interface and return it! + nsCOMPtr pAddress; + nsCOMPtr pGeneric; + rv = ImportVCardAddressImpl::Create(getter_AddRefs(pAddress), + m_stringBundle); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric)); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + nsCOMPtr pInterface(do_QueryInterface(pGeneric)); + pInterface.forget(ppInterface); + } + } + } + return rv; + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult ImportVCardAddressImpl::Create(nsIImportAddressBooks** aImport, + nsIStringBundle* aStringBundle) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new ImportVCardAddressImpl(aStringBundle)); + return NS_OK; +} + +ImportVCardAddressImpl::ImportVCardAddressImpl(nsIStringBundle* aStringBundle) + : m_notProxyBundle(aStringBundle) {} + +ImportVCardAddressImpl::~ImportVCardAddressImpl() {} + +NS_IMPL_ISUPPORTS(ImportVCardAddressImpl, nsIImportAddressBooks) + +NS_IMETHODIMP ImportVCardAddressImpl::GetAutoFind(char16_t** addrDescription, + bool* _retval) { + NS_ENSURE_ARG_POINTER(addrDescription); + NS_ENSURE_ARG_POINTER(_retval); + + nsString str; + *_retval = false; + + if (!m_notProxyBundle) return NS_ERROR_FAILURE; + + nsImportStringBundle::GetStringByName("vCardImportAddressName", + m_notProxyBundle, str); + *addrDescription = ToNewUnicode(str); + return NS_OK; +} + +NS_IMETHODIMP ImportVCardAddressImpl::GetDefaultLocation(nsIFile** ppLoc, + bool* found, + bool* userVerify) { + NS_ENSURE_ARG_POINTER(found); + NS_ENSURE_ARG_POINTER(ppLoc); + NS_ENSURE_ARG_POINTER(userVerify); + + *ppLoc = nullptr; + *found = false; + *userVerify = true; + return NS_OK; +} + +NS_IMETHODIMP ImportVCardAddressImpl::FindAddressBooks( + nsIFile* pLoc, nsTArray>& books) { + NS_ENSURE_ARG_POINTER(pLoc); + + books.Clear(); + bool exists = false; + nsresult rv = pLoc->Exists(&exists); + if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE; + + bool isFile = false; + rv = pLoc->IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) return NS_ERROR_FAILURE; + + m_fileLoc = pLoc; + + /* Build an address book descriptor based on the file passed in! */ + nsString name; + m_fileLoc->GetLeafName(name); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed getting leaf name of file\n"); + return rv; + } + + int32_t idx = name.RFindChar('.'); + if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) { + name.SetLength(idx); + } + + nsCOMPtr desc; + nsCOMPtr impSvc( + do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to obtain the import service\n"); + return rv; + } + + rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc)); + if (NS_SUCCEEDED(rv)) { + int64_t sz = 0; + pLoc->GetFileSize(&sz); + desc->SetPreferredName(name); + desc->SetSize((uint32_t)sz); + desc->SetAbFile(m_fileLoc); + books.AppendElement(desc); + } + if (NS_FAILED(rv)) { + IMPORT_LOG0( + "*** Error creating address book descriptor for vCard import\n"); + return rv; + } + + return NS_OK; +} + +void ImportVCardAddressImpl::ReportSuccess(nsString& name, nsString* pStream, + nsIStringBundle* pBundle) { + if (!pStream) return; + + // load the success string + char16_t* pFmt = nsImportStringBundle::GetStringByName( + "vCardImportAddressSuccess", pBundle); + + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportVCardAddressImpl::ReportError(const char* errorName, nsString& name, + nsString* pStream, + nsIStringBundle* pBundle) { + if (!pStream) return; + + // load the error string + char16_t* pFmt = nsImportStringBundle::GetStringByName(errorName, pBundle); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + free(pFmt); + pStream->Append(char16_t('\n')); +} + +void ImportVCardAddressImpl::SetLogs(nsString& success, nsString& error, + char16_t** pError, char16_t** pSuccess) { + if (pError) *pError = ToNewUnicode(error); + if (pSuccess) *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP ImportVCardAddressImpl::ImportAddressBook( + nsIImportABDescriptor* pSource, nsIAbDirectory* pDestination, + nsIImportFieldMap* fieldMap, nsISupports* aSupportService, + char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) { + NS_ENSURE_ARG_POINTER(pSource); + NS_ENSURE_ARG_POINTER(pDestination); + NS_ENSURE_ARG_POINTER(fatalError); + + if (!m_notProxyBundle) return NS_ERROR_FAILURE; + + m_bytesImported = 0; + nsString success, error; + bool addrAbort = false; + nsString name; + pSource->GetPreferredName(name); + + uint32_t addressSize = 0; + pSource->GetSize(&addressSize); + if (addressSize == 0) { + IMPORT_LOG0("Address book size is 0, skipping import.\n"); + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_OK; + } + + nsCOMPtr inFile; + if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) { + ReportError("vCardImportAddressBadSourceFile", name, &error, + m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + return NS_ERROR_FAILURE; + } + + if (!aSupportService) { + IMPORT_LOG0("Missing support service to import call\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv = m_vCard.ImportAddresses(&addrAbort, name.get(), inFile, + pDestination, error, &m_bytesImported); + + if (NS_SUCCEEDED(rv) && error.IsEmpty()) { + ReportSuccess(name, &success, m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } else { + ReportError("vCardImportAddressConvertError", name, &error, + m_notProxyBundle); + SetLogs(success, error, pErrorLog, pSuccessLog); + } + + IMPORT_LOG0("*** VCard address import done\n"); + return rv; +} + +NS_IMETHODIMP ImportVCardAddressImpl::GetImportProgress(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_bytesImported; + return NS_OK; +} diff --git a/comm/mailnews/import/src/nsVCardImport.h b/comm/mailnews/import/src/nsVCardImport.h new file mode 100644 index 0000000000..f35e5eb3d3 --- /dev/null +++ b/comm/mailnews/import/src/nsVCardImport.h @@ -0,0 +1,39 @@ +/* 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 nsVCardImport_h___ +#define nsVCardImport_h___ + +#include "nsIImportModule.h" +#include "nsIStringBundle.h" +#include "nsCOMPtr.h" + +#define NS_VCARDIMPORT_CID \ + { /* 0EB034A3-964A-4E2F-92EBCC55D9AE9DD2 */ \ + 0x0eb034a3, 0x964a, 0x4e2f, { \ + 0x92, 0xeb, 0xcc, 0x55, 0xd9, 0xae, 0x9d, 0xd2 \ + } \ + } + +#define VCARDIMPORT_MSGS_URL \ + "chrome://messenger/locale/vCardImportMsgs.properties" + +class nsVCardImport : public nsIImportModule { + public: + nsVCardImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we support the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + + protected: + virtual ~nsVCardImport(); + nsCOMPtr m_stringBundle; +}; + +#endif /* nsVCardImport_h___ */ diff --git a/comm/mailnews/import/src/nsWMImport.cpp b/comm/mailnews/import/src/nsWMImport.cpp new file mode 100644 index 0000000000..a3b5dd45b2 --- /dev/null +++ b/comm/mailnews/import/src/nsWMImport.cpp @@ -0,0 +1,199 @@ +/* -*- 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/. */ + +/* + * Windows Live Mail (Win32) import mail and addressbook interfaces + */ + +#include "nscore.h" +#include "nsString.h" +#include "nsMsgUtils.h" +#include "nsWMImport.h" +#include "nsIImportMail.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsXPCOM.h" +#include "nsWMSettings.h" +#include "nsTextFormatter.h" +#include "nsWMStringBundle.h" +#include "nsUnicharUtils.h" + +#include "ImportDebug.h" + +class ImportWMMailImpl : public nsIImportMail { + public: + ImportWMMailImpl(); + + static nsresult Create(nsIImportMail** aImport); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIImportmail interface + + /* void GetDefaultLocation (out nsIFile location, out boolean found, out + * boolean userVerify); */ + NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found, + bool* userVerify); + + /* nsIArray FindMailboxes (in nsIFile location); */ + NS_IMETHOD FindMailboxes(nsIFile* location, + nsTArray>& boxes); + + NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor* source, + nsIMsgFolder* dstFolder, char16_t** pErrorLog, + char16_t** pSuccessLog, bool* fatalError); + + /* unsigned long GetImportProgress (); */ + NS_IMETHOD GetImportProgress(uint32_t* _retval); + + NS_IMETHOD TranslateFolderName(const nsAString& aFolderName, + nsAString& _retval); + + public: + static void ReportSuccess(nsString& name, int32_t count, nsString* pStream); + static void ReportError(int32_t errorNum, nsString& name, nsString* pStream); + static void AddLinebreak(nsString* pStream); + static void SetLogs(nsString& success, nsString& error, char16_t** pError, + char16_t** pSuccess); + + private: + virtual ~ImportWMMailImpl(); +}; + +nsWMImport::nsWMImport() { + IMPORT_LOG0("nsWMImport Module Created\n"); + nsWMStringBundle::GetStringBundle(); +} + +nsWMImport::~nsWMImport() { IMPORT_LOG0("nsWMImport Module Deleted\n"); } + +NS_IMPL_ISUPPORTS(nsWMImport, nsIImportModule) + +NS_IMETHODIMP nsWMImport::GetName(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + // nsString title = "Windows Live Mail"; + // *name = ToNewUnicode(title); + *name = nsWMStringBundle::GetStringByID(WMIMPORT_NAME); + + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetDescription(char16_t** name) { + NS_ENSURE_ARG_POINTER(name); + + // nsString desc = "Windows Live Mail mail and address books"; + // *name = ToNewUnicode(desc); + *name = nsWMStringBundle::GetStringByID(WMIMPORT_DESCRIPTION); + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetSupports(char** supports) { + NS_ASSERTION(supports != nullptr, "null ptr"); + if (!supports) return NS_ERROR_NULL_POINTER; + + *supports = strdup(kWMSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetSupportsUpgrade(bool* pUpgrade) { + NS_ASSERTION(pUpgrade != nullptr, "null ptr"); + if (!pUpgrade) return NS_ERROR_NULL_POINTER; + + *pUpgrade = true; + return NS_OK; +} + +NS_IMETHODIMP nsWMImport::GetImportInterface(const char* pImportType, + nsISupports** ppInterface) { + NS_ENSURE_ARG_POINTER(pImportType); + NS_ENSURE_ARG_POINTER(ppInterface); + + *ppInterface = nullptr; + nsresult rv; + + if (!strcmp(pImportType, "settings")) { + nsCOMPtr pSettings; + rv = nsWMSettings::Create(getter_AddRefs(pSettings)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr pInterface(do_QueryInterface(pSettings)); + pInterface.forget(ppInterface); + } + return rv; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// +nsresult ImportWMMailImpl::Create(nsIImportMail** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new ImportWMMailImpl()); + return NS_OK; +} + +ImportWMMailImpl::ImportWMMailImpl() {} + +ImportWMMailImpl::~ImportWMMailImpl() {} + +NS_IMPL_ISUPPORTS(ImportWMMailImpl, nsIImportMail) + +NS_IMETHODIMP ImportWMMailImpl::TranslateFolderName( + const nsAString& aFolderName, nsAString& _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ImportWMMailImpl::GetDefaultLocation(nsIFile** ppLoc, bool* found, + bool* userVerify) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ImportWMMailImpl::FindMailboxes( + nsIFile* pLoc, nsTArray>& boxes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void ImportWMMailImpl::AddLinebreak(nsString* pStream) { + if (pStream) pStream->Append(char16_t('\n')); +} + +void ImportWMMailImpl::ReportSuccess(nsString& name, int32_t count, + nsString* pStream) { + if (!pStream) return; + // load the success string + char16_t* pFmt = nsWMStringBundle::GetStringByID(WMIMPORT_MAILBOX_SUCCESS); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get(), count); + pStream->Append(pText); + nsWMStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportWMMailImpl::ReportError(int32_t errorNum, nsString& name, + nsString* pStream) { + if (!pStream) return; + // load the error string + char16_t* pFmt = nsWMStringBundle::GetStringByID(errorNum); + nsString pText; + nsTextFormatter::ssprintf(pText, pFmt, name.get()); + pStream->Append(pText); + nsWMStringBundle::FreeString(pFmt); + AddLinebreak(pStream); +} + +void ImportWMMailImpl::SetLogs(nsString& success, nsString& error, + char16_t** pError, char16_t** pSuccess) { + if (pError) *pError = ToNewUnicode(error); + if (pSuccess) *pSuccess = ToNewUnicode(success); +} + +NS_IMETHODIMP ImportWMMailImpl::ImportMailbox( + nsIImportMailboxDescriptor* pSource, nsIMsgFolder* pDstFolder, + char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ImportWMMailImpl::GetImportProgress(uint32_t* pDoneSoFar) { + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/comm/mailnews/import/src/nsWMImport.h b/comm/mailnews/import/src/nsWMImport.h new file mode 100644 index 0000000000..1f14b1331f --- /dev/null +++ b/comm/mailnews/import/src/nsWMImport.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWMImport_h___ +#define nsWMImport_h___ + +#include "nsIImportModule.h" +#include "nsCOMPtr.h" + +#define NS_WMIMPORT_CID \ + { /* 42bc82bc-8e9f-4597-8b6e-e529daaf3af1 */ \ + 0x42bc82bc, 0x8e9f, 0x4597, { \ + 0x8b, 0x6e, 0xe5, 0x29, 0xda, 0xaf, 0x3a, 0xf1 \ + } \ + } + +// currently only support setting import +#define kWMSupportsString NS_IMPORT_SETTINGS_STR + +class nsWMImport : public nsIImportModule { + public: + nsWMImport(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////////////////////// + // we support the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + + protected: + virtual ~nsWMImport(); +}; + +#endif /* nsWMImport_h___ */ diff --git a/comm/mailnews/import/src/nsWMSettings.cpp b/comm/mailnews/import/src/nsWMSettings.cpp new file mode 100644 index 0000000000..ee741fa053 --- /dev/null +++ b/comm/mailnews/import/src/nsWMSettings.cpp @@ -0,0 +1,679 @@ +/* -*- 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/. */ + +/* + Windows Live Mail (Win32) settings +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsString.h" +#include "nsMsgUtils.h" +#include "nsWMImport.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIImportSettings.h" +#include "nsWMSettings.h" +#include "nsMsgI18N.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsWMStringBundle.h" +#include "ImportDebug.h" +#include "nsIPop3IncomingServer.h" +#include "nsIImapIncomingServer.h" +#include "nsINntpIncomingServer.h" +#include "stdlib.h" +#include "mozilla/dom/Document.h" +#include "nsIFile.h" +#include "nsNetUtil.h" +#include "nsTArray.h" +#include +#include "nsIWindowsRegKey.h" +#include "nsCOMArray.h" +#include "nsWMUtils.h" + +class WMSettings { + public: + static bool DoImport(nsIMsgAccount** ppAccount); + static bool DoIMAPServer(nsIMsgAccountManager* pMgr, + mozilla::dom::Document* xmlDoc, + const nsString& serverName, + nsIMsgAccount** ppAccount); + static bool DoPOP3Server(nsIMsgAccountManager* pMgr, + mozilla::dom::Document* xmlDoc, + const nsString& serverName, + nsIMsgAccount** ppAccount); + static bool DoNNTPServer(nsIMsgAccountManager* pMgr, + mozilla::dom::Document* xmlDoc, + const nsString& serverName, + nsIMsgAccount** ppAccount); + static void SetIdentities(nsIMsgAccountManager* pMgr, nsIMsgAccount* pAcc, + mozilla::dom::Document* xmlDoc, + nsAutoString& userName, int32_t authMethodIncoming, + bool isNNTP); + static void SetSmtpServer(mozilla::dom::Document* xmlDoc, nsIMsgIdentity* id, + nsAutoString& inUserName, + int32_t authMethodIncoming); +}; + +static int32_t checkNewMailTime; // WM global setting, let's default to 30 +static bool checkNewMail; // WM global setting, let's default to false + // This won't cause unwanted autodownloads- + // user can set prefs after import + +//////////////////////////////////////////////////////////////////////// +nsresult nsWMSettings::Create(nsIImportSettings** aImport) { + NS_ENSURE_ARG_POINTER(aImport); + NS_ADDREF(*aImport = new nsWMSettings()); + return NS_OK; +} + +nsWMSettings::nsWMSettings() {} + +nsWMSettings::~nsWMSettings() {} + +NS_IMPL_ISUPPORTS(nsWMSettings, nsIImportSettings) + +NS_IMETHODIMP nsWMSettings::AutoLocate(char16_t** description, + nsIFile** location, bool* _retval) { + NS_ASSERTION(description != nullptr, "null ptr"); + NS_ASSERTION(_retval != nullptr, "null ptr"); + if (!description || !_retval) return NS_ERROR_NULL_POINTER; + + *description = nsWMStringBundle::GetStringByID(WMIMPORT_NAME); + *_retval = false; + + if (location) *location = nullptr; + nsCOMPtr key; + if (NS_SUCCEEDED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) *_retval = true; + + return NS_OK; +} + +NS_IMETHODIMP nsWMSettings::SetLocation(nsIFile* location) { return NS_OK; } + +NS_IMETHODIMP nsWMSettings::Import(nsIMsgAccount** localMailAccount, + bool* _retval) { + NS_ASSERTION(_retval != nullptr, "null ptr"); + + if (WMSettings::DoImport(localMailAccount)) { + *_retval = true; + IMPORT_LOG0("Settings import appears successful\n"); + } else { + *_retval = false; + IMPORT_LOG0("Settings import returned FALSE\n"); + } + + return NS_OK; +} + +bool WMSettings::DoImport(nsIMsgAccount** ppAccount) { + // do the windows registry stuff first + nsCOMPtr key; + if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) { + IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n"); + return false; + } + // 'poll for messages' setting in WM is a global setting-Like OE + // for all accounts dword ==0xffffffff for don't poll else 1/60000 = minutes + checkNewMailTime = 30; + checkNewMail = false; + + nsresult rv; + nsCOMPtr subKey; + if (NS_SUCCEEDED(key->OpenChild(u"mail"_ns, + nsIWindowsRegKey::ACCESS_QUERY_VALUE, + getter_AddRefs(subKey)))) { + uint32_t dwordResult = 0xffffffff; + rv = subKey->ReadIntValue(u"Poll For Mail"_ns, + &dwordResult); // reg_dword + subKey->Close(); + if (NS_SUCCEEDED(rv) && dwordResult != 0xffffffff) { + checkNewMail = true; + checkNewMailTime = dwordResult / 60000; + } + } + // these are in main windowsmail key and if they don't exist-not to worry + // (less than 64 chars) e.g. + // account{4A18B81E-83CA-472A-8D7F-5301C0B97B8D}.oeaccount + nsAutoString defMailAcct, defNewsAcct; + key->ReadStringValue(u"Default Mail Account"_ns, + defMailAcct); // ref_sz + key->ReadStringValue(u"Default News Account"_ns, + defNewsAcct); // ref_sz + + nsCOMPtr accMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create an account manager!\n"); + return false; + } + + nsCOMArray fileArray; + if (NS_FAILED(nsWMUtils::GetOEAccountFiles(fileArray))) { + IMPORT_LOG0("*** Failed to get .oeaccount file!\n"); + return false; + } + + // Loop through *.oeaccounts files looking for POP3 & IMAP & NNTP accounts + // Ignore LDAP for now! + int accounts = 0; + nsCOMPtr xmlDoc; + + for (int32_t i = fileArray.Count() - 1; i >= 0; i--) { + nsWMUtils::MakeXMLdoc(getter_AddRefs(xmlDoc), fileArray[i]); + + nsAutoCString name; + fileArray[i]->GetNativeLeafName(name); + nsAutoString value; + nsCOMPtr anAccount; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Server", value))) + if (DoIMAPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount))) + accounts++; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "NNTP_Server", value))) + if (DoNNTPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount))) + accounts++; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "POP3_Server", value))) + if (DoPOP3Server(accMgr, xmlDoc, value, getter_AddRefs(anAccount))) + accounts++; + + if (anAccount) { + nsString name; + // Is this the default account? + fileArray[i]->GetLeafName(name); + if (defMailAcct.Equals(name)) accMgr->SetDefaultAccount(anAccount); + } + } + + // Now save the new acct info to pref file. + rv = accMgr->SaveAccountInfo(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file"); + + return accounts != 0; +} + +bool WMSettings::DoIMAPServer(nsIMsgAccountManager* pMgr, + mozilla::dom::Document* xmlDoc, + const nsString& serverName, + nsIMsgAccount** ppAccount) { + int32_t authMethod; // Secure Password Authentication (SPA) + nsresult errorCode; + if (ppAccount) *ppAccount = nullptr; + + nsAutoString userName, value; + if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "IMAP_User_Name", userName))) + return false; + bool result = false; + // I now have a user name/server name pair, find out if it already exists? + nsCOMPtr in; + nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), "imap"_ns, + 0, getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), + "imap"_ns, getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + nsCOMPtr imapServer = do_QueryInterface(in); + if (!imapServer) { + IMPORT_LOG1("*** Failed to create nsIImapIncomingServer for %S!\n", + static_cast(serverName.get())); + return false; + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Root_Folder", value))) { + imapServer->SetServerDirectory(NS_ConvertUTF16toUTF8(value)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag( + xmlDoc, "IMAP_Secure_Connection", value))) { + if (value.ToInteger(&errorCode, 16)) + in->SetSocketType(nsMsgSocketType::SSL); + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Use_Sicily", value))) { + bool secAuth = (bool)value.ToInteger(&errorCode, 16); + authMethod = secAuth ? nsMsgAuthMethod::secure + : nsMsgAuthMethod::passwordCleartext; + in->SetAuthMethod(authMethod); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Port", value))) { + in->SetPort(value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "Account_Name", value))) { + rv = in->SetPrettyName(value); + } + in->SetDoBiff(checkNewMail); + in->SetBiffMinutes(checkNewMailTime); + + IMPORT_LOG2("Created IMAP server named: %S, userName: %S\n", + static_cast(serverName.get()), + static_cast(userName.get())); + + // We have a server, create an account. + nsCOMPtr account; + rv = pMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0( + "Created an account and set the IMAP server " + "as the incoming server\n"); + + // Fiddle with the identities + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) account.forget(ppAccount); + } + } + } else if (NS_SUCCEEDED(rv) && in) { + // for an existing server we create another identity, + // TB lists under 'manage identities' + nsCOMPtr account; + rv = pMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0( + "Created an identity and added to existing " + "IMAP incoming server\n"); + // Fiddle with the identities + in->GetAuthMethod(&authMethod); + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) account.forget(ppAccount); + } + } else + result = true; + return result; +} + +bool WMSettings::DoPOP3Server(nsIMsgAccountManager* pMgr, + mozilla::dom::Document* xmlDoc, + const nsString& serverName, + nsIMsgAccount** ppAccount) { + int32_t authMethod; // Secure Password Authentication (SPA) + nsresult errorCode; + if (ppAccount) *ppAccount = nullptr; + + nsAutoString userName, value; + if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "POP3_User_Name", userName))) + return false; + bool result = false; + // I now have a user name/server name pair, find out if it already exists? + nsCOMPtr in; + nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), "pop3"_ns, + 0, getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName), + NS_ConvertUTF16toUTF8(serverName), + "pop3"_ns, getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + nsCOMPtr pop3Server = do_QueryInterface(in); + if (!pop3Server) { + IMPORT_LOG1("*** Failed to create nsIPop3IncomingServer for %S!\n", + static_cast(serverName.get())); + return false; + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag( + xmlDoc, "POP3_Secure_Connection", value)) && + value.ToInteger(&errorCode, 16)) { + in->SetSocketType(nsMsgSocketType::SSL); + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "POP3_Use_Sicily", value))) { + bool secAuth = (bool)value.ToInteger(&errorCode, 16); + authMethod = secAuth ? nsMsgAuthMethod::secure + : nsMsgAuthMethod::passwordCleartext; + in->SetAuthMethod(authMethod); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "POP3_Port", value))) { + in->SetPort(value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "POP3_Skip_Account", value))) { + if (!value.IsEmpty()) + // OE:0=='Include this account when receiving mail or synchronizing'== + // TB:1==ActMgr:Server:advanced:Include this server when getting new + // mail + pop3Server->SetDeferGetNewMail(value.ToInteger(&errorCode, 16) == 0); + else + pop3Server->SetDeferGetNewMail(false); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "Leave_Mail_On_Server", + value))) { + pop3Server->SetLeaveMessagesOnServer( + (bool)value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "Remove_When_Deleted", + value))) { + pop3Server->SetDeleteMailLeftOnServer( + (bool)value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "Remove_When_Expired", + value))) { + pop3Server->SetDeleteByAgeFromServer( + (bool)value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "Expire_Days", value))) { + pop3Server->SetNumDaysToLeaveOnServer(value.ToInteger(&errorCode, 16)); + } + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "Account_Name", value))) { + rv = in->SetPrettyName(value); + } + + in->SetDoBiff(checkNewMail); + in->SetBiffMinutes(checkNewMailTime); + + // set local folders as the Inbox to use for this POP3 server + nsCOMPtr localFoldersServer; + pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + if (!localFoldersServer) { + // XXX: We may need to move this local folder creation + // code to the generic nsImportSettings code + // if the other import modules end up needing to do this too. + // if Local Folders does not exist already, create it + rv = pMgr->CreateLocalMailAccount(); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Failed to create Local Folders!\n"); + return false; + } + pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + } + + // now get the account for this server + nsCOMPtr localFoldersAccount; + pMgr->FindAccountForServer(localFoldersServer, + getter_AddRefs(localFoldersAccount)); + if (localFoldersAccount) { + nsCString localFoldersAcctKey; + localFoldersAccount->GetKey(localFoldersAcctKey); + pop3Server->SetDeferredToAccount(localFoldersAcctKey); + } + + IMPORT_LOG2("Created POP3 server named: %S, userName: %S\n", + static_cast(serverName.get()), + static_cast(userName.get())); + + // We have a server, create an account. + nsCOMPtr account; + rv = pMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + IMPORT_LOG0( + "Created a new account and set the incoming " + "server to the POP3 server.\n"); + + // Fiddle with the identities + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) account.forget(ppAccount); + } + } + } else if (NS_SUCCEEDED(rv) && in) { + IMPORT_LOG2("Existing POP3 server named: %S, userName: %S\n", + static_cast(serverName.get()), + static_cast(userName.get())); + // for an existing server we create another identity, + // TB listed under 'manage identities' + nsCOMPtr account; + rv = pMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0( + "Created identity and added to existing POP3 incoming server.\n"); + // Fiddle with the identities + in->GetAuthMethod(&authMethod); + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false); + result = true; + if (ppAccount) account.forget(ppAccount); + } + } else + result = true; + return result; +} + +bool WMSettings::DoNNTPServer(nsIMsgAccountManager* pMgr, + mozilla::dom::Document* xmlDoc, + const nsString& serverName, + nsIMsgAccount** ppAccount) { + int32_t authMethod; + nsresult errorCode; + if (ppAccount) *ppAccount = nullptr; + + nsAutoString userName, value; + // this only exists if NNTP server requires it or not, anonymous login + nsWMUtils::GetValueForTag(xmlDoc, "NNTP_User_Name", userName); + bool result = false; + + // I now have a user name/server name pair, find out if it already exists? + // NNTP can have empty user name. This is wild card in findserver + nsCOMPtr in; + nsresult rv = + pMgr->FindServer(EmptyCString(), NS_ConvertUTF16toUTF8(serverName), + "nntp"_ns, 0, getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = pMgr->CreateIncomingServer(EmptyCString(), + NS_ConvertUTF16toUTF8(serverName), + "nntp"_ns, getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + nsCOMPtr nntpServer = do_QueryInterface(in); + if (!nntpServer) { + IMPORT_LOG1("*** Failed to create nsINnntpIncomingServer for %S!\n", + static_cast(serverName.get())); + return false; + } + if (!userName.IsEmpty()) { // if username req'd then auth req'd + nntpServer->SetPushAuth(true); + in->SetUsername(NS_ConvertUTF16toUTF8(userName)); + } + + nsAutoString value; + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "NNTP_Port", value))) { + in->SetPort(value.ToInteger(&errorCode, 16)); + } + + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "Account_Name", value))) { + in->SetPrettyName(value); + } + + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "NNTP_Use_Sicily", value))) { + bool secAuth = (bool)value.ToInteger(&errorCode, 16); + authMethod = secAuth ? nsMsgAuthMethod::secure + : nsMsgAuthMethod::passwordCleartext; + in->SetAuthMethod(authMethod); + } + + IMPORT_LOG2("Created NNTP server named: %S, userName: %S\n", + static_cast(serverName.get()), + static_cast(userName.get())); + + // We have a server, create an account. + nsCOMPtr account; + rv = pMgr->CreateAccount(getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + rv = account->SetIncomingServer(in); + + IMPORT_LOG0( + "Created an account and set the NNTP server " + "as the incoming server\n"); + + // Fiddle with the identities + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true); + result = true; + if (ppAccount) account.forget(ppAccount); + } + } + } else if (NS_SUCCEEDED(rv) && in) { + // for the existing server... + nsCOMPtr account; + rv = pMgr->FindAccountForServer(in, getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + IMPORT_LOG0( + "Using existing account and set the " + "NNTP server as the incoming server\n"); + // Fiddle with the identities + in->GetAuthMethod(&authMethod); + SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true); + result = true; + if (ppAccount) account.forget(ppAccount); + } + } else + result = true; + return result; +} + +void WMSettings::SetIdentities(nsIMsgAccountManager* pMgr, nsIMsgAccount* pAcc, + mozilla::dom::Document* xmlDoc, + nsAutoString& inUserName, + int32_t authMethodIncoming, bool isNNTP) { + // Get the relevant information for an identity + nsAutoString value; + + nsCOMPtr id; + pMgr->CreateIdentity(getter_AddRefs(id)); + if (id) { + IMPORT_LOG0("Created identity and added to the account\n"); + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag( + xmlDoc, isNNTP ? "NNTP_Display_Name" : "SMTP_Display_Name", + value))) { + id->SetFullName(value); + IMPORT_LOG1("\tname: %S\n", static_cast(value.get())); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag( + xmlDoc, + isNNTP ? "NNTP_Organization_Name" : "SMTP_Organization_Name", + value))) { + id->SetOrganization(value); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag( + xmlDoc, isNNTP ? "NNTP_Email_Address" : "SMTP_Email_Address", + value))) { + id->SetEmail(NS_ConvertUTF16toUTF8(value)); + IMPORT_LOG1("\temail: %S\n", static_cast(value.get())); + } + + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, + isNNTP ? "NNTP_Reply_To_Email_Address" + : "SMTP_Reply_To_Email_Address", + value))) { + id->SetReplyTo(NS_ConvertUTF16toUTF8(value)); + } + + // Windows users are used to top style quoting. + id->SetReplyOnTop(isNNTP ? 0 : 1); + pAcc->AddIdentity(id); + } + + if (!isNNTP) // NNTP does not use SMTP in OE or TB + SetSmtpServer(xmlDoc, id, inUserName, authMethodIncoming); +} + +void WMSettings::SetSmtpServer(mozilla::dom::Document* xmlDoc, + nsIMsgIdentity* id, nsAutoString& inUserName, + int32_t authMethodIncoming) { + nsresult errorCode; + + // set the id.smtpserver accordingly + if (!id) return; + nsCString smtpServerKey, userName; + nsAutoString value, smtpName; + if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Server", smtpName))) + return; + + // first we have to calculate the smtp user name which is based on sicily + // smtp user name depends on sicily which may or not exist + int32_t useSicily = 0; + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Use_Sicily", value))) { + useSicily = (int32_t)value.ToInteger(&errorCode, 16); + } + switch (useSicily) { + case 1: + case 3: + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "SMTP_User_Name", value))) { + CopyUTF16toUTF8(value, userName); + } else { + CopyUTF16toUTF8(inUserName, userName); + } + break; + case 2: + CopyUTF16toUTF8(inUserName, userName); + break; + default: + break; // initial userName == "" + } + + nsresult rv; + nsCOMPtr smtpService( + do_GetService("@mozilla.org/messengercompose/smtp;1", &rv)); + if (NS_SUCCEEDED(rv) && smtpService) { + nsCOMPtr extgServer; + // don't try to make another server + // regardless if username doesn't match + rv = smtpService->FindServer(userName.get(), + NS_ConvertUTF16toUTF8(smtpName).get(), + getter_AddRefs(extgServer)); + if (NS_SUCCEEDED(rv) && extgServer) { + // set our account keyed to this smptserver key + extgServer->GetKey(getter_Copies(smtpServerKey)); + id->SetSmtpServerKey(smtpServerKey); + + IMPORT_LOG1("SMTP server already exists: %s\n", + NS_ConvertUTF16toUTF8(smtpName).get()); + } else { + nsCOMPtr smtpServer; + rv = smtpService->CreateServer(getter_AddRefs(smtpServer)); + if (NS_SUCCEEDED(rv) && smtpServer) { + if (NS_SUCCEEDED( + nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Port", value))) { + smtpServer->SetPort(value.ToInteger(&errorCode, 16)); + } + + if (NS_SUCCEEDED(nsWMUtils::GetValueForTag( + xmlDoc, "SMTP_Secure_Connection", value))) { + if (value.ToInteger(&errorCode, 16) == 1) + smtpServer->SetSocketType(nsMsgSocketType::SSL); + else + smtpServer->SetSocketType(nsMsgSocketType::plain); + } + smtpServer->SetUsername(userName); + switch (useSicily) { + case 1: + smtpServer->SetAuthMethod(nsMsgAuthMethod::secure); + break; + case 2: // requires SMTP authentication to use the incoming server + // settings + smtpServer->SetAuthMethod(authMethodIncoming); + break; + case 3: + smtpServer->SetAuthMethod(nsMsgAuthMethod::passwordCleartext); + break; + default: + smtpServer->SetAuthMethod(nsMsgAuthMethod::none); + } + + smtpServer->SetHostname(NS_ConvertUTF16toUTF8(smtpName)); + + smtpServer->GetKey(getter_Copies(smtpServerKey)); + id->SetSmtpServerKey(smtpServerKey); + + IMPORT_LOG1("Created new SMTP server: %s\n", + NS_ConvertUTF16toUTF8(smtpName).get()); + } + } + } +} diff --git a/comm/mailnews/import/src/nsWMSettings.h b/comm/mailnews/import/src/nsWMSettings.h new file mode 100644 index 0000000000..d1b770d0a1 --- /dev/null +++ b/comm/mailnews/import/src/nsWMSettings.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWMSettings_h___ +#define nsWMSettings_h___ + +#include "nsIImportSettings.h" + +class nsWMSettings : public nsIImportSettings { + public: + nsWMSettings(); + static nsresult Create(nsIImportSettings** aImport); + NS_DECL_ISUPPORTS + NS_DECL_NSIIMPORTSETTINGS + + private: + virtual ~nsWMSettings(); +}; + +#endif /* nsWMSettings_h___ */ diff --git a/comm/mailnews/import/src/nsWMStringBundle.cpp b/comm/mailnews/import/src/nsWMStringBundle.cpp new file mode 100644 index 0000000000..bd60597cbc --- /dev/null +++ b/comm/mailnews/import/src/nsWMStringBundle.cpp @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +#include "prprf.h" +#include "prmem.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsIStringBundle.h" +#include "nsWMStringBundle.h" +#include "mozilla/Components.h" + +#define WM_MSGS_URL "chrome://messenger/locale/wmImportMsgs.properties" + +nsCOMPtr nsWMStringBundle::m_pBundle = nullptr; + +void nsWMStringBundle::GetStringBundle(void) { + if (m_pBundle) return; + + nsCOMPtr sBundleService = + mozilla::components::StringBundle::Service(); + if (sBundleService) { + sBundleService->CreateBundle(WM_MSGS_URL, getter_AddRefs(m_pBundle)); + } +} + +void nsWMStringBundle::GetStringByID(int32_t stringID, nsString& result) { + char16_t* ptrv = GetStringByID(stringID); + result = ptrv; + FreeString(ptrv); +} + +char16_t* nsWMStringBundle::GetStringByID(int32_t stringID) { + if (!m_pBundle) GetStringBundle(); + + if (m_pBundle) { + nsAutoString str; + nsresult rv = m_pBundle->GetStringFromID(stringID, str); + + if (NS_SUCCEEDED(rv)) return ToNewUnicode(str); + } + + nsString resultString; + resultString.AppendLiteral("[StringID "); + resultString.AppendInt(stringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsWMStringBundle::Cleanup(void) { m_pBundle = nullptr; } diff --git a/comm/mailnews/import/src/nsWMStringBundle.h b/comm/mailnews/import/src/nsWMStringBundle.h new file mode 100644 index 0000000000..45c92f75d6 --- /dev/null +++ b/comm/mailnews/import/src/nsWMStringBundle.h @@ -0,0 +1,36 @@ +/* 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 _nsWMStringBundle_H__ +#define _nsWMStringBundle_H__ + +#include "nsString.h" + +class nsIStringBundle; + +class nsWMStringBundle { + public: + static char16_t* GetStringByID(int32_t stringID); + static void GetStringByID(int32_t stringID, nsString& result); + static void GetStringBundle(void); + static void FreeString(char16_t* pStr) { free(pStr); } + static void Cleanup(void); + + private: + static nsCOMPtr m_pBundle; +}; + +#define WMIMPORT_NAME 2000 +#define WMIMPORT_DESCRIPTION 2001 +#define WMIMPORT_MAILBOX_SUCCESS 2002 +#define WMIMPORT_MAILBOX_BADPARAM 2003 +#define WMIMPORT_MAILBOX_BADSOURCEFILE 2004 +#define WMIMPORT_MAILBOX_CONVERTERROR 2005 +#define WMIMPORT_DEFAULT_NAME 2006 +#define WMIMPORT_AUTOFIND 2007 +#define WMIMPORT_ADDRESS_SUCCESS 2008 +#define WMIMPORT_ADDRESS_CONVERTERROR 2009 +#define WMIMPORT_ADDRESS_BADPARAM 2010 + +#endif /* _nsWMStringBundle_H__ */ diff --git a/comm/mailnews/import/src/nsWMUtils.cpp b/comm/mailnews/import/src/nsWMUtils.cpp new file mode 100644 index 0000000000..0172a30a82 --- /dev/null +++ b/comm/mailnews/import/src/nsWMUtils.cpp @@ -0,0 +1,153 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsNetCID.h" +#include "nsString.h" +#include "mozilla/dom/Document.h" +#include "nsWMUtils.h" +#include "nsINodeList.h" +#include "nsContentList.h" +#include "nsINode.h" +#include "nsIFileStreams.h" +#include "nsIFile.h" +#include "nsIDirectoryEnumerator.h" +#include "ImportDebug.h" +#include "prio.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/DOMParser.h" + +nsresult nsWMUtils::FindWMKey(nsIWindowsRegKey** aKey) { + nsresult rv; + nsCOMPtr key = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + u"Software\\Microsoft\\Windows Live Mail"_ns, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + key.forget(aKey); + return rv; + } + + rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + u"Software\\Microsoft\\Windows Mail"_ns, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + key.forget(aKey); + return rv; +} + +nsresult nsWMUtils::GetRootFolder(nsIFile** aRootFolder) { + nsCOMPtr key; + if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) { + IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n"); + return NS_ERROR_NOT_AVAILABLE; + } + // This is essential to proceed; it is the location on disk of xml-type + // account files; it is in reg_expand_sz so it will need expanding to absolute + // path. + nsString storeRoot; + nsresult rv = key->ReadStringValue(u"Store Root"_ns, storeRoot); + key->Close(); // Finished with windows registry key. We do not want to return + // before this closing + if (NS_FAILED(rv) || storeRoot.IsEmpty()) { + IMPORT_LOG0("*** Error finding Windows Live Mail Store Root\n"); + return rv; + } + + uint32_t size = + ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(), nullptr, 0); + nsString expandedStoreRoot; + expandedStoreRoot.SetLength(size - 1); + if (expandedStoreRoot.Length() != size - 1) return NS_ERROR_FAILURE; + ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(), + (LPWSTR)expandedStoreRoot.BeginWriting(), size); + storeRoot = expandedStoreRoot; + + nsCOMPtr rootFolder( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rootFolder->InitWithPath(storeRoot); + NS_ENSURE_SUCCESS(rv, rv); + + rootFolder.forget(aRootFolder); + + return NS_OK; +} + +nsresult nsWMUtils::GetOEAccountFiles(nsCOMArray& aFileArray) { + nsCOMPtr rootFolder; + + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + return GetOEAccountFilesInFolder(rootFolder, aFileArray); +} + +nsresult nsWMUtils::GetOEAccountFilesInFolder(nsIFile* aFolder, + nsCOMArray& aFileArray) { + nsCOMPtr entries; + nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_FAILED(rv) || !entries) return NS_ERROR_FAILURE; + + bool hasMore; + while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr file; + rv = entries->GetNextFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + GetOEAccountFilesInFolder(file, aFileArray); + } else { + nsString name; + rv = file->GetLeafName(name); + NS_ENSURE_SUCCESS(rv, rv); + if (StringEndsWith(name, u".oeaccount"_ns)) aFileArray.AppendObject(file); + } + } + return NS_OK; +} + +nsresult nsWMUtils::MakeXMLdoc(mozilla::dom::Document** aXmlDoc, + nsIFile* aFile) { + nsresult rv; + nsCOMPtr stream = + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->Init(aFile, PR_RDONLY, -1, 0); + mozilla::ErrorResult rv2; + RefPtr parser = + mozilla::dom::DOMParser::CreateWithoutGlobal(rv2); + if (rv2.Failed()) { + return rv2.StealNSResult(); + } + int64_t filesize; + aFile->GetFileSize(&filesize); + nsCOMPtr xmldoc = parser->ParseFromStream( + stream, EmptyString(), int32_t(filesize), + mozilla::dom::SupportedType::Application_xml, rv2); + xmldoc.forget(aXmlDoc); + return rv2.StealNSResult(); +} + +nsresult nsWMUtils::GetValueForTag(mozilla::dom::Document* aXmlDoc, + const char* aTagName, nsAString& aValue) { + nsAutoString tagName; + tagName.AssignASCII(aTagName); + nsCOMPtr list = aXmlDoc->GetElementsByTagName(tagName); + nsCOMPtr node = list->Item(0); + if (!node) return NS_ERROR_FAILURE; + mozilla::ErrorResult rv2; + node->GetTextContent(aValue, rv2); + return rv2.StealNSResult(); +} diff --git a/comm/mailnews/import/src/nsWMUtils.h b/comm/mailnews/import/src/nsWMUtils.h new file mode 100644 index 0000000000..02f15c6379 --- /dev/null +++ b/comm/mailnews/import/src/nsWMUtils.h @@ -0,0 +1,23 @@ +/* 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 nsWMUtils_h___ +#define nsWMUtils_h___ + +#include +#include "nsIWindowsRegKey.h" + +class nsWMUtils { + public: + static nsresult FindWMKey(nsIWindowsRegKey** aKey); + static nsresult GetRootFolder(nsIFile** aRootFolder); + static nsresult GetOEAccountFiles(nsCOMArray& aFileArray); + static nsresult GetOEAccountFilesInFolder(nsIFile* aFolder, + nsCOMArray& aFileArray); + static nsresult MakeXMLdoc(mozilla::dom::Document** aXmlDoc, nsIFile* aFile); + static nsresult GetValueForTag(mozilla::dom::Document* aXmlDoc, + const char* aTagName, nsAString& aValue); +}; + +#endif /* nsWMUtils_h___ */ diff --git a/comm/mailnews/import/src/rtfDecoder.cpp b/comm/mailnews/import/src/rtfDecoder.cpp new file mode 100644 index 0000000000..86a8151618 --- /dev/null +++ b/comm/mailnews/import/src/rtfDecoder.cpp @@ -0,0 +1,561 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include "windows.h" +#include "rtfDecoder.h" + +#define SIZEOF(x) (sizeof(x) / sizeof((x)[0])) +#define IS_DIGIT(i) ((i) >= '0' && (i) <= '9') +#define IS_ALPHA(VAL) \ + (((VAL) >= 'a' && (VAL) <= 'z') || ((VAL) >= 'A' && (VAL) <= 'Z')) + +inline int HexToInt(char ch) { + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return ch - '0'; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return ch - 'A' + 10; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return ch - 'a' + 10; + default: + return 0; + } +} + +inline int CharsetToCP(int charset) { + // We don't know the Code page for the commented out charsets. + switch (charset) { + case 0: + return 1252; // ANSI + case 1: + return 0; // Default + // case 2: return 42; // Symbol + case 2: + return 1252; // Symbol + case 77: + return 10000; // Mac Roman + case 78: + return 10001; // Mac Shift Jis + case 79: + return 10003; // Mac Hangul + case 80: + return 10008; // Mac GB2312 + case 81: + return 10002; // Mac Big5 + // case 82: Mac Johab (old) + case 83: + return 10005; // Mac Hebrew + case 84: + return 10004; // Mac Arabic + case 85: + return 10006; // Mac Greek + case 86: + return 10081; // Mac Turkish + case 87: + return 10021; // Mac Thai + case 88: + return 10029; // Mac East Europe + case 89: + return 10007; // Mac Russian + case 128: + return 932; // Shift JIS + case 129: + return 949; // Hangul + case 130: + return 1361; // Johab + case 134: + return 936; // GB2312 + case 136: + return 950; // Big5 + case 161: + return 1253; // Greek + case 162: + return 1254; // Turkish + case 163: + return 1258; // Vietnamese + case 177: + return 1255; // Hebrew + case 178: + return 1256; // Arabic + // case 179: Arabic Traditional (old) + // case 180: Arabic user (old) + // case 181: Hebrew user (old) + case 186: + return 1257; // Baltic + case 204: + return 1251; // Russian + case 222: + return 874; // Thai + case 238: + return 1250; // Eastern European + case 254: + return 437; // PC 437 + case 255: + return 850; // OEM + default: + return CP_ACP; + } +} + +struct FontInfo { + enum Options { has_fcharset = 0x0001, has_cpg = 0x0002 }; + unsigned int options; + int fcharset; + unsigned int cpg; + FontInfo() : options(0), fcharset(0), cpg(0xFFFFFFFF) {} + unsigned int Codepage() { + if (options & has_cpg) + return cpg; + else if (options & has_fcharset) + return CharsetToCP(fcharset); + else + return 0xFFFFFFFF; + } +}; +typedef std::map Fonttbl; + +struct LocalState { + bool fonttbl; // When fonts are being defined + int f; // Index of the font being defined/used; defines the codepage if no + // \cpg + unsigned int uc; // ucN keyword value; its default is 1 + unsigned int codepage; // defined by \cpg +}; +typedef std::stack StateStack; + +struct GlobalState { + enum Pcdata_state { pcdsno, pcdsin, pcdsfinished }; + std::istream& stream; + Fonttbl fonttbl; + StateStack stack; + unsigned int codepage; // defined by \ansi, \mac, \pc, \pca, and \ansicpgN + int deff; + std::stringstream pcdata_a; + unsigned int pcdata_a_codepage; + Pcdata_state pcdata_a_state; + + explicit GlobalState(std::istream& s) + : stream(s), codepage(CP_ACP), deff(-1), pcdata_a_state(pcdsno) { + LocalState st; + st.fonttbl = false; + st.f = -1; + st.uc = 1; + st.codepage = 0xFFFFFFFF; + stack.push(st); + } + unsigned int GetCurrentCP() { + if (stack.top().codepage != 0xFFFFFFFF) // \cpg in use + return stack.top().codepage; + // \cpg not used; use font settings + int f = (stack.top().f != -1) ? stack.top().f : deff; + if (f != -1) { + Fonttbl::iterator iter = fonttbl.find(f); + if (iter != fonttbl.end()) { + unsigned int cp = iter->second.Codepage(); + if (cp != 0xFFFFFFFF) return cp; + } + } + return codepage; // No overrides; use the top-level legacy setting + } +}; + +struct Keyword { + char name[33]; + bool hasVal; + int val; +}; + +class Lexem { + public: + enum Type { + ltGroupBegin, + ltGroupEnd, + ltKeyword, + ltPCDATA_A, + ltPCDATA_W, + ltBDATA, + ltEOF, + ltError + }; + explicit Lexem(Type t = ltError) : m_type(t) {} + Lexem(Lexem& from) { + switch (m_type = from.m_type) { + case ltKeyword: + m_keyword = from.m_keyword; + break; + case ltPCDATA_A: + m_pcdata_a = from.m_pcdata_a; + break; + case ltPCDATA_W: + m_pcdata_w = from.m_pcdata_w; + break; + case ltBDATA: + m_bdata = from.m_bdata; // Move pointers when copying. + from.m_type = ltError; // Invalidate the original. Not nice. + break; + } + } + ~Lexem() { Clear(); } + Lexem& operator=(Lexem& from) { + if (&from != this) { + Clear(); + switch (m_type = from.m_type) { + case ltKeyword: + m_keyword = from.m_keyword; + break; + case ltPCDATA_A: + m_pcdata_a = from.m_pcdata_a; + break; + case ltPCDATA_W: + m_pcdata_w = from.m_pcdata_w; + break; + case ltBDATA: + m_bdata = from.m_bdata; // Move pointers when copying. + from.m_type = ltError; // Invalidate the original. Not nice. + break; + } + } + return *this; + } + Type type() const { return m_type; } + void SetPCDATA_A(char chdata) { + Clear(); + m_pcdata_a = chdata; + m_type = ltPCDATA_A; + } + void SetPCDATA_W(wchar_t chdata) { + Clear(); + m_pcdata_w = chdata; + m_type = ltPCDATA_W; + } + void SetBDATA(const char* data, int sz) { + char* tmp = new char[sz]; // to allow getting the data from itself + if (tmp) { + memcpy(tmp, data, sz); + Clear(); + m_bdata.data = tmp; + m_bdata.sz = sz; + m_type = ltBDATA; + } else + m_type = ltError; + } + void SetKeyword(const Keyword& src) { + Clear(); + m_type = ltKeyword; + m_keyword = src; + } + void SetKeyword(const char* name, bool hasVal = false, int val = 0) { + char tmp[SIZEOF(m_keyword.name)]; + strncpy(tmp, name, + SIZEOF(m_keyword.name) - 1); // to allow copy drom itself + tmp[SIZEOF(m_keyword.name) - 1] = 0; + Clear(); + m_type = ltKeyword; + memcpy(m_keyword.name, tmp, SIZEOF(m_keyword.name)); + m_keyword.hasVal = hasVal; + m_keyword.val = val; + } + const char* KeywordName() const { + return (m_type == ltKeyword) ? m_keyword.name : 0; + } + const int* KeywordVal() const { + return ((m_type == ltKeyword) && m_keyword.hasVal) ? &m_keyword.val : 0; + } + char pcdata_a() const { return (m_type == ltPCDATA_A) ? m_pcdata_a : 0; } + wchar_t pcdata_w() const { return (m_type == ltPCDATA_W) ? m_pcdata_w : 0; } + const char* bdata() const { return (m_type == ltBDATA) ? m_bdata.data : 0; } + int bdata_sz() const { return (m_type == ltBDATA) ? m_bdata.sz : 0; } + static Lexem eof; + static Lexem groupBegin; + static Lexem groupEnd; + static Lexem error; + + private: + struct BDATA { + size_t sz; + char* data; + }; + + Type m_type; + union { + Keyword m_keyword; + char m_pcdata_a; + wchar_t m_pcdata_w; + BDATA m_bdata; + }; + // This function leaves the object in the broken state. Must be followed + // by a correct initialization. + void Clear() { + switch (m_type) { + case ltBDATA: + delete[] m_bdata.data; + break; + } + // m_type = ltError; + } +}; + +Lexem Lexem::eof(ltEOF); +Lexem Lexem::groupBegin(ltGroupBegin); +Lexem Lexem::groupEnd(ltGroupEnd); +Lexem Lexem::error(ltError); + +// This function moves pos. When calling the function, pos must be next to the +// backslash; pos must be in the same sequence and before end! +Keyword GetKeyword(std::istream& stream) { + Keyword keyword = {"", false, 0}; + char ch; + if (stream.get(ch).eof()) return keyword; + // Control word; maybe delimiter and value + if (IS_ALPHA(ch)) { + int i = 0; + do { + // We take up to 32 characters into account, skipping over extra + // characters (allowing for some non-conformant implementation). + if (i < 32) keyword.name[i++] = ch; + } while (!stream.get(ch).eof() && IS_ALPHA(ch)); + keyword.name[i] = 0; // NULL-terminating + if (!stream.eof() && (IS_DIGIT(ch) || (ch == '-'))) { // Value begin + keyword.hasVal = true; + bool negative = (ch == '-'); + if (negative) stream.get(ch); + i = 0; + while (!stream.eof() && IS_DIGIT(ch)) { + // We take into account only 10 digits, skip other. Older specs stated + // that we must be ready for an arbitrary number of digits. + if (i++ < 10) keyword.val = keyword.val * 10 + (ch - '0'); + stream.get(ch); + } + if (negative) keyword.val = -keyword.val; + } + // End of control word; the space is just a delimiter - skip it + if (!stream.eof() && !(ch == ' ')) stream.unget(); + } else { // Control symbol + keyword.name[0] = ch; + keyword.name[1] = 0; + } + return keyword; +} + +void GetLexem(std::istream& stream, Lexem& result) { + // We always stay at the beginning of the next lexem or a crlf + // If it's a brace then it's group begin/end + // If it's a backslash -> Preprocess + // - if it's a \u or \' -> make UTF16 character + // - else it's a keyword -> Process (e.g., remember the codepage) + // - (if the keyword is \bin then the following is #BDATA) + // If it's some other character -> Preprocess + // - if it's 0x09 -> it's the keyword \tab + // - else it's a PCDATA + char ch; + while (!stream.get(ch).eof() && ((ch == '\n') || (ch == '\r'))) + ; // Skip crlf + if (stream.eof()) + result = Lexem::eof; + else { + switch (ch) { + case '{': // Group begin + case '}': // Group end + result = (ch == '{') ? Lexem::groupBegin : Lexem::groupEnd; + break; + case '\\': // Keyword + result.SetKeyword(GetKeyword(stream)); + break; + case '\t': // tab + result.SetKeyword("tab"); + break; + default: // PSDATA? + result.SetPCDATA_A(ch); + break; + } + } +} + +void PreprocessLexem(/*inout*/ Lexem& lexem, std::istream& stream, int uc) { + if (lexem.type() == Lexem::ltKeyword) { + if (lexem.KeywordName()[0] == 0) // Empty keyword - maybe eof? + lexem = Lexem::error; + else if (eq(lexem.KeywordName(), "u")) { + // Unicode character - get the UTF16 and skip the uc characters + if (const int* val = lexem.KeywordVal()) { + lexem.SetPCDATA_W(*val); + stream.ignore(uc); + } else + lexem = Lexem::error; + } else if (eq(lexem.KeywordName(), "'")) { + // 8-bit character (\'hh) -> use current codepage + char ch = 0, ch1 = 0; + if (!stream.get(ch).eof()) ch1 = HexToInt(ch); + if (!stream.get(ch).eof()) (ch1 <<= 4) += HexToInt(ch); + lexem.SetPCDATA_A(ch1); + } else if (eq(lexem.KeywordName(), "\\") || eq(lexem.KeywordName(), "{") || + eq(lexem.KeywordName(), "}")) // escaped characters + lexem.SetPCDATA_A(lexem.KeywordName()[0]); + else if (eq(lexem.KeywordName(), "bin")) { + if (const int* i = lexem.KeywordVal()) { + char* data = new char[*i]; + if (data) { + stream.read(data, *i); + if (stream.fail()) + lexem = Lexem::error; + else + lexem.SetBDATA(data, *i); + delete[] data; + } else + lexem = Lexem::error; + } else + lexem = Lexem::error; + } else if (eq(lexem.KeywordName(), "\n") || eq(lexem.KeywordName(), "\r")) { + // escaped cr or lf + lexem.SetKeyword("par"); + } + } +} + +void UpdateState(const Lexem& lexem, /*inout*/ GlobalState& globalState) { + switch (globalState.pcdata_a_state) { + case GlobalState::pcdsfinished: // Last time we finished the pcdata + globalState.pcdata_a_state = GlobalState::pcdsno; + break; + case GlobalState::pcdsin: + // to be reset later if still in the pcdata + globalState.pcdata_a_state = GlobalState::pcdsfinished; + break; + } + + switch (lexem.type()) { + case Lexem::ltGroupBegin: + globalState.stack.push(globalState.stack.top()); + break; + case Lexem::ltGroupEnd: + globalState.stack.pop(); + break; + case Lexem::ltKeyword: { + const int* val = lexem.KeywordVal(); + if (eq(lexem.KeywordName(), "ansi")) + globalState.codepage = CP_ACP; + else if (eq(lexem.KeywordName(), "mac")) + globalState.codepage = CP_MACCP; + else if (eq(lexem.KeywordName(), "pc")) + globalState.codepage = 437; + else if (eq(lexem.KeywordName(), "pca")) + globalState.codepage = 850; + else if (eq(lexem.KeywordName(), "ansicpg") && val) + globalState.codepage = static_cast(*val); + else if (eq(lexem.KeywordName(), "deff") && val) + globalState.deff = *val; + else if (eq(lexem.KeywordName(), "fonttbl")) + globalState.stack.top().fonttbl = true; + else if (eq(lexem.KeywordName(), "f") && val) { + globalState.stack.top().f = *val; + } else if (eq(lexem.KeywordName(), "fcharset") && + globalState.stack.top().fonttbl && + (globalState.stack.top().f != -1) && val) { + FontInfo& f = globalState.fonttbl[globalState.stack.top().f]; + f.options |= FontInfo::has_fcharset; + f.fcharset = *val; + } else if (eq(lexem.KeywordName(), "cpg") && val) { + if (globalState.stack.top().fonttbl && + (globalState.stack.top().f != -1)) { // Defining a font + FontInfo& f = globalState.fonttbl[globalState.stack.top().f]; + f.options |= FontInfo::has_cpg; + f.cpg = *val; + } else { // Overriding the codepage for the block - may be in filenames + globalState.stack.top().codepage = *val; + } + } else if (eq(lexem.KeywordName(), "plain")) + globalState.stack.top().f = -1; + else if (eq(lexem.KeywordName(), "uc") && val) + globalState.stack.top().uc = *val; + } break; + case Lexem::ltPCDATA_A: + if (globalState.pcdata_a_state == + GlobalState::pcdsno) // Beginning of the pcdata + globalState.pcdata_a_codepage = + globalState.GetCurrentCP(); // to use later to convert to utf16 + globalState.pcdata_a_state = GlobalState::pcdsin; + globalState.pcdata_a << lexem.pcdata_a(); + break; + } +} + +void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder) { + // Check if this is the rtf + Lexem lexem; + GetLexem(rtf, lexem); + if (lexem.type() != Lexem::ltGroupBegin) return; + decoder.BeginGroup(); + GetLexem(rtf, lexem); + if ((lexem.type() != Lexem::ltKeyword) || !eq(lexem.KeywordName(), "rtf") || + !lexem.KeywordVal() || (*lexem.KeywordVal() != 1)) + return; + decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal()); + + GlobalState state(rtf); + // Level is the count of elements in the stack + + while (!state.stream.eof() && + (state.stack.size() > 0)) { // Don't go past the global group + GetLexem(state.stream, lexem); + PreprocessLexem(lexem, state.stream, state.stack.top().uc); + UpdateState(lexem, state); + + if (state.pcdata_a_state == GlobalState::pcdsfinished) { + std::string s = state.pcdata_a.str(); + int sz = ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), + s.size(), 0, 0); + if (sz) { + wchar_t* data = new wchar_t[sz]; + ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), s.size(), + data, sz); + decoder.PCDATA(data, sz); + delete[] data; + } + state.pcdata_a.str(""); // reset + } + + switch (lexem.type()) { + case Lexem::ltGroupBegin: + decoder.BeginGroup(); + break; + case Lexem::ltGroupEnd: + decoder.EndGroup(); + break; + case Lexem::ltKeyword: + decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal()); + break; + case Lexem::ltPCDATA_W: { + wchar_t ch = lexem.pcdata_w(); + decoder.PCDATA(&ch, 1); + } break; + case Lexem::ltBDATA: + decoder.BDATA(lexem.bdata(), lexem.bdata_sz()); + break; + case Lexem::ltError: + break; // Just silently skip the erroneous data - basic error recovery + } + } // while +} // DecodeRTF diff --git a/comm/mailnews/import/src/rtfDecoder.h b/comm/mailnews/import/src/rtfDecoder.h new file mode 100644 index 0000000000..1b547c77b8 --- /dev/null +++ b/comm/mailnews/import/src/rtfDecoder.h @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +template +inline bool eq(const char* str1, const char (&str2)[len]) { + return ::strncmp(str1, str2, len) == 0; +}; + +class CRTFDecoder { + public: + virtual void BeginGroup() = 0; + virtual void EndGroup() = 0; + virtual void Keyword(const char* name, const int* Val) = 0; + virtual void PCDATA(const wchar_t* data, size_t cch) = 0; + virtual void BDATA(const char* data, size_t sz) = 0; +}; + +void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder); diff --git a/comm/mailnews/import/src/rtfMailDecoder.cpp b/comm/mailnews/import/src/rtfMailDecoder.cpp new file mode 100644 index 0000000000..c5a234320e --- /dev/null +++ b/comm/mailnews/import/src/rtfMailDecoder.cpp @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "rtfMailDecoder.h" + +void CRTFMailDecoder::BeginGroup() { + ClearState(sAsterisk); + SetState(sBeginGroup); + if (m_skipLevel) ++m_skipLevel; +} + +void CRTFMailDecoder::EndGroup() { + ClearState(sAsterisk | sBeginGroup); + if (m_skipLevel) --m_skipLevel; +} + +void CRTFMailDecoder::AddText(const wchar_t* txt, size_t cch) { + if (!IsHtmlRtf()) { + if (cch == static_cast(-1)) + m_text += txt; + else + m_text.append(txt, cch); + } +} + +void CRTFMailDecoder::Keyword(const char* name, const int* Val) { + bool asterisk = IsAsterisk(); + ClearState(sAsterisk); // for inside use only + bool beginGroup = IsBeginGroup(); + ClearState(sBeginGroup); // for inside use only + if (!m_skipLevel) { + if (eq(name, "*") && beginGroup) + SetState(sAsterisk); + else if (asterisk) { + if (eq(name, "htmltag") && + (m_mode == + mHTML)) { // \*\htmltag -> don't ignore; include the following text + } else + ++m_skipLevel; + } else if (eq(name, "htmlrtf")) { + if (Val && (*Val == 0)) + ClearState(sHtmlRtf); + else + SetState(sHtmlRtf); + } else if (eq(name, "par") || eq(name, "line")) { + AddText(L"\r\n"); + } else if (eq(name, "tab")) { + AddText(L"\t"); + } else if (eq(name, "rquote")) { + AddText(L"\x2019"); // Unicode right single quotation mark + } else if (eq(name, "fromtext") && + (m_mode == mNone)) { // avoid double "fromX" + m_mode = mText; + } else if (eq(name, "fromhtml") && + (m_mode == mNone)) { // avoid double "fromX" + m_mode = mHTML; + } else if (eq(name, "fonttbl") || eq(name, "colortbl") || + eq(name, "stylesheet") || eq(name, "pntext")) + ++m_skipLevel; + } +} + +void CRTFMailDecoder::PCDATA(const wchar_t* data, size_t cch) { + ClearState(sAsterisk | sBeginGroup); + if (!m_skipLevel) AddText(data, cch); +} + +void CRTFMailDecoder::BDATA(const char* data, size_t sz) { + ClearState(sAsterisk | sBeginGroup); +} diff --git a/comm/mailnews/import/src/rtfMailDecoder.h b/comm/mailnews/import/src/rtfMailDecoder.h new file mode 100644 index 0000000000..7f4063c5ae --- /dev/null +++ b/comm/mailnews/import/src/rtfMailDecoder.h @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" +#include +#include "rtfDecoder.h" + +class CRTFMailDecoder : public CRTFDecoder { + public: + enum Mode { mNone, mText, mHTML }; + CRTFMailDecoder() : m_mode(mNone), m_state(sNormal), m_skipLevel(0) {} + void BeginGroup() override; + void EndGroup() override; + void Keyword(const char* name, const int* Val) override; + void PCDATA(const wchar_t* data, size_t cch) override; + void BDATA(const char* data, size_t sz) override; + const wchar_t* text() { return m_text.c_str(); } + std::wstring::size_type textSize() { return m_text.size(); } + Mode mode() { return m_mode; } + + private: + enum State { + sNormal = 0x0000, + sBeginGroup = 0x0001, + sAsterisk = 0x0002, + sHtmlRtf = 0x0004 + }; + + std::wstring m_text; + Mode m_mode; + unsigned int m_state; // bitmask of State + // bool m_beginGroup; // true just after the { + // bool m_asterisk; // true just after the {\* + int m_skipLevel; // if >0 then we ignore everything + // bool m_htmlrtf; + inline void SetState(unsigned int s) { m_state |= s; } + inline void ClearState(unsigned int s) { m_state &= ~s; } + inline bool CheckState(State s) { return (m_state & s) != 0; } + inline bool IsAsterisk() { return CheckState(sAsterisk); } + inline bool IsBeginGroup() { return CheckState(sBeginGroup); } + inline bool IsHtmlRtf() { return CheckState(sHtmlRtf); } + void AddText(const wchar_t* txt, size_t cch = static_cast(-1)); +}; -- cgit v1.2.3