diff options
Diffstat (limited to 'mailnews/import/outlook/src')
24 files changed, 9033 insertions, 0 deletions
diff --git a/mailnews/import/outlook/src/MapiApi.cpp b/mailnews/import/outlook/src/MapiApi.cpp new file mode 100644 index 000000000..d6a159754 --- /dev/null +++ b/mailnews/import/outlook/src/MapiApi.cpp @@ -0,0 +1,1940 @@ +/* -*- 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/. */ + +#include "MapiDbgLog.h" +#include "MapiApi.h" + +#include <sstream> +#include "rtfMailDecoder.h" + +#include "prprf.h" +#include "nsMemory.h" +#include "nsMsgUtils.h" +#include "nsUnicharUtils.h" + +int CMapiApi::m_clients = 0; +BOOL CMapiApi::m_initialized = false; +nsTArray<CMsgStore*> *CMapiApi::m_pStores = NULL; +LPMAPISESSION CMapiApi::m_lpSession = NULL; +LPMDB CMapiApi::m_lpMdb = NULL; +HRESULT CMapiApi::m_lastError; +char16_t * CMapiApi::m_pUniBuff = NULL; +int CMapiApi::m_uniBuffLen = 0; +/* +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, + LPTSTR 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 = ::LoadLibrary("MAPI32.DLL"); + if (!hInst) + return FALSE; + FARPROC pProc = GetProcAddress(hInst, "MAPIGetNetscapeVersion"); + if (pProc) { + ::FreeLibrary(hInst); + hInst = ::LoadLibrary("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<CMsgStore*>(); +} + +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(); + + if (m_pUniBuff) + delete [] m_pUniBuff; + m_pUniBuff = NULL; + m_uniBuffLen = 0; + } +} + +void CMapiApi::CStrToUnicode(const char *pStr, nsString& result) +{ + result.Truncate(); + int wLen = MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), 0); + if (wLen >= m_uniBuffLen) { + if (m_pUniBuff) + delete [] m_pUniBuff; + m_pUniBuff = new char16_t[wLen + 64]; + m_uniBuffLen = wLen + 64; + } + if (wLen) { + MultiByteToWideChar(CP_ACP, 0, pStr, -1, wwc(m_pUniBuff), m_uniBuffLen); + result = m_pUniBuff; + } +} + +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 = -1; + 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, wwc(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 = -1; + 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<wchar_t*>(result)), val); + else // either PT_STRING8 or some other binary - use as is + val.Assign(static_cast<char*>(result)); + delete[] 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<wchar_t*>(result)); + else // either PT_STRING8 or some other binary + CStrToUnicode(static_cast<char*>(result), val); + delete[] 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); + char * pStr = uid.ToString(); + if (pStr) { + MAPI_TRACE2("%s %s\n", pTag, (const char *)pStr); + NS_Free(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 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 += "..."; + } + MsgReplaceSubstring(strVal, "\r", "\\r"); + MsgReplaceSubstring(strVal, "\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.AssignLiteral("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.AppendLiteral("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); + NS_Free(ansiStr); +#endif + pFolder->GetFilePath(str); +#ifdef MAPI_DEBUG + ansiStr = ToNewCString(str); + MAPI_TRACE2("depth=%d, filePath=%s\n", pFolder->GetDepth(), ansiStr); + NS_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; + HRESULT hr = m_lpMdb->StoreLogoff(&flags); + m_lpMdb->Release(); + m_lpMdb = NULL; + } +} + +void CMsgStore::SetEntryID(ULONG cbEid, LPENTRYID lpEid) +{ + HRESULT hr; + + 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; + hr = 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 = -1; + 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/mailnews/import/outlook/src/MapiApi.h b/mailnews/import/outlook/src/MapiApi.h new file mode 100644 index 000000000..8b59b80d8 --- /dev/null +++ b/mailnews/import/outlook/src/MapiApi.h @@ -0,0 +1,265 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsTArray.h" + +#include <stdio.h> + +#include <windows.h> +#include <mapi.h> +#include <mapix.h> +#include <mapidefs.h> +#include <mapicode.h> +#include <mapitags.h> +#include <mapiutil.h> +// 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, LPTSTR 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<CMsgStore*> * 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(); + 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<CMapiFolder*> m_array; +}; + + +class CMsgStore { +public: + 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/mailnews/import/outlook/src/MapiDbgLog.h b/mailnews/import/outlook/src/MapiDbgLog.h new file mode 100644 index 000000000..668418c42 --- /dev/null +++ b/mailnews/import/outlook/src/MapiDbgLog.h @@ -0,0 +1,40 @@ +/* -*- 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 <stdio.h> + +#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/mailnews/import/outlook/src/MapiMessage.cpp b/mailnews/import/outlook/src/MapiMessage.cpp new file mode 100644 index 000000000..52ec2e4c0 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMessage.cpp @@ -0,0 +1,1474 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef INITGUID +#define INITGUID +#endif + +#ifndef USES_IID_IMessage +#define USES_IID_IMessage +#endif + +#include "nscore.h" +#include <time.h> +#include "nsStringGlue.h" +#include "nsDirectoryServiceDefs.h" +#include "nsMsgUtils.h" +#include "nsMimeTypes.h" +#include "nsIOutputStream.h" + +#include "nsMsgCompCID.h" +#include "nsIMutableArray.h" +#include "MapiDbgLog.h" +#include "MapiApi.h" + +#include "MapiMimeTypes.h" + +#include <algorithm> +#include "nsMsgI18N.h" +#include "nsICharsetConverterManager.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "MapiMessage.h" + +#include "nsOutlookMail.h" + +// needed for the call the OpenStreamOnFile +extern LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer; +extern LPMAPIFREEBUFFER gpMapiFreeBuffer; + +// Sample From line: From - 1 Jan 1965 00:00:00 + +typedef const char * PC_S8; + +static const char * kWhitespace = "\b\t\r\n "; +static const char * sFromLine = "From - "; +static const char * sFromDate = "Mon Jan 1 00:00:00 1965"; +static const char * sDaysOfWeek[7] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static const char *sMonths[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +CMapiMessage::CMapiMessage(LPMESSAGE lpMsg) + : m_lpMsg(lpMsg), m_dldStateHeadersOnly(false), m_msgFlags(0) +{ + nsresult rv; + m_pIOService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return; + + FetchHeaders(); + if (ValidState()) { + BuildFromLine(); + FetchFlags(); + GetDownloadState(); + if (FullMessageDownloaded()) { + FetchBody(); + ProcessAttachments(); + } + } +} + +CMapiMessage::~CMapiMessage() +{ + ClearAttachments(); + if (m_lpMsg) + m_lpMsg->Release(); +} + +void CMapiMessage::FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ) +{ + long offset = _timezone; + s += sDaysOfWeek[tm.wDayOfWeek]; + s += ", "; + s.AppendInt((int32_t) tm.wDay); + s += " "; + s += sMonths[tm.wMonth - 1]; + s += " "; + s.AppendInt((int32_t) tm.wYear); + s += " "; + int val = tm.wHour; + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + s += ":"; + val = tm.wMinute; + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + s += ":"; + val = tm.wSecond; + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + if (includeTZ) { + s += " "; + if (offset < 0) { + offset *= -1; + s += "+"; + } + else + s += "-"; + offset /= 60; + val = (int) (offset / 60); + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + val = (int) (offset % 60); + if (val < 10) + s += "0"; + s.AppendInt((int32_t) val); + } +} + +bool CMapiMessage::EnsureHeader(CMapiMessageHeaders::SpecialHeader special, + ULONG mapiTag) +{ + if (m_headers.Value(special)) + return true; + + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, mapiTag); + bool success = false; + if (pVal) { + if (PROP_TYPE(pVal->ulPropTag) == PT_STRING8) { + if (pVal->Value.lpszA && strlen(pVal->Value.lpszA)) { + m_headers.SetValue(special, pVal->Value.lpszA); + success = true; + } + } + else if (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) { + if (pVal->Value.lpszW && wcslen(pVal->Value.lpszW)) { + m_headers.SetValue(special, NS_ConvertUTF16toUTF8(pVal->Value.lpszW).get()); + success = true; + } + } + CMapiApi::MAPIFreeBuffer(pVal); + } + + return success; +} + +bool CMapiMessage::EnsureDate() +{ + if (m_headers.Value(CMapiMessageHeaders::hdrDate)) + return true; + + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_DELIVERY_TIME); + if (!pVal) + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME); + if (pVal) { + SYSTEMTIME st; + // the following call returns UTC + ::FileTimeToSystemTime(&(pVal->Value.ft), &st); + CMapiApi::MAPIFreeBuffer(pVal); + // FormatDateTime would append the local time zone, so don't use it. + // Instead, we just append +0000 for GMT/UTC here. + nsCString str; + FormatDateTime(st, str, false); + str += " +0000"; + m_headers.SetValue(CMapiMessageHeaders::hdrDate, str.get()); + return true; + } + + return false; +} + +void CMapiMessage::BuildFromLine(void) +{ + m_fromLine = sFromLine; + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME); + if (pVal) { + SYSTEMTIME st; + ::FileTimeToSystemTime(&(pVal->Value.ft), &st); + CMapiApi::MAPIFreeBuffer(pVal); + FormatDateTime(st, m_fromLine, FALSE); + } + else + m_fromLine += sFromDate; + + m_fromLine += "\x0D\x0A"; +} + +#ifndef dispidHeaderItem +#define dispidHeaderItem 0x8578 +#endif +DEFINE_OLEGUID(PSETID_Common, MAKELONG(0x2000+(8),0x0006),0,0); + +void CMapiMessage::GetDownloadState() +{ + // See http://support.microsoft.com/kb/912239 + HRESULT hRes = S_OK; + ULONG ulVal = 0; + LPSPropValue lpPropVal = NULL; + LPSPropTagArray lpNamedPropTag = NULL; + MAPINAMEID NamedID = {0}; + LPMAPINAMEID lpNamedID = NULL; + + NamedID.lpguid = (LPGUID) &PSETID_Common; + NamedID.ulKind = MNID_ID; + NamedID.Kind.lID = dispidHeaderItem; + lpNamedID = &NamedID; + + hRes = m_lpMsg->GetIDsFromNames(1, &lpNamedID, NULL, &lpNamedPropTag); + + if (lpNamedPropTag && 1 == lpNamedPropTag->cValues) + { + lpNamedPropTag->aulPropTag[0] = CHANGE_PROP_TYPE(lpNamedPropTag->aulPropTag[0], PT_LONG); + + //Get the value of the property. + hRes = m_lpMsg->GetProps(lpNamedPropTag, 0, &ulVal, &lpPropVal); + if (lpPropVal && 1 == ulVal && PT_LONG == PROP_TYPE(lpPropVal->ulPropTag) && + lpPropVal->Value.ul) + m_dldStateHeadersOnly = true; + } + + CMapiApi::MAPIFreeBuffer(lpPropVal); + CMapiApi::MAPIFreeBuffer(lpNamedPropTag); +} + +// Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS +// or if they do not exist will build a header from +// PR_DISPLAY_TO, _CC, _BCC +// PR_SUBJECT +// PR_MESSAGE_RECIPIENTS +// and PR_CREATION_TIME if needed? +bool CMapiMessage::FetchHeaders(void) +{ + ULONG tag = PR_TRANSPORT_MESSAGE_HEADERS_A; + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag); + if (!pVal) + pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag = PR_TRANSPORT_MESSAGE_HEADERS_W); + if (pVal) { + if (CMapiApi::IsLargeProperty(pVal)) { + nsCString headers; + CMapiApi::GetLargeStringProperty(m_lpMsg, tag, headers); + m_headers.Assign(headers.get()); + } + else if ((PROP_TYPE(pVal->ulPropTag) == PT_STRING8) && + (pVal->Value.lpszA) && (*(pVal->Value.lpszA))) + m_headers.Assign(pVal->Value.lpszA); + else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) && + (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) { + nsCString headers; + LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), headers); + m_headers.Assign(headers.get()); + } + + CMapiApi::MAPIFreeBuffer(pVal); + } + + EnsureDate(); + if (!EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_NAME_W)) + EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_EMAIL_ADDRESS_W); + EnsureHeader(CMapiMessageHeaders::hdrSubject, PR_SUBJECT_W); + EnsureHeader(CMapiMessageHeaders::hdrTo, PR_DISPLAY_TO_W); + EnsureHeader(CMapiMessageHeaders::hdrCc, PR_DISPLAY_CC_W); + EnsureHeader(CMapiMessageHeaders::hdrBcc, PR_DISPLAY_BCC_W); + + ProcessContentType(); + + return !m_headers.IsEmpty(); +} + +// Mime-Version: 1.0 +// Content-Type: text/plain; charset="US-ASCII" +// Content-Type: multipart/mixed; boundary="=====================_874475278==_" + +void CMapiMessage::ProcessContentType() +{ + m_mimeContentType.Truncate(); + m_mimeBoundary.Truncate(); + m_mimeCharset.Truncate(); + + const char* contentType = m_headers.Value(CMapiMessageHeaders::hdrContentType); + if (!contentType) + return; + + const char *begin = contentType, *end; + nsCString tStr; + + // Note: this isn't a complete parser, the content type + // we extract could have rfc822 comments in it + while (*begin && IsSpace(*begin)) + begin++; + if (!(*begin)) + return; + end = begin; + while (*end && (*end != ';')) + end++; + m_mimeContentType.Assign(begin, end-begin); + if (!(*end)) + return; + // look for "boundary=" + begin = end + 1; + bool haveB; + bool haveC; + while (*begin) { + haveB = false; + haveC = false; + while (*begin && IsSpace(*begin)) + begin++; + if (!(*begin)) + return; + end = begin; + while (*end && (*end != '=')) + end++; + if (end - begin) { + tStr.Assign(begin, end-begin); + if (tStr.LowerCaseEqualsLiteral("boundary")) + haveB = true; + else if (tStr.LowerCaseEqualsLiteral("charset")) + haveC = true; + } + if (!(*end)) + return; + begin = end+1; + while (*begin && IsSpace(*begin)) + begin++; + if (*begin == '"') { + begin++; + bool slash = false; + tStr.Truncate(); + while (*begin) { + if (slash) { + slash = false; + tStr.Append(*begin); + } + else if (*begin == '"') + break; + else if (*begin != '\\') + tStr.Append(*begin); + else + slash = true; + begin++; + } + if (haveB) { + m_mimeBoundary = tStr; + haveB = false; + } + if (haveC) { + m_mimeCharset = tStr; + haveC = false; + } + if (!(*begin)) + return; + begin++; + } + tStr.Truncate(); + while (*begin && (*begin != ';')) { + tStr.Append(*(begin++)); + } + if (haveB) { + tStr.Trim(kWhitespace); + m_mimeBoundary = tStr; + } + if (haveC) { + tStr.Trim(kWhitespace); + m_mimeCharset = tStr; + } + if (*begin) + begin++; + } +} + +const char* CpToCharset(unsigned int cp) +{ + struct CODEPAGE_TO_CHARSET { + unsigned long cp; + const char* charset; + }; + + // This table is based on http://msdn.microsoft.com/en-us/library/dd317756(v=VS.85).aspx#1; + // Please extend as appropriate. The codepage values are sorted ascending. + static const CODEPAGE_TO_CHARSET cptocharset[] = + { + {37, "IBM037"}, // IBM EBCDIC US-Canada + {437, "IBM437"}, //OEM United States + {500, "IBM500"}, //IBM EBCDIC International + {708, "ASMO-708"}, //Arabic (ASMO 708) + //709 Arabic (ASMO-449+, BCON V4) + //710 Arabic - Transparent Arabic + {720, "DOS-720"}, //Arabic (Transparent ASMO); Arabic (DOS) + {737, "ibm737"}, // OEM Greek (formerly 437G); Greek (DOS) + {775, "ibm775"}, // OEM Baltic; Baltic (DOS) + {850, "ibm850"}, // OEM Multilingual Latin 1; Western European (DOS) + {852, "ibm852"}, // OEM Latin 2; Central European (DOS) + {855, "IBM855"}, // OEM Cyrillic (primarily Russian) + {857, "ibm857"}, // OEM Turkish; Turkish (DOS) + {858, "IBM00858"}, // OEM Multilingual Latin 1 + Euro symbol + {860, "IBM860"}, // OEM Portuguese; Portuguese (DOS) + {861, "ibm861"}, // OEM Icelandic; Icelandic (DOS) + {862, "DOS-862"}, // OEM Hebrew; Hebrew (DOS) + {863, "IBM863"}, // OEM French Canadian; French Canadian (DOS) + {864, "IBM864"}, // OEM Arabic; Arabic (864) + {865, "IBM865"}, // OEM Nordic; Nordic (DOS) + {866, "cp866"}, // OEM Russian; Cyrillic (DOS) + {869, "ibm869"}, // OEM Modern Greek; Greek, Modern (DOS) + {870, "IBM870"}, // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2 + {874, "windows-874"}, // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai (Windows) + {875, "cp875"}, // IBM EBCDIC Greek Modern + {932, "shift_jis"}, // ANSI/OEM Japanese; Japanese (Shift-JIS) + {936, "gb2312"}, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312) + {949, "ks_c_5601-1987"}, // ANSI/OEM Korean (Unified Hangul Code) + {950, "big5"}, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5) + {1026, "IBM1026"}, // IBM EBCDIC Turkish (Latin 5) + {1047, "IBM01047"}, // IBM EBCDIC Latin 1/Open System + {1140, "IBM01140"}, // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro) + {1141, "IBM01141"}, // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro) + {1142, "IBM01142"}, // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro) + {1143, "IBM01143"}, // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro) + {1144, "IBM01144"}, // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro) + {1145, "IBM01145"}, // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro) + {1146, "IBM01146"}, // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro) + {1147, "IBM01147"}, // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro) + {1148, "IBM01148"}, // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro) + {1149, "IBM01149"}, // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro) + {1200, "utf-16"}, // Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications + {1201, "unicodeFFFE"}, // Unicode UTF-16, big endian byte order; available only to managed applications + {1250, "windows-1250"}, // ANSI Central European; Central European (Windows) + {1251, "windows-1251"}, // ANSI Cyrillic; Cyrillic (Windows) + {1252, "windows-1252"}, // ANSI Latin 1; Western European (Windows) + {1253, "windows-1253"}, // ANSI Greek; Greek (Windows) + {1254, "windows-1254"}, // ANSI Turkish; Turkish (Windows) + {1255, "windows-1255"}, // ANSI Hebrew; Hebrew (Windows) + {1256, "windows-1256"}, // ANSI Arabic; Arabic (Windows) + {1257, "windows-1257"}, // ANSI Baltic; Baltic (Windows) + {1258, "windows-1258"}, // ANSI/OEM Vietnamese; Vietnamese (Windows) + {1361, "Johab"}, // Korean (Johab) + {10000, "macintosh"}, // MAC Roman; Western European (Mac) + {10001, "x-mac-japanese"}, // Japanese (Mac) + {10002, "x-mac-chinesetrad"}, // MAC Traditional Chinese (Big5); Chinese Traditional (Mac) + {10003, "x-mac-korean"}, // Korean (Mac) + {10004, "x-mac-arabic"}, // Arabic (Mac) + {10005, "x-mac-hebrew"}, // Hebrew (Mac) + {10006, "x-mac-greek"}, // Greek (Mac) + {10007, "x-mac-cyrillic"}, // Cyrillic (Mac) + {10008, "x-mac-chinesesimp"}, // MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac) + {10010, "x-mac-romanian"}, // Romanian (Mac) + {10017, "x-mac-ukrainian"}, // Ukrainian (Mac) + {10021, "x-mac-thai"}, // Thai (Mac) + {10029, "x-mac-ce"}, // MAC Latin 2; Central European (Mac) + {10079, "x-mac-icelandic"}, // Icelandic (Mac) + {10081, "x-mac-turkish"}, // Turkish (Mac) + {10082, "x-mac-croatian"}, // Croatian (Mac) + // Unicode UTF-32, little endian byte order; available only to managed applications + // impossible in 8-bit mail + {12000, "utf-32"}, + // Unicode UTF-32, big endian byte order; available only to managed applications + // impossible in 8-bit mail + {12001, "utf-32BE"}, + {20000, "x-Chinese_CNS"}, // CNS Taiwan; Chinese Traditional (CNS) + {20001, "x-cp20001"}, // TCA Taiwan + {20002, "x_Chinese-Eten"}, // Eten Taiwan; Chinese Traditional (Eten) + {20003, "x-cp20003"}, // IBM5550 Taiwan + {20004, "x-cp20004"}, // TeleText Taiwan + {20005, "x-cp20005"}, // Wang Taiwan + {20105, "x-IA5"}, // IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5) + {20106, "x-IA5-German"}, // IA5 German (7-bit) + {20107, "x-IA5-Swedish"}, // IA5 Swedish (7-bit) + {20108, "x-IA5-Norwegian"}, // IA5 Norwegian (7-bit) + {20127, "us-ascii"}, // US-ASCII (7-bit) + {20261, "x-cp20261"}, // T.61 + {20269, "x-cp20269"}, // ISO 6937 Non-Spacing Accent + {20273, "IBM273"}, // IBM EBCDIC Germany + {20277, "IBM277"}, // IBM EBCDIC Denmark-Norway + {20278, "IBM278"}, // IBM EBCDIC Finland-Sweden + {20280, "IBM280"}, // IBM EBCDIC Italy + {20284, "IBM284"}, // IBM EBCDIC Latin America-Spain + {20285, "IBM285"}, // IBM EBCDIC United Kingdom + {20290, "IBM290"}, // IBM EBCDIC Japanese Katakana Extended + {20297, "IBM297"}, // IBM EBCDIC France + {20420, "IBM420"}, // IBM EBCDIC Arabic + {20423, "IBM423"}, // IBM EBCDIC Greek + {20424, "IBM424"}, // IBM EBCDIC Hebrew + {20833, "x-EBCDIC-KoreanExtended"}, // IBM EBCDIC Korean Extended + {20838, "IBM-Thai"}, // IBM EBCDIC Thai + {20866, "koi8-r"}, // Russian (KOI8-R); Cyrillic (KOI8-R) + {20871, "IBM871"}, // IBM EBCDIC Icelandic + {20880, "IBM880"}, // IBM EBCDIC Cyrillic Russian + {20905, "IBM905"}, // IBM EBCDIC Turkish + {20924, "IBM00924"}, // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol) + {20932, "EUC-JP"}, // Japanese (JIS 0208-1990 and 0121-1990) + {20936, "x-cp20936"}, // Simplified Chinese (GB2312); Chinese Simplified (GB2312-80) + {20949, "x-cp20949"}, // Korean Wansung + {21025, "cp1025"}, // IBM EBCDIC Cyrillic Serbian-Bulgarian + //21027 (deprecated) + {21866, "koi8-u"}, // Ukrainian (KOI8-U); Cyrillic (KOI8-U) + {28591, "iso-8859-1"}, // ISO 8859-1 Latin 1; Western European (ISO) + {28592, "iso-8859-2"}, // ISO 8859-2 Central European; Central European (ISO) + {28593, "iso-8859-3"}, // ISO 8859-3 Latin 3 + {28594, "iso-8859-4"}, // ISO 8859-4 Baltic + {28595, "iso-8859-5"}, // ISO 8859-5 Cyrillic + {28596, "iso-8859-6"}, // ISO 8859-6 Arabic + {28597, "iso-8859-7"}, // ISO 8859-7 Greek + {28598, "iso-8859-8"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Visual) + {28599, "iso-8859-9"}, // ISO 8859-9 Turkish + {28603, "iso-8859-13"}, // ISO 8859-13 Estonian + {28605, "iso-8859-15"}, // ISO 8859-15 Latin 9 + {29001, "x-Europa"}, // Europa 3 + {38598, "iso-8859-8-i"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Logical) + {50220, "iso-2022-jp"}, // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS) + {50221, "csISO2022JP"}, // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana) + {50222, "iso-2022-jp"}, // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI) + {50225, "iso-2022-kr"}, // ISO 2022 Korean + {50227, "x-cp50227"}, // ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022) + //50229 ISO 2022 Traditional Chinese + //50930 EBCDIC Japanese (Katakana) Extended + //50931 EBCDIC US-Canada and Japanese + //50933 EBCDIC Korean Extended and Korean + //50935 EBCDIC Simplified Chinese Extended and Simplified Chinese + //50936 EBCDIC Simplified Chinese + //50937 EBCDIC US-Canada and Traditional Chinese + //50939 EBCDIC Japanese (Latin) Extended and Japanese + {51932, "euc-jp"}, // EUC Japanese + {51936, "EUC-CN"}, // EUC Simplified Chinese; Chinese Simplified (EUC) + {51949, "euc-kr"}, // EUC Korean + //51950 EUC Traditional Chinese + {52936, "hz-gb-2312"}, // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ) + {54936, "GB18030"}, // Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030) + {57002, "x-iscii-de"}, // ISCII Devanagari + {57003, "x-iscii-be"}, // ISCII Bengali + {57004, "x-iscii-ta"}, // ISCII Tamil + {57005, "x-iscii-te"}, // ISCII Telugu + {57006, "x-iscii-as"}, // ISCII Assamese + {57007, "x-iscii-or"}, // ISCII Oriya (Odia) + {57008, "x-iscii-ka"}, // ISCII Kannada + {57009, "x-iscii-ma"}, // ISCII Malayalam + {57010, "x-iscii-gu"}, // ISCII Gujarati + {57011, "x-iscii-pa"}, // ISCII Punjabi + {65000, "utf-7"}, // Unicode (UTF-7) + {65001, "utf-8"}, // Unicode (UTF-8) + }; + + // Binary search + int begin = 0, end = sizeof(cptocharset)/sizeof(cptocharset[0])-1; + while (begin <= end) { + int mid = (begin+end)/2; + unsigned int mid_cp = cptocharset[mid].cp; + if (cp == mid_cp) + return cptocharset[mid].charset; + if (cp < mid_cp) + end = mid - 1; + else // cp > cptocharset[mid].cp + begin = mid + 1; + } + return 0; // not found +} + +// We don't use nsMsgI18Ncheck_data_in_charset_range because it returns true +// even if there's no such charset: +// 1. result initialized by true and returned if, eg, GetUnicodeEncoderRaw fail +// 2. it uses GetUnicodeEncoderRaw(), not GetUnicodeEncoder() (to normalize the +// charset string) (see nsMsgI18N.cpp) +// This function returns true only if the unicode (utf-16) text can be +// losslessly represented in specified charset +bool CMapiMessage::CheckBodyInCharsetRange(const char* charset) +{ + if (m_body.IsEmpty()) + return true; + if (!_stricmp(charset, "utf-8")) + return true; + if (!_stricmp(charset, "utf-7")) + return true; + + nsresult rv; + static nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIUnicodeEncoder> encoder; + + // get an unicode converter + rv = ccm->GetUnicodeEncoder(charset, getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, false); + rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Signal, nullptr, 0); + NS_ENSURE_SUCCESS(rv, false); + + const char16_t *txt = m_body.get(); + int32_t txtLen = m_body.Length(); + const char16_t *currentSrcPtr = txt; + int srcLength; + int dstLength; + char localbuf[512]; + int consumedLen = 0; + + // convert + while (consumedLen < txtLen) { + srcLength = txtLen - consumedLen; + dstLength = sizeof(localbuf)/sizeof(localbuf[0]); + rv = encoder->Convert(currentSrcPtr, &srcLength, localbuf, &dstLength); + if (rv == NS_ERROR_UENC_NOMAPPING) + return false; + if (NS_FAILED(rv) || dstLength == 0) + break; + + currentSrcPtr += srcLength; + consumedLen = currentSrcPtr - txt; // src length used so far + } + return true; +} + +bool CaseInsensitiveComp (wchar_t elem1, wchar_t elem2) +{ + return _wcsnicmp(&elem1, &elem2, 1) == 0; +} + +void ExtractMetaCharset(const wchar_t* body, int bodySz, /*out*/nsCString& charset) +{ + charset.Truncate(); + const wchar_t* body_end = body+bodySz; + const wchar_t str_eohd[] = L"/head"; + const wchar_t *str_eohd_end = str_eohd+sizeof(str_eohd)/sizeof(str_eohd[0])-1; + const wchar_t* eohd_pos = std::search(body, body_end, str_eohd, str_eohd_end, + CaseInsensitiveComp); + if (eohd_pos == body_end) // No header! + return; + const wchar_t str_chset[] = L"charset="; + const wchar_t *str_chset_end = + str_chset + sizeof(str_chset)/sizeof(str_chset[0])-1; + const wchar_t* chset_pos = std::search(body, eohd_pos, str_chset, + str_chset_end, CaseInsensitiveComp); + if (chset_pos == eohd_pos) // No charset! + return; + chset_pos += 8; + + // remove everything from the string after the next ; or " or space, + // whichever comes first. + // The inital sting looks something like + // <META content="text/html; charset=utf-8" http-equiv=Content-Type> + // <META content="text/html; charset=utf-8;" http-equiv=Content-Type> + // <META content="text/html; charset=utf-8 ;" http-equiv=Content-Type> + // <META content="text/html; charset=utf-8 " http-equiv=Content-Type> + const wchar_t term[] = L";\" ", *term_end= term+sizeof(term)/sizeof(term[0])-1; + const wchar_t* chset_end = std::find_first_of(chset_pos, eohd_pos, term, + term_end); + if (chset_end != eohd_pos) + LossyCopyUTF16toASCII(Substring(wwc(const_cast<wchar_t *>(chset_pos)), + wwc(const_cast<wchar_t *>(chset_end))), + charset); +} + +bool CMapiMessage::FetchBody(void) +{ + m_bodyIsHtml = false; + m_body.Truncate(); + + // Get the Outlook codepage info; if unsuccessful then it defaults to 0 (CP_ACP) -> system default + // Maybe we can use this info later? + unsigned int codepage=0; + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_INTERNET_CPID); + if (pVal) { + if (PROP_TYPE(pVal->ulPropTag) == PT_LONG) + codepage = pVal->Value.l; + CMapiApi::MAPIFreeBuffer(pVal); + } + + unsigned long nativeBodyType = 0; + if (CMapiApi::GetRTFPropertyDecodedAsUTF16(m_lpMsg, m_body, nativeBodyType, + codepage)) { + m_bodyIsHtml = nativeBodyType == MAPI_NATIVE_BODY_TYPE_HTML; + } + else { // Cannot get RTF version + // Is it html? + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_HTML_W); + if (pVal) { + if (CMapiApi::IsLargeProperty(pVal)) + CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_HTML_W, m_body); + else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) && + (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) + m_body.Assign(pVal->Value.lpszW); + CMapiApi::MAPIFreeBuffer(pVal); + } + + // Kind-hearted Outlook will give us html even for a plain text message. + // But it will include a comment saying it did the conversion. + // We'll use this as a hack to really use the plain text part. + // + // Sadly there are cases where this string is returned despite the fact + // that the message is indeed HTML. + // + // To detect the "true" plain text messages, we look for our string + // immediately following the <BODY> tag. + if (!m_body.IsEmpty() && + m_body.Find("<BODY>\r\n<!-- Converted from text/plain format -->") == + kNotFound) { + m_bodyIsHtml = true; + } + else { + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_W); + if (pVal) { + if (CMapiApi::IsLargeProperty(pVal)) + CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_W, m_body); + else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) && + (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) + m_body.Assign(pVal->Value.lpszW); + CMapiApi::MAPIFreeBuffer(pVal); + } + } + } + + // OK, now let's restore the original encoding! + // 1. We may have a header defining the charset (we already called the FetchHeaders(), and there ProcessHeaders(); + // in this case, the m_mimeCharset is set. See nsOutlookMail::ImportMailbox()) + // 2. We may have the codepage walue provided by Outlook ("codepage" at the very beginning of this function) + // 3. We may have an HTML charset header. + + bool bFoundCharset = false; + + if (!m_mimeCharset.IsEmpty()) // The top-level header data + bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get()); + // No valid charset in the message header - try the HTML header. + // arguably may be useless + if (!bFoundCharset && m_bodyIsHtml) { + ExtractMetaCharset(m_body.get(), m_body.Length(), m_mimeCharset); + if (!m_mimeCharset.IsEmpty()) + bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get()); + } + // Get from Outlook (seems like it keeps the MIME part header encoding info) + if (!bFoundCharset && codepage) { + const char* charset = CpToCharset(codepage); + if (charset) { + bFoundCharset = CheckBodyInCharsetRange(charset); + if (bFoundCharset) + m_mimeCharset.Assign(charset); + } + } + if (!bFoundCharset) { // Use system default + const char* charset = nsMsgI18NFileSystemCharset(); + if (charset) { + bFoundCharset = CheckBodyInCharsetRange(charset); + if (bFoundCharset) + m_mimeCharset.Assign(charset); + } + } + if (!bFoundCharset) // Everything else failed, let's use the lossless utf-8... + m_mimeCharset.Assign("utf-8"); + + MAPI_DUMP_STRING(m_body.get()); + MAPI_TRACE0("\r\n"); + + return true; +} + +void CMapiMessage::GetBody(nsCString& dest) const +{ + nsMsgI18NConvertFromUnicode(m_mimeCharset.get(), m_body, dest); +} + +void CMapiMessage::FetchFlags(void) +{ + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_FLAGS); + if (pVal) + m_msgFlags = CMapiApi::GetLongFromProp(pVal); + pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_LAST_VERB_EXECUTED); + if (pVal) + m_msgLastVerb = CMapiApi::GetLongFromProp(pVal); +} + +enum { + ieidPR_ATTACH_NUM = 0, + ieidAttachMax +}; + +static const SizedSPropTagArray(ieidAttachMax, ptaEid)= +{ + ieidAttachMax, + { + PR_ATTACH_NUM + } +}; + +bool CMapiMessage::IterateAttachTable(LPMAPITABLE lpTable) +{ + ULONG rowCount; + HRESULT hr = lpTable->GetRowCount(0, &rowCount); + if (!rowCount) { + return true; + } + + hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0); + if (FAILED(hr)) { + MAPI_TRACE2("SetColumns for attachment table failed: 0x%lx, %d\r\n", (long)hr, (int)hr); + return false; + } + + hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL); + if (FAILED(hr)) { + MAPI_TRACE2("SeekRow for attachment table failed: 0x%lx, %d\r\n", (long)hr, (int)hr); + return false; + } + + int cNumRows = 0; + LPSRowSet lpRow; + bool bResult = true; + do { + + lpRow = NULL; + hr = lpTable->QueryRows(1, 0, &lpRow); + + if(HR_FAILED(hr)) { + MAPI_TRACE2("QueryRows for attachment table failed: 0x%lx, %d\n", (long)hr, (int)hr); + bResult = false; + break; + } + + if (lpRow) { + cNumRows = lpRow->cRows; + + if (cNumRows) { + DWORD aNum = lpRow->aRow[0].lpProps[ieidPR_ATTACH_NUM].Value.ul; + AddAttachment(aNum); + MAPI_TRACE1("\t\t****Attachment found - #%d\r\n", (int)aNum); + } + CMapiApi::FreeProws(lpRow); + } + + } while (SUCCEEDED(hr) && cNumRows && lpRow); + + return bResult; +} + +bool CMapiMessage::GetTmpFile(/*out*/ nsIFile **aResult) +{ + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "mapiattach.tmp", + getter_AddRefs(tmpFile)); + if (NS_FAILED(rv)) + return false; + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + return false; + + tmpFile.forget(aResult); + return true; +} + +bool CMapiMessage::CopyMsgAttachToFile(LPATTACH lpAttach, /*out*/ nsIFile **tmp_file) +{ + bool bResult = true; + LPMESSAGE lpMsg; + HRESULT hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 0, + reinterpret_cast<LPUNKNOWN *>(&lpMsg)); + if (HR_FAILED(hr)) + return false; + + if (!GetTmpFile(tmp_file)) + return false; + + nsCOMPtr<nsIOutputStream> destOutputStream; + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(destOutputStream), *tmp_file, -1, 0600); + if (NS_SUCCEEDED(rv)) + rv = nsOutlookMail::ImportMessage(lpMsg, destOutputStream, nsIMsgSend::nsMsgSaveAsDraft); + + if (NS_FAILED(rv)) { + (*tmp_file)->Remove(false); + (*tmp_file)->Release(); + *tmp_file = 0; + } + + return NS_SUCCEEDED(rv); +} + +bool CMapiMessage::CopyBinAttachToFile(LPATTACH lpAttach, + nsIFile **tmp_file) +{ + nsCOMPtr<nsIFile> _tmp_file; + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + "mapiattach.tmp", + getter_AddRefs(_tmp_file)); + NS_ENSURE_SUCCESS(rv, false); + + rv = _tmp_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + NS_ENSURE_SUCCESS(rv, false); + + nsCString tmpPath; + _tmp_file->GetNativePath(tmpPath); + LPSTREAM lpStreamFile; + HRESULT hr = CMapiApi::OpenStreamOnFile(gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE, + const_cast<char*>(tmpPath.get()), NULL, &lpStreamFile); + if (HR_FAILED(hr)) { + MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n", + tmpPath.get()); + return false; + } + + bool bResult = true; + LPSTREAM lpAttachStream; + hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0, (LPUNKNOWN *)&lpAttachStream); + + if (HR_FAILED(hr)) { + MAPI_TRACE0("~~ERROR~~ OpenProperty failed for PR_ATTACH_DATA_BIN.\r\n"); + lpAttachStream = NULL; + bResult = false; + } + else { + STATSTG st; + hr = lpAttachStream->Stat(&st, STATFLAG_NONAME); + if (HR_FAILED(hr)) { + MAPI_TRACE0("~~ERROR~~ Stat failed for attachment stream\r\n"); + bResult = false; + } + else { + hr = lpAttachStream->CopyTo(lpStreamFile, st.cbSize, NULL, NULL); + if (HR_FAILED(hr)) { + MAPI_TRACE0("~~ERROR~~ Attach Stream CopyTo temp file failed.\r\n"); + bResult = false; + } + } + } + + if (lpAttachStream) + lpAttachStream->Release(); + lpStreamFile->Release(); + if (!bResult) + _tmp_file->Remove(false); + else + _tmp_file.forget(tmp_file); + + return bResult; +} + +bool CMapiMessage::GetURL(nsIFile *aFile, nsIURI **url) +{ + if (!m_pIOService) + return false; + + nsresult rv = m_pIOService->NewFileURI(aFile, url); + return NS_SUCCEEDED(rv); +} + +bool CMapiMessage::AddAttachment(DWORD aNum) +{ + LPATTACH lpAttach = NULL; + HRESULT hr = m_lpMsg->OpenAttach(aNum, NULL, 0, &lpAttach); + if (HR_FAILED(hr)) { + MAPI_TRACE2("\t\t****Attachment error, unable to open attachment: %d, 0x%lx\r\n", idx, hr); + return false; + } + + bool bResult = false; + attach_data *data = new attach_data; + ULONG aMethod; + if (data) { + bResult = true; + + // 1. Get the file that contains the attachment data + LPSPropValue pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_METHOD); + if (pVal) { + aMethod = CMapiApi::GetLongFromProp(pVal); + switch (aMethod) { + case ATTACH_BY_VALUE: + MAPI_TRACE1("\t\t** Attachment #%d by value.\r\n", aNum); + bResult = CopyBinAttachToFile(lpAttach, getter_AddRefs(data->tmp_file)); + data->delete_file = true; + break; + case ATTACH_BY_REFERENCE: + case ATTACH_BY_REF_RESOLVE: + case ATTACH_BY_REF_ONLY: + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_PATHNAME_W); + if (pVal) { + nsCString path; + CMapiApi::GetStringFromProp(pVal, path); + nsresult rv; + data->tmp_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv) || !data->tmp_file) { + MAPI_TRACE0("*** Error creating file spec for attachment\n"); + bResult = false; + } + else data->tmp_file->InitWithNativePath(path); + } + MAPI_TRACE2("\t\t** Attachment #%d by ref: %s\r\n", + aNum, m_attachPath.get()); + break; + case ATTACH_EMBEDDED_MSG: + MAPI_TRACE1("\t\t** Attachment #%d by Embedded Message??\r\n", aNum); + // Convert the embedded IMessage from PR_ATTACH_DATA_OBJ to rfc822 attachment + // (see http://msdn.microsoft.com/en-us/library/cc842329.aspx) + // This is a recursive call. + bResult = CopyMsgAttachToFile(lpAttach, getter_AddRefs(data->tmp_file)); + data->delete_file = true; + break; + case ATTACH_OLE: + MAPI_TRACE1("\t\t** Attachment #%d by OLE - yuck!!!\r\n", aNum); + break; + default: + MAPI_TRACE2("\t\t** Attachment #%d unknown attachment method - 0x%lx\r\n", aNum, aMethod); + bResult = false; + } + } + else + bResult = false; + + if (bResult) + bResult = data->tmp_file; + + if (bResult) { + bool isFile = false; + bool exists = false; + data->tmp_file->Exists(&exists); + data->tmp_file->IsFile(&isFile); + + if (!exists || !isFile) { + bResult = false; + MAPI_TRACE0("Attachment file does not exist\n"); + } + } + + if (bResult) + bResult = GetURL(data->tmp_file, getter_AddRefs(data->orig_url)); + + if (bResult) { + // Now we have the file; proceed to the other properties + + data->encoding = NS_strdup(ENCODING_BINARY); + + nsString fname, fext; + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_LONG_FILENAME_W); + if (!pVal) + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FILENAME_W); + CMapiApi::GetStringFromProp(pVal, fname); + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_EXTENSION_W); + CMapiApi::GetStringFromProp(pVal, fext); + MAPI_TRACE2("\t\t\t--- File name: %s, extension: %s\r\n", + fname.get(), fext.get()); + + if (fext.IsEmpty()) { + int idx = fname.RFindChar(L'.'); + if (idx != -1) + fext = Substring(fname, idx); + } + else if (fname.RFindChar(L'.') == -1) { + fname += L"."; + fname += fext; + } + if (fname.IsEmpty()) { + // If no description use "Attachment i" format. + fname = L"Attachment "; + fname.AppendInt(static_cast<uint32_t>(aNum)); + } + data->real_name = ToNewUTF8String(fname); + + nsCString tmp; + // We have converted it to the rfc822 document + if (aMethod == ATTACH_EMBEDDED_MSG) { + data->type = NS_strdup(MESSAGE_RFC822); + } else { + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_MIME_TAG_A); + CMapiApi::GetStringFromProp(pVal, tmp); + MAPI_TRACE1("\t\t\t--- Mime type: %s\r\n", tmp.get()); + if (tmp.IsEmpty()) { + uint8_t *pType = NULL; + if (!fext.IsEmpty()) { + pType = CMimeTypes::GetMimeType(fext); + } + if (pType) + data->type = NS_strdup((PC_S8)pType); + else + data->type = NS_strdup(APPLICATION_OCTET_STREAM); + } + else + data->type = ToNewCString(tmp); + } + + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_CONTENT_ID_A); + CMapiApi::GetStringFromProp(pVal, tmp); + if (!tmp.IsEmpty()) + data->cid = ToNewCString(tmp); + } + if (bResult) { + // Now we need to decide if this attachment is embedded or not. + // At first, I tried to simply check for the presence of the Content-Id. + // But it turned out that this method is unreliable, since there exist cases + // when an attachment has a Content-Id while isn't embedded (even in a message + // with a plain-text body!). So next I tried to look for <img> tags that contain + // the found Content-Id. But this is unreliable, too, because there exist cases + // where other places of HTML reference the embedded messages (e.g. it may be + // a background of a table cell, or some CSS; further, it is possible that the + // reference to an embedded object is not in the main body, but in another + // embedded object - like body references a CSS attachment that in turn references + // a picture as a background of its element). From the other hand, it's unreliable + // to relax the search criteria to any occurence of the Content-Id string in the body - + // partly because the string may be simply in a text or other non-referencing part, + // partly because of the abovementioned possibility that the reference is outside + // the body at all. + // There exist the PR_ATTACH_FLAGS property of the attachment. The MS documentation + // tells about two possible flags in it: ATT_INVISIBLE_IN_HTML and ATT_INVISIBLE_IN_RTF. + // There is at least one more undocumented flag: ATT_MHTML_REF. Some sources in Internet + // suggest simply check for the latter flag to distinguish between the embedded + // and ordinary attachments. But my observations indicate that even if the flags + // don't include ATT_MHTML_REF, the attachment is still may be embedded. + // However, my observations always show that the message is embedded if the flags + // is not 0. + // So now I will simply test for the non-zero flags to decide whether the attachment + // is embedded or not. Possible advantage is reliability (I hope). + // Another advantage is that it's much faster than search the body for Content-Id. + + DWORD flags = 0; + + pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FLAGS); + if (pVal) + flags = CMapiApi::GetLongFromProp(pVal); + if (m_bodyIsHtml && data->cid && (flags != 0)) // this is the embedded attachment + m_embattachments.push_back(data); + else // this is ordinary attachment + m_stdattachments.push_back(data); + } + else { + delete data; + } + } + + lpAttach->Release(); + return bResult; +} + +void CMapiMessage::ClearAttachment(attach_data* data) +{ + if (data->delete_file && data->tmp_file) + data->tmp_file->Remove(false); + + if (data->type) + NS_Free(data->type); + if (data->encoding) + NS_Free(data->encoding); + if (data->real_name) + NS_Free(data->real_name); + if (data->cid) + NS_Free(data->cid); + + delete data; +} + +void CMapiMessage::ClearAttachments() +{ + std::for_each(m_stdattachments.begin(), m_stdattachments.end(), ClearAttachment); + m_stdattachments.clear(); + std::for_each(m_embattachments.begin(), m_embattachments.end(), ClearAttachment); + m_embattachments.clear(); +} + +// This method must be called AFTER the retrieval of the body, +// since the decision if an attachment is embedded or not is made +// based on the body type and contents +void CMapiMessage::ProcessAttachments() +{ + LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_HASATTACH); + bool hasAttach = true; + + if (pVal) { + if (PROP_TYPE(pVal->ulPropTag) == PT_BOOLEAN) + hasAttach = (pVal->Value.b != 0); + CMapiApi::MAPIFreeBuffer(pVal); + } + + if (!hasAttach) + return; + + // Get the attachment table? + LPMAPITABLE pTable = NULL; + HRESULT hr = m_lpMsg->GetAttachmentTable(0, &pTable); + if (FAILED(hr) || !pTable) + return; + IterateAttachTable(pTable); + pTable->Release(); +} + +nsresult CMapiMessage::GetAttachments(nsIArray **aArray) +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> attachments (do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aArray = attachments); + + for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin(); + it != m_stdattachments.end(); it++) { + nsCOMPtr<nsIMsgAttachedFile> a(do_CreateInstance(NS_MSGATTACHEDFILE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + a->SetOrigUrl((*it)->orig_url); + a->SetTmpFile((*it)->tmp_file); + a->SetEncoding(nsDependentCString((*it)->encoding)); + a->SetRealName(nsDependentCString((*it)->real_name)); + a->SetType(nsDependentCString((*it)->type)); + attachments->AppendElement(a, false); + } + return rv; +} + +bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI **uri, + const char **cid, + const char **name) const +{ + if ((i < 0) || (i >= m_embattachments.size())) + return false; + attach_data* data = m_embattachments[i]; + if (!data) + return false; + *uri = data->orig_url; + *cid = data->cid; + *name = data->real_name; + return true; +} + +////////////////////////////////////////////////////// + +// begin and end MUST point to the same string +char* dup(const char* begin, const char* end) +{ + if (begin >= end) + return 0; + char* str = new char[end-begin+1]; + memcpy(str, begin, (end-begin)*sizeof(begin[0])); + str[end - begin] = 0; + return str; +} + +// See RFC822 +// 9 = '\t', 32 = ' '. +inline bool IsPrintableASCII(char c) { return (c > ' ') && (c < 127); } +inline bool IsWSP(char c) { return (c == ' ') || (c == '\t'); } + +CMapiMessageHeaders::CHeaderField::CHeaderField(const char* begin, int len) + : m_fname(0), m_fbody(0), m_fbody_utf8(false) +{ + const char *end = begin+len, *fname_end = begin; + while ((fname_end < end) && IsPrintableASCII(*fname_end) && (*fname_end != ':')) + ++fname_end; + if ((fname_end == end) || (*fname_end != ':')) + return; // Not a valid header! + m_fname = dup(begin, fname_end+1); // including colon + m_fbody = dup(fname_end+1, end); +} + +CMapiMessageHeaders::CHeaderField::CHeaderField(const char* name, const char* body, bool utf8) + : m_fname(dup(name, name+strlen(name))), m_fbody(dup(body, body+strlen(body))), m_fbody_utf8(utf8) +{ +} + +CMapiMessageHeaders::CHeaderField::~CHeaderField() +{ + delete[] m_fname; + delete[] m_fbody; +} + +void CMapiMessageHeaders::CHeaderField::set_fbody(const char* txt) +{ + if (m_fbody == txt) + return; // to avoid assigning to self + char* oldbody = m_fbody; + m_fbody = dup(txt, txt+strlen(txt)); + delete[] oldbody; + m_fbody_utf8 = true; +} + +void CMapiMessageHeaders::CHeaderField::GetUnfoldedString(nsString& dest, + const char* fallbackCharset) const +{ + dest.Truncate(); + if (!m_fbody) + return; + + nsCString unfolded; + const char* pos = m_fbody; + while (*pos) { + if ((*pos == nsCRT::CR) && (*(pos+1) == nsCRT::LF) && IsWSP(*(pos+2))) + pos += 2; // Skip CRLF if it is followed by SPACE or TAB + else + unfolded.Append(*(pos++)); + } + if (m_fbody_utf8) + CopyUTF8toUTF16(unfolded, dest); + else + nsMsgI18NConvertToUnicode(fallbackCharset, unfolded, dest); +} + +//////////////////////////////////////// + +const char* CMapiMessageHeaders::Specials[hdrMax] = { + "Date:", + "From:", + "Sender:", + "Reply-To:", + "To:", + "Cc:", + "Bcc:", + "Message-ID:", + "Subject:", + "Mime-Version:", + "Content-Type:", + "Content-Transfer-Encoding:" +}; + +CMapiMessageHeaders::~CMapiMessageHeaders() +{ + ClearHeaderFields(); +} + +void Delete(void* p) { delete p; } + +void CMapiMessageHeaders::ClearHeaderFields() +{ + std::for_each(m_headerFields.begin(), m_headerFields.end(), Delete); + m_headerFields.clear(); +} + +void CMapiMessageHeaders::Assign(const char* headers) +{ + for (int i=0; i<hdrMax; i++) + m_SpecialHeaders[i] = 0; + ClearHeaderFields(); + if (!headers) + return; + + const char *start=headers, *end=headers; + while (*end) { + if ((*end == nsCRT::CR) && (*(end+1) == nsCRT::LF)) { + if (!IsWSP(*(end+2))) { // Not SPACE nor TAB (avoid FSP) -> next header or EOF + Add(new CHeaderField(start, end-start)); + start = ++end + 1; + } + } + ++end; + } + + if (start < end) { // Last header left + Add(new CHeaderField(start, end-start)); + } +} + +void CMapiMessageHeaders::Add(CHeaderField* f) +{ + if (!f) + return; + if (!f->Valid()) { + delete f; + return; + } + + SpecialHeader idx = CheckSpecialHeader(f->fname()); + if (idx != hdrNone) { + // Now check if the special header was already inserted; + // if so, remove previous and add this new + CHeaderField* PrevSpecial = m_SpecialHeaders[idx]; + if (PrevSpecial) { + std::vector<CHeaderField*>::iterator iter = std::find(m_headerFields.begin(), m_headerFields.end(), PrevSpecial); + if (iter != m_headerFields.end()) + m_headerFields.erase(iter); + delete PrevSpecial; + } + m_SpecialHeaders[idx] = f; + } + m_headerFields.push_back(f); +} + +CMapiMessageHeaders::SpecialHeader CMapiMessageHeaders::CheckSpecialHeader(const char* fname) +{ + for (int i = hdrFirst; i < hdrMax; i++) + if (stricmp(fname, Specials[i]) == 0) + return static_cast<SpecialHeader>(i); + + return hdrNone; +} + +const CMapiMessageHeaders::CHeaderField* CMapiMessageHeaders::CFind(const char* name) const +{ + SpecialHeader special = CheckSpecialHeader(name); + if ((special > hdrNone) && (special < hdrMax)) + return m_SpecialHeaders[special]; // No need to search further, because it MUST be here + + std::vector<CHeaderField*>::const_iterator iter = std::find_if(m_headerFields.begin(), m_headerFields.end(), fname_equals(name)); + if (iter == m_headerFields.end()) + return 0; + return *iter; +} + +const char* CMapiMessageHeaders::SpecialName(SpecialHeader special) +{ + if ((special <= hdrNone) || (special >= hdrMax)) + return 0; + return Specials[special]; +} + +const char* CMapiMessageHeaders::Value(SpecialHeader special) const +{ + if ((special <= hdrNone) || (special >= hdrMax)) + return 0; + return (m_SpecialHeaders[special]) ? m_SpecialHeaders[special]->fbody() : 0; +} + +const char* CMapiMessageHeaders::Value(const char* name) const +{ + const CHeaderField* result = CFind(name); + return result ? result->fbody() : 0; +} + +void CMapiMessageHeaders::UnfoldValue(const char* name, nsString& dest, const char* fallbackCharset) const +{ + const CHeaderField* result = CFind(name); + if (result) + result->GetUnfoldedString(dest, fallbackCharset); + else + dest.Truncate(); +} + +void CMapiMessageHeaders::UnfoldValue(SpecialHeader special, nsString& dest, const char* fallbackCharset) const +{ + if ((special <= hdrNone) || (special >= hdrMax) || (!m_SpecialHeaders[special])) + dest.Truncate(); + else + m_SpecialHeaders[special]->GetUnfoldedString(dest, fallbackCharset); +} + +int CMapiMessageHeaders::SetValue(const char* name, const char* value, bool replace) +{ + if (!replace) { + CHeaderField* result = Find(name); + if (result) { + result->set_fbody(value); + return 0; + } + } + Add(new CHeaderField(name, value, true)); + return 0; // No sensible result is returned; maybe do something senseful later +} + +int CMapiMessageHeaders::SetValue(SpecialHeader special, const char* value) +{ + CHeaderField* result = m_SpecialHeaders[special]; + if (result) + result->set_fbody(value); + else + Add(new CHeaderField(Specials[special], value, true)); + return 0; +} + +void CMapiMessageHeaders::write_to_stream::operator () (const CHeaderField* f) +{ + if (!f || NS_FAILED(m_rv)) + return; + + uint32_t written; + m_rv = m_pDst->Write(f->fname(), strlen(f->fname()), &written); + NS_ENSURE_SUCCESS_VOID(m_rv); + if (f->fbody()) { + m_rv = m_pDst->Write(f->fbody(), strlen(f->fbody()), &written); + NS_ENSURE_SUCCESS_VOID(m_rv); + } + m_rv = m_pDst->Write("\x0D\x0A", 2, &written); +} + +nsresult CMapiMessageHeaders::ToStream(nsIOutputStream *pDst) const +{ + nsresult rv = std::for_each(m_headerFields.begin(), m_headerFields.end(), + write_to_stream(pDst)); + if (NS_SUCCEEDED(rv)) { + uint32_t written; + rv = pDst->Write("\x0D\x0A", 2, &written); // Separator line + } + return rv; +} diff --git a/mailnews/import/outlook/src/MapiMessage.h b/mailnews/import/outlook/src/MapiMessage.h new file mode 100644 index 000000000..dc6a6cda1 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMessage.h @@ -0,0 +1,271 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsIFile.h" +#include "MapiApi.h" +#include "nsIMsgSend.h" + +#include <vector> + +#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 + }; + + 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: + 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: + 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<CHeaderField*> m_headerFields; + CHeaderField* m_SpecialHeaders[hdrMax]; // Pointers into the m_headerFields + + void ClearHeaderFields(); + void Add(CHeaderField* f); + static SpecialHeader CheckSpecialHeader(const char* fname); + const CHeaderField* CFind(const char* name) const; + inline CHeaderField* Find(const char* name) { return const_cast<CHeaderField*>(CFind(name)); } + +}; // class CMapiMessageHeaders + +////////////////////////////////////////////////////// + +class CMapiMessage { +public: + CMapiMessage(LPMESSAGE lpMsg); + ~CMapiMessage(); + + // Attachments + // Ordinary (not embedded) attachments. + nsresult GetAttachments(nsIArray **aArray); + // 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<nsIURI> orig_url; + nsCOMPtr<nsIFile> 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<attach_data*> m_stdattachments; + std::vector<attach_data*> 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<nsIIOService> 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<char>(c)); } // Avoid false detections +}; + +#endif /* MapiMessage_h__ */ diff --git a/mailnews/import/outlook/src/MapiMimeTypes.cpp b/mailnews/import/outlook/src/MapiMimeTypes.cpp new file mode 100644 index 000000000..38b2046db --- /dev/null +++ b/mailnews/import/outlook/src/MapiMimeTypes.cpp @@ -0,0 +1,96 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "nsStringGlue.h" +#include "MapiMimeTypes.h" + +uint8_t CMimeTypes::m_mimeBuffer[kMaxMimeTypeSize]; + + +BOOL CMimeTypes::GetKey(HKEY root, LPCTSTR pName, PHKEY pKey) +{ + LONG result = RegOpenKeyEx(root, pName, 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, pKey); + return result == ERROR_SUCCESS; +} + +BOOL CMimeTypes::GetValueBytes(HKEY rootKey, LPCTSTR pValName, LPBYTE *ppBytes) +{ + LONG err; + DWORD bufSz; + + *ppBytes = NULL; + // Get the installed directory + err = RegQueryValueEx(rootKey, pValName, NULL, NULL, NULL, &bufSz); + if (err == ERROR_SUCCESS) { + *ppBytes = new BYTE[bufSz]; + err = RegQueryValueEx(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 nsCString& ext, LPBYTE *ppBytes) +{ + HKEY extensionKey; + BOOL result = FALSE; + *ppBytes = NULL; + if (GetKey(HKEY_CLASSES_ROOT, ext.get(), &extensionKey)) { + result = GetValueBytes(extensionKey, "Content Type", ppBytes); + RegCloseKey(extensionKey); + } + + return result; +} + +uint8_t * CMimeTypes::GetMimeType(const nsString& theExt) +{ + nsCString ext; + LossyCopyUTF16toASCII(theExt, ext); + return GetMimeType(ext); +} + +uint8_t * CMimeTypes::GetMimeType(const nsCString& theExt) +{ + nsCString ext = theExt; + if (ext.Length()) { + if (ext.First() != '.') { + ext = "."; + 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/mailnews/import/outlook/src/MapiMimeTypes.h b/mailnews/import/outlook/src/MapiMimeTypes.h new file mode 100644 index 000000000..61a90dd19 --- /dev/null +++ b/mailnews/import/outlook/src/MapiMimeTypes.h @@ -0,0 +1,31 @@ +/* -*- 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 <windows.h> + +#define kMaxMimeTypeSize 256 + +class CMimeTypes { +public: + +static uint8_t * GetMimeType(const nsCString& theExt); +static uint8_t * GetMimeType(const nsString& theExt); + +protected: + // Registry stuff +static BOOL GetKey(HKEY root, LPCTSTR pName, PHKEY pKey); +static BOOL GetValueBytes(HKEY rootKey, LPCTSTR pValName, LPBYTE *ppBytes); +static void ReleaseValueBytes(LPBYTE pBytes); +static BOOL GetMimeTypeFromReg(const nsCString& ext, LPBYTE *ppBytes); + + +static uint8_t m_mimeBuffer[kMaxMimeTypeSize]; +}; + +#endif /* MapiMimeTypes_h__ */ + diff --git a/mailnews/import/outlook/src/MapiTagStrs.cpp b/mailnews/import/outlook/src/MapiTagStrs.cpp new file mode 100644 index 000000000..217b2186c --- /dev/null +++ b/mailnews/import/outlook/src/MapiTagStrs.cpp @@ -0,0 +1,1070 @@ +/* -*- 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/. */ + +/* + * 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/mailnews/import/outlook/src/OutlookDebugLog.h b/mailnews/import/outlook/src/OutlookDebugLog.h new file mode 100644 index 000000000..5b189bf9b --- /dev/null +++ b/mailnews/import/outlook/src/OutlookDebugLog.h @@ -0,0 +1,24 @@ +/* -*- 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 OutlookDebugLog_h___ +#define OutlookDebugLog_h___ + +#ifdef NS_DEBUG +#define IMPORT_DEBUG 1 +#endif + +// Use PR_LOG for logging. +#include "mozilla/Logging.h" +extern PRLogModuleInfo *OUTLOOKLOGMODULE; // Logging module + +#define IMPORT_LOG0(x) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x)) +#define IMPORT_LOG1(x, y) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x, y)) +#define IMPORT_LOG2(x, y, z) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (x, y, z)) +#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(OUTLOOKLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d)) + + + +#endif /* OutlookDebugLog_h___ */ diff --git a/mailnews/import/outlook/src/moz.build b/mailnews/import/outlook/src/moz.build new file mode 100644 index 000000000..4ffbad572 --- /dev/null +++ b/mailnews/import/outlook/src/moz.build @@ -0,0 +1,24 @@ +# 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 += [ + 'MapiApi.cpp', + 'MapiMessage.cpp', + 'MapiMimeTypes.cpp', + 'nsOutlookCompose.cpp', + 'nsOutlookImport.cpp', + 'nsOutlookMail.cpp', + 'nsOutlookSettings.cpp', + 'nsOutlookStringBundle.cpp', + 'rtfDecoder.cpp', + 'rtfMailDecoder.cpp', +] + +FINAL_LIBRARY = 'import' + +LOCAL_INCLUDES += [ + '../../src' +] + diff --git a/mailnews/import/outlook/src/nsOutlookCompose.cpp b/mailnews/import/outlook/src/nsOutlookCompose.cpp new file mode 100644 index 000000000..eb47a29fd --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookCompose.cpp @@ -0,0 +1,815 @@ +/* -*- 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/. */ + +#include "nscore.h" +#include "prthread.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsUnicharUtils.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "nsMsgI18N.h" +#include "nsINetUtil.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsMsgAttachmentData.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsIArray.h" +#include "nsIMsgCompose.h" +#include "nsIMsgCompFields.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgSend.h" +#include "nsIMutableArray.h" +#include "nsImportEmbeddedImageData.h" +#include "nsNetCID.h" +#include "nsCRT.h" +#include "nsOutlookCompose.h" + +#include "OutlookDebugLog.h" + +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" + +#include "nsAutoPtr.h" + +#include "nsMsgMessageFlags.h" +#include "nsMsgLocalFolderHdrs.h" + +#include <algorithm> + +static NS_DEFINE_CID(kMsgSendCID, NS_MSGSEND_CID); +static NS_DEFINE_CID(kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID); + +// We need to do some calculations to set these numbers to something reasonable! +// Unless of course, CreateAndSendMessage will NEVER EVER leave us in the lurch +#define kHungCount 100000 +#define kHungAbortCount 1000 + +#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: <ikGD3jRTsKklU.Ggm2HmE2A1Jsqd0p@netppl.invalid>\n\ +From: \"adsales@qualityservice.invalid\" <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); + + 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<nsIFile> m_pFile; + nsCOMPtr<nsIInputStream> 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 <class _OutFn> nsresult ToDest(_OutFn dest, const char* term, int termSize); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +// First off, a listener +class OutlookSendListener : public nsIMsgSendListener +{ +public: + OutlookSendListener() { + m_done = false; + m_location = nullptr; + } + + // 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; + NS_IF_ADDREF(m_location = returnFile); + return NS_OK; + } + + /* void OnSendNotPerformed */ + NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus) {return NS_OK;} + + /* void OnGetDraftFolderURI (); */ + NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;} + + static nsresult CreateSendListener(nsIMsgSendListener **ppListener); + void Reset() { m_done = false; NS_IF_RELEASE(m_location);} + +public: + virtual ~OutlookSendListener() { NS_IF_RELEASE(m_location); } + + bool m_done; + nsIFile * m_location; +}; + +NS_IMPL_ISUPPORTS(OutlookSendListener, nsIMsgSendListener) + +nsresult OutlookSendListener::CreateSendListener(nsIMsgSendListener **ppListener) +{ + NS_PRECONDITION(ppListener != nullptr, "null ptr"); + NS_ENSURE_ARG_POINTER(ppListener); + + *ppListener = new OutlookSendListener(); + if (! *ppListener) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*ppListener); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////// + +#define hackBeginA "begin" +#define hackBeginW u"begin" +#define hackEndA "\015\012end" +#define hackEndW u"\015\012end" +#define hackCRLFA "crlf" +#define hackCRLFW u"crlf" +#define hackAmpersandA "amp" +#define hackAmpersandW u"amp" + +nsOutlookCompose::nsOutlookCompose() +{ + m_pListener = nullptr; + m_pMsgFields = nullptr; + + m_optimizationBuffer = new char[FILE_IO_BUFFER_SIZE]; +} + +nsOutlookCompose::~nsOutlookCompose() +{ + NS_IF_RELEASE(m_pListener); + NS_IF_RELEASE(m_pMsgFields); + 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; +} + +nsIMsgIdentity * nsOutlookCompose::m_pIdentity = nullptr; + +nsresult nsOutlookCompose::CreateIdentity(void) +{ + if (m_pIdentity) + return NS_OK; + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = accMgr->CreateIdentity(&m_pIdentity); + nsString name; + name.AssignLiteral("Import Identity"); + if (m_pIdentity) { + m_pIdentity->SetFullName(name); + m_pIdentity->SetEmail(NS_LITERAL_CSTRING("import@service.invalid")); + } + return rv; +} + +void nsOutlookCompose::ReleaseIdentity() +{ + NS_IF_RELEASE(m_pIdentity); +} + +nsresult nsOutlookCompose::CreateComponents(void) +{ + nsresult rv = NS_OK; + + NS_IF_RELEASE(m_pMsgFields); + if (!m_pListener && NS_SUCCEEDED(rv)) + rv = OutlookSendListener::CreateSendListener(&m_pListener); + + if (NS_SUCCEEDED(rv)) { + rv = CallCreateInstance(kMsgCompFieldsCID, &m_pMsgFields); + 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); + m_pMsgFields->SetCharacterSet(msg.GetBodyCharset()); + 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(). + + nsCOMPtr<nsIArray> pAttach; + msg.GetAttachments(getter_AddRefs(pAttach)); + + nsString bodyW; + // Bug 593907 + if (GenerateHackSequence(msg.GetBody(), msg.GetBodyLen())) + HackBody(msg.GetBody(), msg.GetBodyLen(), bodyW); + else + bodyW = msg.GetBody(); + // End Bug 593907 + + nsCOMPtr<nsIMutableArray> 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)) { + if (!embeddedObjects) { + embeddedObjects = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr<nsIMsgEmbeddedImageData> imageData = + new nsImportEmbeddedImageData(uri, nsDependentCString(cid), + nsDependentCString(name)); + embeddedObjects->AppendElement(imageData, false); + } + } + } + + nsCString bodyA; + nsMsgI18NConvertFromUnicode(msg.GetBodyCharset(), bodyW, bodyA); + + nsCOMPtr<nsIImportService> impService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = impService->CreateRFC822Message( + m_pIdentity, // dummy identity + m_pMsgFields, // message fields + msg.BodyIsHtml() ? "text/html" : "text/plain", + bodyA, // body pointer + mode == nsIMsgSend::nsMsgSaveAsDraft, + pAttach, // local attachments + embeddedObjects, + m_pListener); // listener + + OutlookSendListener *pListen = (OutlookSendListener *)m_pListener; + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** Error, CreateAndSendMessage FAILED: 0x%lx\n", rv); + } + else { + // wait for the listener to get done! + int32_t abortCnt = 0; + int32_t cnt = 0; + int32_t sleepCnt = 1; + while (!pListen->m_done && (abortCnt < kHungAbortCount)) { + PR_Sleep(sleepCnt); + cnt++; + if (cnt > kHungCount) { + abortCnt++; + sleepCnt *= 2; + cnt = 0; + } + } + + if (abortCnt >= kHungAbortCount) { + IMPORT_LOG0("**** Create and send message hung\n"); + rv = NS_ERROR_FAILURE; + } + } + + 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(), newHeadersStr.get()); + rv = origMsg.GetHeaders()->ToStream(pDst); + NS_ENSURE_SUCCESS(rv, rv); + + // Bug 593907 + if (!m_hackedPostfix.IsEmpty()) { + nsCString hackedPartEnd; + LossyCopyUTF16toASCII(m_hackedPostfix, hackedPartEnd); + hackedPartEnd.Insert(hackEndA, 0); + nsCString body; + rv = f.ToString(body, hackedPartEnd.get(), hackedPartEnd.Length()); + UnhackBody(body); + EscapeFromSpaceLine(pDst, const_cast<char*>(body.get()), body.get()+body.Length()); + } + // End Bug 593907 + + // 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<char*>(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<nsIFile> 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); +} + +// Bug 593907 +// This is just a workaround of the deficiency of the nsMsgComposeAndSend::EnsureLineBreaks(). +// The import from Outlook will stay OK (I hope), but other messages may still suffer. +// However, I cannot deny the possibility that the (possible) recode of the body +// may interfere with this hack. A possible scenario is if a multi-byte character will either +// contain 0x0D 0x0A sequence, or end with 0x0D, after which MAC-style standalone LF will go. +// I hope that this possibility is insignificant (eg, utf-8 doesn't contain such sequences). +// This hack will slow down the import, but as the import is one-time procedure, I hope that +// the user will agree to wait a little longer to get better results. + +// The process of composing the message differs depending on whether the editor is present or not. +// If the editor is absent, the "attachment1_body" parameter of CreateAndSendMessage() is taken as is, +// while in the presence o the editor, the body that is taken from it is further processed in the +// nsMsgComposeAndSend::GetBodyFromEditor(). Specifically, the TXTToHTML::ScanHTML() first calls +// UnescapeStr() to properly handle a limited number of HTML character entities (namely & < > ") +// and then calls ScanTXT() where escapes all ampersands and quotes again. As the UnescapeStr() works so +// selectively (i.e. handling only a subset of valid entities), the so often seen " " becomes "&nbsp;" +// in the resulting body, which leads to text " " interspersed all over the imported mail. The same +// applies to html &#XXXX; (where XXXX is unicode codepoint). +// See also Bug 503690, where the same issue in Eudora import is reported. +// By the way, the root of the Bug 359303 lies in the same place - the nsMsgComposeAndSend::GetBodyFromEditor() +// changes the 0xA0 codes to 0x20 when it converts the body to plain text. +// We scan the body here to find all the & and convert them to the safe character sequense to revert later. + +void nsOutlookCompose::HackBody(const wchar_t* orig, size_t origLen, nsString& hack) +{ +#ifdef MOZILLA_INTERNAL_API + hack.SetCapacity(static_cast<size_t>(origLen*1.4)); +#endif + hack.Assign(hackBeginW); + hack.Append(m_hackedPostfix); + + while (*orig) { + if (*orig == L'&') { + hack.Append(hackAmpersandW); + hack.Append(m_hackedPostfix); + } else if ((*orig == L'\x0D') && (*(orig+1) == L'\x0A')) { + hack.Append(hackCRLFW); + hack.Append(m_hackedPostfix); + ++orig; + } else + hack.Append(*orig); + ++orig; + } + + hack.Append(hackEndW); + hack.Append(m_hackedPostfix); +} + +void nsOutlookCompose::UnhackBody(nsCString& txt) +{ + nsCString hackedPostfixA; + LossyCopyUTF16toASCII(m_hackedPostfix, hackedPostfixA); + + nsCString hackedString(hackBeginA); + hackedString.Append(hackedPostfixA); + int32_t begin = txt.Find(hackedString); + if (begin == kNotFound) + return; + txt.Cut(begin, hackedString.Length()); + + hackedString.Assign(hackEndA); + hackedString.Append(hackedPostfixA); + int32_t end = MsgFind(txt, hackedString, false, begin); + if (end == kNotFound) + return; // ? + txt.Cut(end, hackedString.Length()); + + nsCString range; + range.Assign(Substring(txt, begin, end - begin)); + // 1. Remove all CRLFs from the selected range + MsgReplaceSubstring(range, MSG_LINEBREAK, ""); + // 2. Restore the original CRLFs + hackedString.Assign(hackCRLFA); + hackedString.Append(hackedPostfixA); + MsgReplaceSubstring(range, hackedString.get(), MSG_LINEBREAK); + + // 3. Restore the original ampersands + hackedString.Assign(hackAmpersandA); + hackedString.Append(hackedPostfixA); + MsgReplaceSubstring(range, hackedString.get(), "&"); + + txt.Replace(begin, end - begin, range); +} + +bool nsOutlookCompose::GenerateHackSequence(const wchar_t* body, size_t origLen) +{ + nsDependentString nsBody(body, origLen); + const wchar_t* hack_base = L"hacked"; + int i = 0; + do { + if (++i == 0) { // Cycle complete :) - could not generate an unique string + m_hackedPostfix.Truncate(); + return false; + } + m_hackedPostfix.Assign(hack_base); + m_hackedPostfix.AppendInt(i); + } while (nsBody.Find(m_hackedPostfix) != kNotFound); + + return true; +} +// End Bug 593907 + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer, + uint32_t fifoBufferSize, bool convertCRs) + : m_pFile(aFile), m_fileSize(0), m_fileReadPos(0), + m_fifoBuffer(static_cast<char*>(fifoBuffer)), + m_fifoBufferSize(fifoBufferSize), + m_fifoBufferReadPos(static_cast<char*>(fifoBuffer)), + m_fifoBufferWrittenPos(static_cast<char*>(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 <class _OutFn> +nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize) +{ + CTermGuard guard(term, termSize); + +#ifdef MOZILLA_INTERNAL_API + // We already know the required string size, so reduce future reallocations + if (!guard.IsChecking() && !m_convertCRs) + dest.SetCapacity(m_fileSize - m_fileReadPos); +#endif + + 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: + dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); } +#ifdef MOZILLA_INTERNAL_API + void SetCapacity(int32_t sz) { m_str.SetCapacity(sz); } +#endif + nsresult Append(const char* buf, uint32_t count) { + m_str.Append(buf, count); return NS_OK; } +private: + nsCString& m_str; +}; + +class dest_Stream { +public: + dest_Stream(nsIOutputStream *dest) : m_stream(dest) {} +#ifdef MOZILLA_INTERNAL_API + void SetCapacity(int32_t) { /*do nothing*/ } +#endif + // 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<char*>(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/mailnews/import/outlook/src/nsOutlookCompose.h b/mailnews/import/outlook/src/nsOutlookCompose.h new file mode 100644 index 000000000..68f07f754 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookCompose.h @@ -0,0 +1,66 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsIFile.h" +#include "nsIImportService.h" + +class nsIMsgSend; +class nsIMsgCompFields; +class nsIMsgIdentity; +class nsIMsgSendListener; +class nsIIOService; + +#include "nsIMsgSend.h" +#include "nsNetUtil.h" + +#include "MapiMessage.h" + +#include <list> + +/////////////////////////////////////////////////////////////////////////////////////////////// + +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); + + // Bug 593907 + void HackBody(const wchar_t* orig, size_t origLen, nsString& hack); + void UnhackBody(nsCString& body); + bool GenerateHackSequence(const wchar_t* body, size_t origLen); + // End Bug 593907 + +private: + nsIMsgSendListener * m_pListener; + nsIMsgCompFields * m_pMsgFields; + static nsIMsgIdentity * m_pIdentity; + char* m_optimizationBuffer; + nsCOMPtr<nsIImportService> m_pImportService; + + // Bug 593907 + nsString m_hackedPostfix; + // End Bug 593907 +}; + + +#endif /* nsOutlookCompose_h__ */ diff --git a/mailnews/import/outlook/src/nsOutlookImport.cpp b/mailnews/import/outlook/src/nsOutlookImport.cpp new file mode 100644 index 000000000..eaaf24fc3 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookImport.cpp @@ -0,0 +1,589 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +/* + Outlook Express (Win32) import mail and addressbook interfaces +*/ +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIComponentManager.h" +#include "nsOutlookImport.h" +#include "nsIMemory.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 "nsIOutputStream.h" +#include "nsIAddrDatabase.h" +#include "nsOutlookSettings.h" +#include "nsTextFormatter.h" +#include "nsOutlookStringBundle.h" +#include "nsIStringBundle.h" +#include "OutlookDebugLog.h" +#include "nsUnicharUtils.h" + +#include "nsOutlookMail.h" + +#include "MapiApi.h" + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); +PRLogModuleInfo *OUTLOOKLOGMODULE = nullptr; + +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); + + /* nsIArray FindMailboxes (in nsIFile location); */ + NS_IMETHOD FindMailboxes(nsIFile *location, nsIArray **_retval); + + 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, nsIArray **_retval); + + NS_IMETHOD InitFieldMap(nsIImportFieldMap *fieldMap) + { return NS_ERROR_FAILURE; } + + NS_IMETHOD ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *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() +{ + // Init logging module. + if (!OUTLOOKLOGMODULE) + OUTLOOKLOGMODULE = PR_NewLogModule("IMPORT"); + + 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_PRECONDITION(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_PRECONDITION(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_PRECONDITION(supports != nullptr, "null ptr"); + if (! supports) + return NS_ERROR_NULL_POINTER; + + *supports = strdup(kOutlookSupportsString); + return NS_OK; +} + +NS_IMETHODIMP nsOutlookImport::GetSupportsUpgrade(bool *pUpgrade) +{ + NS_PRECONDITION(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_PRECONDITION(pImportType != nullptr, "null ptr"); + if (! pImportType) + return NS_ERROR_NULL_POINTER; + NS_PRECONDITION(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! + nsIImportMail * pMail = nullptr; + nsIImportGeneric *pGeneric = nullptr; + rv = ImportOutlookMailImpl::Create(&pMail); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericMail(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("mailInterface", pMail); + nsString name; + nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME, name); + nsCOMPtr<nsISupportsString> nameString (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nameString->SetData(name); + pGeneric->SetData("name", nameString); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + } + NS_IF_RELEASE(pMail); + NS_IF_RELEASE(pGeneric); + return rv; + } + + if (!strcmp(pImportType, "addressbook")) { + // create the nsIImportAddressBook interface and return it! + nsIImportAddressBooks * pAddress = nullptr; + nsIImportGeneric * pGeneric = nullptr; + rv = ImportOutlookAddressImpl::Create(&pAddress); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = impSvc->CreateNewGenericAddressBooks(&pGeneric); + if (NS_SUCCEEDED(rv)) { + pGeneric->SetData("addressInterface", pAddress); + rv = pGeneric->QueryInterface(kISupportsIID, (void **)ppInterface); + } + } + } + NS_IF_RELEASE(pAddress); + NS_IF_RELEASE(pGeneric); + return rv; + } + + if (!strcmp(pImportType, "settings")) { + nsIImportSettings *pSettings = nullptr; + rv = nsOutlookSettings::Create(&pSettings); + if (NS_SUCCEEDED(rv)) + pSettings->QueryInterface(kISupportsIID, (void **)ppInterface); + NS_IF_RELEASE(pSettings); + return rv; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +///////////////////////////////////////////////////////////////////////////////// +nsresult ImportOutlookMailImpl::Create(nsIImportMail** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new ImportOutlookMailImpl(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + 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_PRECONDITION(ppLoc != nullptr, "null ptr"); + NS_PRECONDITION(found != nullptr, "null ptr"); + NS_PRECONDITION(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 <nsIFile> resultFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + *found = true; + NS_IF_ADDREF(*ppLoc = resultFile); + *userVerify = false; + + return NS_OK; +} + + +NS_IMETHODIMP ImportOutlookMailImpl::FindMailboxes(nsIFile *pLoc, nsIArray **ppArray) +{ + NS_PRECONDITION(pLoc != nullptr, "null ptr"); + NS_PRECONDITION(ppArray != nullptr, "null ptr"); + if (!pLoc || !ppArray) + return NS_ERROR_NULL_POINTER; + return m_mail.GetMailFolders(ppArray); +} + +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); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get(), count); + pStream->Append(pText); + nsTextFormatter::smprintf_free(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); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(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; + NS_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_PRECONDITION(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(kDestTrashFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("sent items")) + _retval = NS_LITERAL_STRING(kDestSentFolderName); + else if (aFolderName.LowerCaseEqualsLiteral("outbox")) + _retval = NS_LITERAL_STRING(kDestUnsentMessagesFolderName); + else + _retval = aFolderName; + return NS_OK; +} + +nsresult ImportOutlookAddressImpl::Create(nsIImportAddressBooks** aImport) +{ + NS_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new ImportOutlookAddressImpl(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + 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_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_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, nsIArray **_retval) +{ + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (!_retval) + return NS_ERROR_NULL_POINTER; + + return m_address.GetAddressBooks(_retval); +} + +NS_IMETHODIMP ImportOutlookAddressImpl::ImportAddressBook(nsIImportABDescriptor *source, + nsIAddrDatabase *destination, + nsIImportFieldMap *fieldMap, + nsISupports *aSupportService, + char16_t **pErrorLog, + char16_t **pSuccessLog, + bool *fatalError) +{ + m_msgCount = 0; + m_msgTotal = 0; + NS_PRECONDITION(source != nullptr, "null ptr"); + NS_PRECONDITION(destination != nullptr, "null ptr"); + NS_PRECONDITION(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 destination->Commit(nsAddrDBCommitType::kLargeCommit); +} + + +NS_IMETHODIMP ImportOutlookAddressImpl::GetImportProgress(uint32_t *_retval) +{ + NS_PRECONDITION(_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); + char16_t *pText = nsTextFormatter::smprintf(pFmt, name.get()); + pStream->Append(pText); + nsTextFormatter::smprintf_free(pText); + nsOutlookStringBundle::FreeString(pFmt); + ImportOutlookMailImpl::AddLinebreak(pStream); +} diff --git a/mailnews/import/outlook/src/nsOutlookImport.h b/mailnews/import/outlook/src/nsOutlookImport.h new file mode 100644 index 000000000..e017d6efc --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookImport.h @@ -0,0 +1,44 @@ +/* -*- 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 suppport the nsIImportModule interface + //////////////////////////////////////////////////////////////////////////////////////// + + NS_DECL_NSIIMPORTMODULE + +protected: + virtual ~nsOutlookImport(); +}; + + + + +#endif /* nsOutlookImport_h___ */ diff --git a/mailnews/import/outlook/src/nsOutlookMail.cpp b/mailnews/import/outlook/src/nsOutlookMail.cpp new file mode 100644 index 000000000..b9a851ce8 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookMail.cpp @@ -0,0 +1,863 @@ +/* -*- 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/. */ + +/* + Outlook mail import +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIImportFieldMap.h" +#include "nsIImportMailboxDescriptor.h" +#include "nsIImportABDescriptor.h" +#include "nsIMutableArray.h" +#include "nsOutlookStringBundle.h" +#include "nsABBaseCID.h" +#include "nsIAbCard.h" +#include "mdb.h" +#include "OutlookDebugLog.h" +#include "nsOutlookMail.h" +#include "nsUnicharUtils.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIMsgPluggableStore.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsMsgI18N.h" +#include "nsNetUtil.h" + +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + +/* ------------ 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(nsIArray **pArray) +{ + if (!m_haveMapi) { + IMPORT_LOG0("GetMailFolders called before Mapi is initialized\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIMutableArray for the mail folder list\n"); + return rv; + } + + nsCOMPtr<nsIImportService> 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 + nsIImportMailboxDescriptor * pID; + nsISupports * pInterface; + nsString name; + nsString uniName; + + for (i = 0; i < m_folderList.GetSize(); i++) { + pFolder = m_folderList.GetItem(i); + rv = impSvc->CreateNewMailboxDescriptor(&pID); + if (NS_SUCCEEDED(rv)) { + pID->SetDepth(pFolder->GetDepth()); + pID->SetIdentifier(i); + + pFolder->GetDisplayName(name); + pID->SetDisplayName(name.get()); + + pID->SetSize(1000); + rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface); + array->AppendElement(pInterface, false); + pInterface->Release(); + pID->Release(); + } + } + array.forget(pArray); + return NS_OK; +} + +bool nsOutlookMail::IsAddressBookNameUnique(nsString& name, nsString& list) +{ + nsString usedName; + usedName.AppendLiteral("["); + 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.AppendLiteral("["); + list.Append(name); + list.AppendLiteral("],"); +} + +nsresult nsOutlookMail::GetAddressBooks(nsIArray **pArray) +{ + if (!m_haveMapi) { + IMPORT_LOG0("GetAddressBooks called before Mapi is initialized\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("FAILED to allocate the nsIMutableArray for the address book list\n"); + return rv; + } + + nsCOMPtr<nsIImportService> 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 + nsIImportABDescriptor * pID; + nsISupports * pInterface; + nsString name; + nsString list; + + for (i = 0; i < m_addressList.GetSize(); i++) { + pFolder = m_addressList.GetItem(i); + if (!pFolder->IsStore()) { + rv = impSvc->CreateNewABDescriptor(&pID); + if (NS_SUCCEEDED(rv)) { + pID->SetIdentifier(i); + pFolder->GetDisplayName(name); + MakeAddressBookNameUnique(name, list); + pID->SetPreferredName(name); + pID->SetSize(100); + rv = pID->QueryInterface(kISupportsIID, (void **) &pInterface); + array->AppendElement(pInterface, false); + pInterface->Release(); + pID->Release(); + } + } + } + array.forget(pArray); + 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 + +nsresult nsOutlookMail::ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort, + int32_t index, const char16_t *pName, + nsIMsgFolder *dstFolder, + int32_t *pMsgCount) +{ + if ((index < 0) || (index >= m_folderList.GetSize())) { + IMPORT_LOG0("*** Bad mailbox identifier, unable to import\n"); + *pAbort = true; + return NS_ERROR_FAILURE; + } + + int32_t dummyMsgCount = 0; + if (pMsgCount) + *pMsgCount = 0; + else + pMsgCount = &dummyMsgCount; + + CMapiFolder *pFolder = m_folderList.GetItem(index); + OpenMessageStore(pFolder); + if (!m_lpMdb) { + IMPORT_LOG1("*** Unable to obtain mapi message store for mailbox: %S\n", pName); + return NS_ERROR_FAILURE; + } + + if (pFolder->IsStore()) + return NS_OK; + + // now what? + CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(), pFolder->GetEntryID()); + + BOOL done = FALSE; + ULONG cbEid; + LPENTRYID lpEid; + ULONG oType; + LPMESSAGE lpMsg = nullptr; + ULONG totalCount; + double doneCalc; + + nsCOMPtr<nsIOutputStream> outputStream; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = dstFolder->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", pName); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + bool reusable; + + rv = msgStore->GetNewMsgOutputStream(dstFolder, getter_AddRefs(msgHdr), &reusable, + getter_AddRefs(outputStream)); + if (NS_FAILED(rv)) { + IMPORT_LOG1("*** Error getting nsIOutputStream of mailbox: %S\n", pName); + return rv; + } + totalCount = contents.GetCount(); + doneCalc = *pMsgCount; + doneCalc /= totalCount; + doneCalc *= 1000; + if (pDoneSoFar) { + *pDoneSoFar = (uint32_t) doneCalc; + if (*pDoneSoFar > 1000) + *pDoneSoFar = 1000; + } + + if (!done && (oType == MAPI_MESSAGE)) { + if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN *) &lpMsg)) { + IMPORT_LOG1("*** Error opening messages in mailbox: %S\n", pName); + return NS_ERROR_FAILURE; + } + + // 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(pName); + 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 + (*pMsgCount)++; + msgStore->FinishNewMessage(outputStream, msgHdr); + } + else { + IMPORT_LOG1( "*** Error reading message from mailbox: %S\n", pName); + msgStore->DiscardNewMessage(outputStream, msgHdr); + } + if (!reusable) + outputStream->Close(); + } + } + + if (outputStream) + outputStream->Close(); + return NS_OK; +} + +nsresult nsOutlookMail::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 + WriteData(pDest, "\x0D\x0A", 2); + + return rv; +} + +BOOL nsOutlookMail::WriteData(nsIOutputStream *pDest, const char *pData, int32_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, nsIAddrDatabase *pDb, 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", pName); + return NS_ERROR_FAILURE; + } + + if (pFolder->IsStore()) + return NS_OK; + + nsresult rv; + + nsCOMPtr<nsIImportFieldMap> pFieldMap; + + nsCOMPtr<nsIImportService> 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", 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", 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); + + nsIMdbRow* newRow = nullptr; + pDb->GetNewRow(&newRow); + // FIXME: Check with Candice about releasing the newRow if it + // isn't added to the database. Candice's code in nsAddressBook + // never releases it but that doesn't seem right to me! + if (newRow) { + if (BuildCard(subject.get(), pDb, newRow, lpMsg, pFieldMap)) { + pDb->AddCardRowToDB(newRow); + } + } + } + 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.get(), pDb, lpMsg, pFieldMap); + } + } + + lpMsg->Release(); + } + } + + rv = pDb->Commit(nsAddrDBCommitType::kLargeCommit); + return rv; +} +nsresult nsOutlookMail::CreateList(const char16_t * pName, + nsIAddrDatabase *pDb, + LPMAPIPROP pUserList, + nsIImportFieldMap *pFieldMap) +{ + // If no name provided then we're done. + if (!pName || !(*pName)) + return NS_OK; + + nsresult rv = NS_ERROR_FAILURE; + // Make sure we have db to work with. + if (!pDb) + return rv; + + nsCOMPtr <nsIMdbRow> newListRow; + rv = pDb->GetNewListRow(getter_AddRefs(newListRow)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString column; + LossyCopyUTF16toASCII(nsDependentString(pName), column); + rv = pDb->AddListName(newListRow, column.get()); + 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", pName); + 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 <nsIMdbRow> newRow; + nsCOMPtr <nsIMdbRow> oldRow; + pDb->GetNewRow(getter_AddRefs(newRow)); + if (newRow) { + if (BuildCard(subject.get(), pDb, newRow, lpMsg, pFieldMap)) + { + nsCOMPtr <nsIAbCard> userCard; + nsCOMPtr <nsIAbCard> newCard; + userCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + pDb->InitCardFromRow(userCard,newRow); + + //add card to db + pDb->FindRowByCard(userCard,getter_AddRefs(oldRow)); + if (oldRow) + newRow = oldRow; + else + pDb->AddCardRowToDB(newRow); + + //add card list + pDb->AddListCardColumnsToRow(userCard, + newListRow,idx+1, getter_AddRefs(newCard), + true, nullptr, nullptr); + } + } + } + m_mapi.MAPIFreeBuffer(value); + + rv = pDb->AddCardRowToDB(newListRow); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pDb->SetListAddressTotal(newListRow, (uint32_t)total); + rv = pDb->AddListDirNode(newListRow); + return rv; +} + +void nsOutlookMail::SanitizeValue(nsString& val) +{ + MsgReplaceSubstring(val, NS_LITERAL_STRING("\r\n"), NS_LITERAL_STRING(", ")); + MsgReplaceChar(val, "\r\n", ','); +} + +void nsOutlookMail::SplitString(nsString& val1, nsString& val2) +{ + // Find the last line if there is more than one! + int32_t idx = val1.RFind("\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, nsIAddrDatabase *pDb, nsIMdbRow *newRow, 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()) { + pDb->AddDisplayName(newRow, NS_ConvertUTF16toUTF8(displayName).get()); + } + if (!firstName.IsEmpty()) { + pDb->AddFirstName(newRow, NS_ConvertUTF16toUTF8(firstName).get()); + } + if (!lastName.IsEmpty()) { + pDb->AddLastName(newRow, NS_ConvertUTF16toUTF8(lastName).get()); + } + if (!nickName.IsEmpty()) { + pDb->AddNickName(newRow, NS_ConvertUTF16toUTF8(nickName).get()); + } + if (!eMail.IsEmpty()) { + pDb->AddPrimaryEmail(newRow, NS_ConvertUTF16toUTF8(eMail).get()); + } + if (!secondEMail.IsEmpty()) { + pDb->Add2ndEmail(newRow, NS_ConvertUTF16toUTF8(secondEMail).get()); + } + + // 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(pDb, newRow, gMapiFields[i].mozField, value.get()); + } + else if (gMapiFields[i].multiLine == kIsMultiLine) { + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get()); + } + else { + line2.Truncate(); + SplitString(value, line2); + if (!value.IsEmpty()) + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].mozField, value.get()); + if (!line2.IsEmpty()) + pFieldMap->SetFieldValue(pDb, newRow, gMapiFields[i].multiLine, line2.get()); + } + } + } + } + } + + return true; +} diff --git a/mailnews/import/outlook/src/nsOutlookMail.h b/mailnews/import/outlook/src/nsOutlookMail.h new file mode 100644 index 000000000..3aa317ece --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookMail.h @@ -0,0 +1,54 @@ +/* -*- 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 "nsStringGlue.h" +#include "nsOutlookCompose.h" +#include "nsIFile.h" +#include "MapiApi.h" +#include "MapiMessage.h" +#include "nsIAddrDatabase.h" + +class nsIAddrDatabase; +class nsIImportFieldMap; + +class nsOutlookMail { +public: + nsOutlookMail(); + ~nsOutlookMail(); + + nsresult GetMailFolders(nsIArray **pArray); + nsresult GetAddressBooks(nsIArray **pArray); + nsresult ImportMailbox(uint32_t *pDoneSoFar, bool *pAbort, int32_t index, + const char16_t *pName, nsIMsgFolder *pDest, + int32_t *pMsgCount); + static nsresult ImportMessage(LPMESSAGE lpMsg, nsIOutputStream *destOutputStream, nsMsgDeliverMode mode); + nsresult ImportAddresses(uint32_t *pCount, uint32_t *pTotal, const char16_t *pName, uint32_t id, nsIAddrDatabase *pDb, nsString& errors); +private: + void OpenMessageStore(CMapiFolder *pNextFolder); + static BOOL WriteData(nsIOutputStream *pDest, const char *pData, int32_t len); + + 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, nsIAddrDatabase *pDb, nsIMdbRow *newRow, LPMAPIPROP pUser, nsIImportFieldMap *pFieldMap); + nsresult CreateList(const char16_t * pName, nsIAddrDatabase *pDb, LPMAPIPROP pUserList, nsIImportFieldMap *pFieldMap); + +private: + bool m_gotFolders; + bool m_gotAddresses; + bool m_haveMapi; + CMapiApi m_mapi; + CMapiFolderList m_folderList; + CMapiFolderList m_addressList; + CMapiFolderList m_storeList; + LPMDB m_lpMdb; +}; + +#endif /* nsOutlookMail_h___ */ diff --git a/mailnews/import/outlook/src/nsOutlookSettings.cpp b/mailnews/import/outlook/src/nsOutlookSettings.cpp new file mode 100644 index 000000000..ec03c8ee2 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookSettings.cpp @@ -0,0 +1,567 @@ +/* -*- 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/. */ + +/* + + Outlook (Win32) settings + +*/ + +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsMsgUtils.h" +#include "nsOutlookImport.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIImportService.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAccount.h" +#include "nsIImportSettings.h" +#include "nsOutlookSettings.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsOutlookStringBundle.h" +#include "OutlookDebugLog.h" +#include "nsIPop3IncomingServer.h" +#include "nsMsgI18N.h" +#include <windows.h> +#include "nsIWindowsRegKey.h" +#include "nsComponentManagerUtils.h" +#ifdef MOZILLA_INTERNAL_API +#include "nsNativeCharsetUtils.h" +#else +#include "nsMsgI18N.h" +#define NS_CopyNativeToUnicode(source, dest) \ + nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#define NS_CopyUnicodeToNative(source, dest) \ + nsMsgI18NConvertFromUnicode(nsMsgI18NFileSystemCharset(), source, dest) +#endif + +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_PRECONDITION(aImport != nullptr, "null ptr"); + if (! aImport) + return NS_ERROR_NULL_POINTER; + + *aImport = new nsOutlookSettings(); + if (! *aImport) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aImport); + return NS_OK; +} + +nsOutlookSettings::nsOutlookSettings() +{ +} + +nsOutlookSettings::~nsOutlookSettings() +{ +} + +NS_IMPL_ISUPPORTS(nsOutlookSettings, nsIImportSettings) + +NS_IMETHODIMP nsOutlookSettings::AutoLocate(char16_t **description, nsIFile **location, bool *_retval) +{ + NS_PRECONDITION(description != nullptr, "null ptr"); + NS_PRECONDITION(_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<nsIWindowsRegKey> 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_PRECONDITION(_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<nsIWindowsRegKey> 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(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(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<nsIWindowsRegKey> 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(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(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<nsIWindowsRegKey> key; + nsresult rv = QueryAccountSubKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) + return rv; + + return key->ReadStringValue(NS_LITERAL_STRING("Default Mail Account"), aName); +} + +bool OutlookSettings::DoImport(nsIMsgAccount **aAccount) +{ + nsCOMPtr<nsIWindowsRegKey> key; + nsresult rv = OutlookSettings::FindAccountsKey(getter_AddRefs(key)); + if (NS_FAILED(rv)) { + IMPORT_LOG0("*** Error finding Outlook registry account keys\n"); + return false; + } + + nsCOMPtr<nsIMsgAccountManager> accMgr = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &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<nsIWindowsRegKey> 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<nsIMsgAccount> account; + nsAutoString value; + rv = subKey->ReadStringValue(NS_LITERAL_STRING("IMAP Server"), value); + if (NS_SUCCEEDED(rv) && + DoIMAPServer(accMgr, subKey, value, getter_AddRefs(account))) + accounts++; + + rv = subKey->ReadStringValue(NS_LITERAL_STRING("POP3 Server"), 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(NS_LITERAL_STRING("Account Name"), 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(NS_LITERAL_STRING("IMAP User Name"), 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<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_FAILED(rv) || (in == nullptr)) { + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("imap"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv) && in) { + rv = in->SetType(NS_LITERAL_CSTRING("imap")); + // 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<nsIMsgAccount> 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(NS_LITERAL_STRING("POP3 User Name"), 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<nsIMsgIncomingServer> in; + rv = aMgr->FindServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + if (NS_SUCCEEDED(rv)) + return true; + + // Create the incoming server and an account for it? + rv = aMgr->CreateIncomingServer(nativeUserName, + nativeServerName, + NS_LITERAL_CSTRING("pop3"), + getter_AddRefs(in)); + rv = in->SetType(NS_LITERAL_CSTRING("pop3")); + + // TODO SSL, auth method + + nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in); + NS_ENSURE_SUCCESS(rv, false); + + // set local folders as the Inbox to use for this POP3 server + nsCOMPtr<nsIMsgIncomingServer> 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<nsIMsgAccount> 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<nsIMsgAccount> 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(NS_LITERAL_STRING("Leave Mail On Server"), &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(NS_LITERAL_STRING("SMTP Display Name"), name); + + nsAutoString server; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Server"), server); + + nsAutoString email; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Email Address"), email); + + nsAutoString reply; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Reply To Email Address"), reply); + + nsAutoString userName; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP User Name"), userName); + + nsAutoString orgName; + aKey->ReadStringValue(NS_LITERAL_STRING("SMTP Organization Name"), orgName); + + nsresult rv; + nsCOMPtr<nsIMsgIdentity> 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<nsIMsgIncomingServer> 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<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString nativeUserName; + NS_CopyUnicodeToNative(aUser, nativeUserName); + nsAutoCString nativeServerName; + NS_CopyUnicodeToNative(aServer, nativeServerName); + nsCOMPtr<nsISmtpServer> 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<nsISmtpServer> 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("Ceated new SMTP server: %s\n", + nativeServerName.get()); + return NS_OK; +} + diff --git a/mailnews/import/outlook/src/nsOutlookSettings.h b/mailnews/import/outlook/src/nsOutlookSettings.h new file mode 100644 index 000000000..ea074e5b4 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookSettings.h @@ -0,0 +1,29 @@ +/* -*- 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/mailnews/import/outlook/src/nsOutlookStringBundle.cpp b/mailnews/import/outlook/src/nsOutlookStringBundle.cpp new file mode 100644 index 000000000..826e30e40 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookStringBundle.cpp @@ -0,0 +1,71 @@ +/* -*- 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 "nsIServiceManager.h" +#include "nsIURI.h" +#include "mozilla/Services.h" + +#define OUTLOOK_MSGS_URL "chrome://messenger/locale/outlookImportMsgs.properties" + +nsIStringBundle * nsOutlookStringBundle::m_pBundle = nullptr; + +nsIStringBundle *nsOutlookStringBundle::GetStringBundle(void) +{ + if (m_pBundle) + return m_pBundle; + + char* propertyURL = OUTLOOK_MSGS_URL; + nsIStringBundle* sBundle = nullptr; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) { + sBundleService->CreateBundle(propertyURL, &sBundle); + } + + m_pBundle = sBundle; + + return sBundle; +} + +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) + m_pBundle = GetStringBundle(); + + if (m_pBundle) { + char16_t *ptrv = nullptr; + nsresult rv = m_pBundle->GetStringFromID(stringID, &ptrv); + + if (NS_SUCCEEDED(rv) && ptrv) + return ptrv; + } + + nsString resultString; + resultString.AppendLiteral("[StringID "); + resultString.AppendInt(stringID); + resultString.AppendLiteral("?]"); + + return ToNewUnicode(resultString); +} + +void nsOutlookStringBundle::Cleanup(void) +{ + if (m_pBundle) + m_pBundle->Release(); + m_pBundle = nullptr; +} diff --git a/mailnews/import/outlook/src/nsOutlookStringBundle.h b/mailnews/import/outlook/src/nsOutlookStringBundle.h new file mode 100644 index 000000000..1351088d4 --- /dev/null +++ b/mailnews/import/outlook/src/nsOutlookStringBundle.h @@ -0,0 +1,38 @@ +/* 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 "nsStringGlue.h" + +class nsIStringBundle; + +class nsOutlookStringBundle { +public: + static char16_t * GetStringByID(int32_t stringID); + static void GetStringByID(int32_t stringID, nsString& result); + static nsIStringBundle * GetStringBundle(void); // don't release + static void FreeString(char16_t *pStr) { NS_Free(pStr);} + static void Cleanup(void); +private: + static nsIStringBundle * 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/mailnews/import/outlook/src/rtfDecoder.cpp b/mailnews/import/outlook/src/rtfDecoder.cpp new file mode 100644 index 000000000..837beec0b --- /dev/null +++ b/mailnews/import/outlook/src/rtfDecoder.cpp @@ -0,0 +1,520 @@ +/* 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 <stack> +#include <map> +#include <sstream> +#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<int, FontInfo> 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<LocalState> 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; + + 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}; + Lexem(Type t=ltError) : m_type(t) {} + Lexem(Lexem& from) // Move pointers when copying + { + 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; + from.m_type = ltError; + 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; + from.m_type = ltError; + 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; +} + +Lexem 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; + } + } + return result; +} + +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, ch1; + 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<unsigned int>(*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); + if (lexem.type() != Lexem::ltGroupBegin) + return; + decoder.BeginGroup(); + lexem = GetLexem(rtf); + 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 + lexem = GetLexem(state.stream); + 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/mailnews/import/outlook/src/rtfDecoder.h b/mailnews/import/outlook/src/rtfDecoder.h new file mode 100644 index 000000000..85a17721d --- /dev/null +++ b/mailnews/import/outlook/src/rtfDecoder.h @@ -0,0 +1,22 @@ +/* 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 <istream> + +template <size_t len> +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/mailnews/import/outlook/src/rtfMailDecoder.cpp b/mailnews/import/outlook/src/rtfMailDecoder.cpp new file mode 100644 index 000000000..9a6b34725 --- /dev/null +++ b/mailnews/import/outlook/src/rtfMailDecoder.cpp @@ -0,0 +1,79 @@ +/* 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<size_t>(-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/mailnews/import/outlook/src/rtfMailDecoder.h b/mailnews/import/outlook/src/rtfMailDecoder.h new file mode 100644 index 000000000..2b621577f --- /dev/null +++ b/mailnews/import/outlook/src/rtfMailDecoder.h @@ -0,0 +1,41 @@ +/* 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 <string> +#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<size_t>(-1)); +}; |