diff options
Diffstat (limited to 'mailnews/mapi/mapihook')
22 files changed, 3099 insertions, 0 deletions
diff --git a/mailnews/mapi/mapihook/build/Makefile.in b/mailnews/mapi/mapihook/build/Makefile.in new file mode 100644 index 000000000..0614220e9 --- /dev/null +++ b/mailnews/mapi/mapihook/build/Makefile.in @@ -0,0 +1,36 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MIDL_GENERATED_FILES = msgMapi.h msgMapi_p.c msgMapi_i.c dlldata.c + +INSTALL_TARGETS += msgmapi +msgmapi_FILES := msgMapi.h +msgmapi_DEST = $(DIST)/include +msgmapi_TARGET := export + +SRCDIR_CSRCS = $(addprefix $(srcdir)/,$(CSRCS)) + +GARBAGE += $(MIDL_GENERATED_FILES) done_gen $(CSRCS) $(SRCDIR_CSRCS) + +EMBED_MANIFEST_AT = 2 + +CSRCS += \ + dlldata.c \ + msgMapi_i.c \ + msgMapi_p.c \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + +$(MIDL_GENERATED_FILES): done_gen + +done_gen: msgMapi.idl + $(RM) $(SRCDIR_CSRCS) + $(MIDL) $(MIDL_FLAGS) $(UNICODE_FLAGS) $(srcdir)/msgMapi.idl + touch $@ + +export:: done_gen + + diff --git a/mailnews/mapi/mapihook/build/MapiProxy.def b/mailnews/mapi/mapihook/build/MapiProxy.def new file mode 100644 index 000000000..4da08eb7d --- /dev/null +++ b/mailnews/mapi/mapihook/build/MapiProxy.def @@ -0,0 +1,13 @@ +; 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/. + +LIBRARY MapiProxy.dll + +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE + GetProxyDllInfo PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + diff --git a/mailnews/mapi/mapihook/build/module.ver b/mailnews/mapi/mapihook/build/module.ver new file mode 100644 index 000000000..7691020c2 --- /dev/null +++ b/mailnews/mapi/mapihook/build/module.ver @@ -0,0 +1,6 @@ +WIN32_MODULE_FILEVERSION=0,8,0,0 +WIN32_MODULE_FILEVERSION_STRING=0.8 +WIN32_MODULE_COPYRIGHT=ŠThunderbird and Mozilla Developers, according to the MPL 1.1/GPL 2.0/LGPL 2.1 licenses, as applicable. +WIN32_MODULE_COMPANYNAME=Mozilla.org +WIN32_MODULE_TRADEMARKS=Mozilla +WIN32_MODULE_COMMENT=Mozilla Thunderbird Thunderbird MAPI Proxy Dll diff --git a/mailnews/mapi/mapihook/build/moz.build b/mailnews/mapi/mapihook/build/moz.build new file mode 100644 index 000000000..5c42875cc --- /dev/null +++ b/mailnews/mapi/mapihook/build/moz.build @@ -0,0 +1,22 @@ +# 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/. + +SharedLibrary('MapiProxy') + +OS_LIBS += [ + 'rpcrt4', +] + +for var in ('REGISTER_PROXY_DLL', 'UNICODE', '_UNICODE'): + DEFINES[var] = True + +# This produces a compile warning mozilla-config.h(145): warning C4005: '_WIN32_WINNT': macro redefinition +#DEFINES['_WIN32_WINNT'] = '0x400' + +if not CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']: + DEFINES['MOZILLA_INTERNAL_API'] = True + +DEFFILE = SRCDIR + '/MapiProxy.def' + diff --git a/mailnews/mapi/mapihook/build/msgMapi.idl b/mailnews/mapi/mapihook/build/msgMapi.idl new file mode 100644 index 000000000..3ca3fd493 --- /dev/null +++ b/mailnews/mapi/mapihook/build/msgMapi.idl @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This idl will be compiled by MIDL. MS-COM is used +// as bridge between MAPI clients and the Mozilla. + +import "unknwn.idl"; + +typedef wchar_t LOGIN_PW_TYPE[256]; + +typedef struct +{ + unsigned long ulReserved; + unsigned long flFlags; /* Flags */ + unsigned long nPosition_NotUsed; /* character in text to be replaced by attachment */ + LPTSTR lpszPathName; /* Full path name including file name */ + LPTSTR lpszFileName; /* Real (original) file name */ + unsigned char * lpFileType_NotUsed ; +} nsMapiFileDesc, * lpnsMapiFileDesc; + + +typedef struct +{ + unsigned long ulReserved; + unsigned long ulRecipClass; /* MAPI_TO, MAPI_CC, MAPI_BCC, MAPI_ORIG */ + LPSTR lpszName; /* Recipient name to display */ + LPSTR lpszAddress; /* Recipient email address */ + unsigned long ulEIDSize_NotUsed; + unsigned char * lpEntryID_NotUsed ; +} nsMapiRecipDesc, * lpnsMapiRecipDesc; + +typedef struct +{ + unsigned long ulReserved; + LPSTR lpszSubject; /* Message Subject */ + LPSTR lpszNoteText; /* Message Text */ + LPSTR lpszMessageType; + LPSTR lpszDateReceived; /* in YYYY/MM/DD HH:MM format */ + LPSTR lpszConversationID_NotUsed; /* conversation thread ID */ + unsigned long flFlags; /* unread,return receipt */ + lpnsMapiRecipDesc lpOriginator; /* Originator descriptor */ + unsigned long nRecipCount; /* Number of recipients */ + [size_is (nRecipCount)] lpnsMapiRecipDesc lpRecips;/* Recipient descriptors */ + unsigned long nFileCount; /* # of file attachments */ + [size_is (nFileCount)] lpnsMapiFileDesc lpFiles; /* Attachment descriptors */ +} nsMapiMessage, * lpnsMapiMessage; + +[ + object, + uuid(6EDCD38E-8861-11d5-A3DD-00B0D0F3BAA7), + helpstring("nsIMapi Inteface"), + pointer_default(unique) +] + +interface nsIMapi : IUnknown +{ + HRESULT Login([in] unsigned long aUIArg, [in, unique] LOGIN_PW_TYPE aLogin, + [in, unique] LOGIN_PW_TYPE aPassWord, [in] unsigned long aFlags, + [out] unsigned long *aSessionId); + + HRESULT Initialize(); + HRESULT IsValid(); + HRESULT IsValidSession([in] unsigned long aSession); + + HRESULT SendMail([in] unsigned long aSession, [in, unique] lpnsMapiMessage aMessage, + [in] short aRecipCount, [in, size_is(aRecipCount)] lpnsMapiRecipDesc aRecips, + [in] short aFileCount, [in, size_is(aFileCount)] lpnsMapiFileDesc aFiles, + [in] unsigned long aFlags, [in] unsigned long aReserved) ; + + HRESULT SendDocuments( [in] unsigned long aSession, + [in, unique] LPTSTR aDelimChar, [in, unique] LPTSTR aFilePaths, + [in, unique] LPTSTR aFileNames, [in] ULONG aFlags ) ; + + HRESULT FindNext( [in] unsigned long aSession, [in] ULONG ulUIParam, [in, unique] LPTSTR lpszMessageType, + [in, unique] LPTSTR lpszSeedMessageID, [in] ULONG flFlags, [in] ULONG ulReserved, + [in] [out] char lpszMessageID[64] ) ; + + HRESULT ReadMail( [in] unsigned long lhSession, [in] ULONG ulUIParam, [in, unique] LPTSTR lpszMessageID, + [in] ULONG flFlags, [in] ULONG ulReserved, [out] lpnsMapiMessage *lppMessage); + + HRESULT DeleteMail( [in] unsigned long lhSession, [in] ULONG ulUIParam, [in, unique] LPTSTR lpszMessageID, + [in] ULONG flFlags, [in] ULONG ulReserved); + + HRESULT SaveMail( [in] unsigned long lhSession, [in] ULONG ulUIParam, [in, unique] lpnsMapiMessage lppMessage, + [in] ULONG flFlags, [in] ULONG ulReserved, [in, unique] LPTSTR lpszMessageID); + + HRESULT Logoff (unsigned long aSession); + HRESULT CleanUp(); +}; + + + diff --git a/mailnews/mapi/mapihook/moz.build b/mailnews/mapi/mapihook/moz.build new file mode 100644 index 000000000..65ca38934 --- /dev/null +++ b/mailnews/mapi/mapihook/moz.build @@ -0,0 +1,11 @@ +# 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/. + +DIRS += [ + 'public', + 'build', + 'src', +] + diff --git a/mailnews/mapi/mapihook/public/moz.build b/mailnews/mapi/mapihook/public/moz.build new file mode 100644 index 000000000..a2d2b2ef9 --- /dev/null +++ b/mailnews/mapi/mapihook/public/moz.build @@ -0,0 +1,11 @@ +# 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/. + +XPIDL_SOURCES += [ + 'nsIMapiSupport.idl', +] + +XPIDL_MODULE = 'mapihook' + diff --git a/mailnews/mapi/mapihook/public/nsIMapiSupport.idl b/mailnews/mapi/mapihook/public/nsIMapiSupport.idl new file mode 100644 index 000000000..e160566a4 --- /dev/null +++ b/mailnews/mapi/mapihook/public/nsIMapiSupport.idl @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface provides support for registering Mozilla as a COM component + * for extending the use of Mail/News through Simple MAPI. + * + */ + +[noscript, uuid(2907B676-C4BD-49af-880A-E27A0616291E)] +interface nsIMapiSupport : nsISupports { + + /** Initiates MAPI support + */ + + void initializeMAPISupport(); + + /** Shuts down the MAPI support + */ + + void shutdownMAPISupport(); + + /** registerServer - register the mapi DLL with the desktop + * Typically called by the window shell service when we are + * made the default mail app + */ + void registerServer(); + + /** unRegisterServer - unregister the mapi DLL with the desktop + * Typically called by the window shell service when we are + * removed as the default mail app. + */ + void unRegisterServer(); +}; + +%{C++ +#define NS_IMAPISUPPORT_CONTRACTID "@mozilla.org/mapisupport;1" +#define NS_IMAPISUPPORT_CLASSNAME "Mozilla MAPI Support" +%} + + diff --git a/mailnews/mapi/mapihook/src/Makefile.in b/mailnews/mapi/mapihook/src/Makefile.in new file mode 100644 index 000000000..10e6172bf --- /dev/null +++ b/mailnews/mapi/mapihook/src/Makefile.in @@ -0,0 +1,6 @@ +# +# 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/. + +CSRCS += ../build/msgMapi_i.c diff --git a/mailnews/mapi/mapihook/src/Registry.cpp b/mailnews/mapi/mapihook/src/Registry.cpp new file mode 100644 index 000000000..31db7520b --- /dev/null +++ b/mailnews/mapi/mapihook/src/Registry.cpp @@ -0,0 +1,291 @@ +/* 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/. */ + +#undef _UNICODE +#undef UNICODE + +#include <objbase.h> +#include "nsStringGlue.h" +#include "Registry.h" + +#define MAPI_PROXY_DLL_NAME "MapiProxy.dll" +#define MAPI_STARTUP_ARG " /MAPIStartUp" +#define MAX_SIZE 2048 + +// Size of a CLSID as a string +const int CLSID_STRING_SIZE = 39; + +// Proxy/Stub Dll Routines + +typedef HRESULT (__stdcall ProxyServer)(); + + +// Convert a CLSID to a char string. + +BOOL CLSIDtochar(const CLSID& clsid, char* szCLSID, + int length) +{ + LPOLESTR wszCLSID = NULL; + + // Get CLSID + HRESULT hr = StringFromCLSID(clsid, &wszCLSID); + if (FAILED(hr)) + return FALSE; + + // Covert from wide characters to non-wide. + wcstombs(szCLSID, wszCLSID, length); + + // Free memory. + CoTaskMemFree(wszCLSID); + + return TRUE; +} + +// Create a key and set its value. + +BOOL setKeyAndValue(nsAutoCString keyName, const char* subKey, + const char* theValue) +{ + HKEY hKey; + BOOL retValue = TRUE; + + nsAutoCString theKey(keyName); + if (subKey != NULL) + { + theKey += "\\"; + theKey += subKey; + } + + // Create and open key and subkey. + long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT, theKey.get(), + 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, NULL, &hKey, NULL); + if (lResult != ERROR_SUCCESS) + return FALSE ; + + // Set the Value. + if (theValue != NULL) + { + lResult = RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)theValue, + strlen(theValue)+1); + if (lResult != ERROR_SUCCESS) + retValue = FALSE; + } + + RegCloseKey(hKey); + return TRUE; +} + +// Delete a key and all of its descendents. + +LONG recursiveDeleteKey(HKEY hKeyParent, // Parent of key to delete + const char* lpszKeyChild) // Key to delete +{ + // Open the child. + HKEY hKeyChild ; + LONG lRes = RegOpenKeyEx(hKeyParent, lpszKeyChild, 0, + KEY_ALL_ACCESS, &hKeyChild) ; + if (lRes != ERROR_SUCCESS) + { + return lRes ; + } + + // Enumerate all of the decendents of this child. + FILETIME time ; + char szBuffer[MAX_SIZE] ; + DWORD dwSize = MAX_SIZE ; + while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL, + NULL, NULL, &time) == S_OK) + { + // Delete the decendents of this child. + lRes = recursiveDeleteKey(hKeyChild, szBuffer) ; + if (lRes != ERROR_SUCCESS) + { + // Cleanup before exiting. + RegCloseKey(hKeyChild) ; + return lRes; + } + dwSize = MAX_SIZE; + } + + // Close the child. + RegCloseKey(hKeyChild) ; + + // Delete this child. + return RegDeleteKey(hKeyParent, lpszKeyChild) ; +} + +void RegisterProxy() +{ + HINSTANCE h = NULL; + ProxyServer *RegisterFunc = NULL; + + char szModule[MAX_SIZE]; + char *pTemp = NULL; + + HMODULE hModule = GetModuleHandle(NULL); + DWORD dwResult = ::GetModuleFileName(hModule, szModule, + sizeof(szModule)/sizeof(char)); + if (dwResult == 0) + return; + + pTemp = strrchr(szModule, '\\'); + if (pTemp == NULL) + return; + + *pTemp = '\0'; + nsAutoCString proxyPath(szModule); + + proxyPath += "\\"; + proxyPath += MAPI_PROXY_DLL_NAME; + + h = LoadLibrary(proxyPath.get()); + if (h == NULL) + return; + + RegisterFunc = (ProxyServer *) GetProcAddress(h, "DllRegisterServer"); + if (RegisterFunc) + RegisterFunc(); + + FreeLibrary(h); +} + +void UnRegisterProxy() +{ + HINSTANCE h = NULL; + ProxyServer *UnRegisterFunc = NULL; + + char szModule[MAX_SIZE]; + char *pTemp = NULL; + + HMODULE hModule = GetModuleHandle(NULL); + DWORD dwResult = ::GetModuleFileName(hModule, szModule, + sizeof(szModule)/sizeof(char)); + if (dwResult == 0) + return; + + pTemp = strrchr(szModule, '\\'); + if (pTemp == NULL) + return; + + *pTemp = '\0'; + nsAutoCString proxyPath(szModule); + + proxyPath += "\\"; + proxyPath += MAPI_PROXY_DLL_NAME; + + h = LoadLibrary(proxyPath.get()); + if (h == NULL) + return; + + UnRegisterFunc = (ProxyServer *) GetProcAddress(h, "DllUnregisterServer"); + if (UnRegisterFunc) + UnRegisterFunc(); + + FreeLibrary(h); +} + +// Register the component in the registry. + +HRESULT RegisterServer(const CLSID& clsid, // Class ID + const char* szFriendlyName, // Friendly Name + const char* szVerIndProgID, // Programmatic + const char* szProgID) // IDs +{ + HMODULE hModule = GetModuleHandle(NULL); + char szModuleName[MAX_SIZE]; + char szCLSID[CLSID_STRING_SIZE]; + + nsAutoCString independentProgId(szVerIndProgID); + nsAutoCString progId(szProgID); + + DWORD dwResult = ::GetModuleFileName(hModule, szModuleName, + sizeof(szModuleName)/sizeof(char)); + + if (dwResult == 0) + return S_FALSE; + + nsAutoCString moduleName(szModuleName); + nsAutoCString registryKey("CLSID\\"); + + moduleName += MAPI_STARTUP_ARG; + + // Convert the CLSID into a char. + + if (!CLSIDtochar(clsid, szCLSID, sizeof(szCLSID))) + return S_FALSE; + registryKey += szCLSID; + + // Add the CLSID to the registry. + if (!setKeyAndValue(registryKey, NULL, szFriendlyName)) + return S_FALSE; + + if (!setKeyAndValue(registryKey, "LocalServer32", moduleName.get())) + return S_FALSE; + + // Add the ProgID subkey under the CLSID key. + if (!setKeyAndValue(registryKey, "ProgID", szProgID)) + return S_FALSE; + + // Add the version-independent ProgID subkey under CLSID key. + if (!setKeyAndValue(registryKey, "VersionIndependentProgID", szVerIndProgID)) + return S_FALSE; + + // Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT. + if (!setKeyAndValue(independentProgId, NULL, szFriendlyName)) + return S_FALSE; + if (!setKeyAndValue(independentProgId, "CLSID", szCLSID)) + return S_FALSE; + if (!setKeyAndValue(independentProgId, "CurVer", szProgID)) + return S_FALSE; + + // Add the versioned ProgID subkey under HKEY_CLASSES_ROOT. + if (!setKeyAndValue(progId, NULL, szFriendlyName)) + return S_FALSE; + if (!setKeyAndValue(progId, "CLSID", szCLSID)) + return S_FALSE; + + RegisterProxy(); + + return S_OK; +} + +LONG UnregisterServer(const CLSID& clsid, // Class ID + const char* szVerIndProgID, // Programmatic + const char* szProgID) // IDs +{ + LONG lResult = S_OK; + + // Convert the CLSID into a char. + + char szCLSID[CLSID_STRING_SIZE]; + if (!CLSIDtochar(clsid, szCLSID, sizeof(szCLSID))) + return S_FALSE; + + UnRegisterProxy(); + + nsAutoCString registryKey("CLSID\\"); + registryKey += szCLSID; + + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, registryKey.get()); + if (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND) + return lResult; + + registryKey += "\\LocalServer32"; + + // Delete only the path for this server. + + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, registryKey.get()); + if (lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND) + return lResult; + + // Delete the version-independent ProgID Key. + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID); + if (lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND) + return lResult; + + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID); + + return lResult; +} diff --git a/mailnews/mapi/mapihook/src/Registry.h b/mailnews/mapi/mapihook/src/Registry.h new file mode 100644 index 000000000..34a4efca7 --- /dev/null +++ b/mailnews/mapi/mapihook/src/Registry.h @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _REGISTRY_H_ +#define _REGISTRY_H_ + +#include <objbase.h> + +// This function will register a component in the Registry. + +HRESULT RegisterServer(const CLSID& clsid, + const char* szFriendlyName, + const char* szVerIndProgID, + const char* szProgID) ; + +// This function will unregister a component. + +HRESULT UnregisterServer(const CLSID& clsid, + const char* szVerIndProgID, + const char* szProgID) ; + +#endif diff --git a/mailnews/mapi/mapihook/src/moz.build b/mailnews/mapi/mapihook/src/moz.build new file mode 100644 index 000000000..3e8cd3450 --- /dev/null +++ b/mailnews/mapi/mapihook/src/moz.build @@ -0,0 +1,25 @@ +# 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 += [ + 'msgMapiFactory.cpp', + 'msgMapiHook.cpp', + 'msgMapiImp.cpp', + 'msgMapiMain.cpp', + 'msgMapiSupport.cpp', + 'Registry.cpp', +] + +if CONFIG['MOZ_INCOMPLETE_EXTERNAL_LINKAGE']: + XPCOMBinaryComponent('msgMapi') +else: + FINAL_LIBRARY = 'xul' + +OS_LIBS += [ + 'ole32', +] + +DEFINES['UNICODE'] = True +DEFINES['_UNICODE'] = True diff --git a/mailnews/mapi/mapihook/src/msgMapiFactory.cpp b/mailnews/mapi/mapihook/src/msgMapiFactory.cpp new file mode 100644 index 000000000..829279f62 --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiFactory.cpp @@ -0,0 +1,85 @@ +/* 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/. */ + +#undef UNICODE +#undef _UNICODE + +#include "msgMapiFactory.h" +#include "msgMapiImp.h" +#include "msgMapi.h" + +CMapiFactory ::CMapiFactory() +: m_cRef(1) +{ +} + +CMapiFactory::~CMapiFactory() +{ +} + +STDMETHODIMP CMapiFactory::QueryInterface(const IID& aIid, void** aPpv) +{ + if ((aIid == IID_IUnknown) || (aIid == IID_IClassFactory)) + { + *aPpv = static_cast<IClassFactory*>(this); + } + else + { + *aPpv = nullptr; + return E_NOINTERFACE; + } + reinterpret_cast<IUnknown*>(*aPpv)->AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) CMapiFactory::AddRef() +{ + return ++m_cRef; +} + +STDMETHODIMP_(ULONG) CMapiFactory::Release() +{ + int32_t temp = --m_cRef; + if (m_cRef == 0) + { + delete this; + return 0; + } + + return temp; +} + +STDMETHODIMP CMapiFactory::CreateInstance(IUnknown* aUnknownOuter, + const IID& aIid, + void** aPpv) +{ + // Cannot aggregate. + + if (aUnknownOuter != nullptr) + { + return CLASS_E_NOAGGREGATION ; + } + + // Create component. + + CMapiImp* pImp = new CMapiImp(); + if (pImp == nullptr) + { + return E_OUTOFMEMORY ; + } + + // Get the requested interface. + HRESULT hr = pImp->QueryInterface(aIid, aPpv); + + // Release the IUnknown pointer. + // (If QueryInterface failed, component will delete itself.) + + pImp->Release(); + return hr; +} + +STDMETHODIMP CMapiFactory::LockServer(BOOL aLock) +{ + return S_OK ; +} diff --git a/mailnews/mapi/mapihook/src/msgMapiFactory.h b/mailnews/mapi/mapihook/src/msgMapiFactory.h new file mode 100644 index 000000000..7ae2f2ed3 --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiFactory.h @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MSG_MAPI_FACTORY_H +#define MSG_MAPI_FACTORY_H + +#include <windows.h> +#include <objbase.h> +#include "nspr.h" +#include "nsISupportsImpl.h" // ThreadSafeAutoRefCnt +#include <stdint.h> + + +class CMapiFactory : public IClassFactory +{ +public : + + // IUnknown + + STDMETHODIMP QueryInterface (REFIID aIid, void** aPpv); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // IClassFactory + + STDMETHODIMP CreateInstance (LPUNKNOWN aUnkOuter, REFIID aIid, void **aPpv); + STDMETHODIMP LockServer (BOOL aLock); + + CMapiFactory (); + +private: + mozilla::ThreadSafeAutoRefCnt m_cRef; + + ~CMapiFactory (); +}; + +#endif // MSG_MAPI_FACTORY_H + diff --git a/mailnews/mapi/mapihook/src/msgMapiHook.cpp b/mailnews/mapi/mapihook/src/msgMapiHook.cpp new file mode 100644 index 000000000..02696fe94 --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiHook.cpp @@ -0,0 +1,829 @@ +/* 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/. */ + +#define MAPI_STARTUP_ARG "/MAPIStartUp" + +#include <mapidefs.h> +#include <mapi.h> +#include <tchar.h> +#include <direct.h> +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsISupports.h" +#include "nsIPromptService.h" +#include "nsIAppStartup.h" +#include "nsIAppShellService.h" +#include "mozIDOMWindow.h" +#include "nsINativeAppSupport.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsIStringBundle.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgCompFields.h" +#include "nsIMsgComposeParams.h" +#include "nsIMsgCompose.h" +#include "nsMsgCompCID.h" +#include "nsIMsgSend.h" +#include "nsIMsgComposeService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIDirectoryService.h" +#include "nsMsgI18N.h" +#include "msgMapi.h" +#include "msgMapiHook.h" +#include "msgMapiSupport.h" +#include "msgMapiMain.h" +#include "nsThreadUtils.h" +#include "nsMsgUtils.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" +#include "nsEmbedCID.h" +#include "mozilla/Logging.h" + +extern PRLogModuleInfo *MAPI; + +class nsMAPISendListener : public nsIMsgSendListener +{ +public: + + // 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) { + PR_CEnterMonitor(this); + PR_CNotifyAll(this); + m_done = true; + PR_CExitMonitor(this); + return NS_OK ; + } + + /* void OnSendNotPerformed */ + NS_IMETHOD OnSendNotPerformed(const char *aMsgID, nsresult aStatus) + { + return OnStopSending(aMsgID, aStatus, nullptr, nullptr) ; + } + + /* void OnGetDraftFolderURI (); */ + NS_IMETHOD OnGetDraftFolderURI(const char *aFolderURI) {return NS_OK;} + + static nsresult CreateMAPISendListener( nsIMsgSendListener **ppListener); + + bool IsDone() { return m_done ; } + +protected : + nsMAPISendListener() { + m_done = false; + } + + bool m_done; +private: + virtual ~nsMAPISendListener() { } + +}; + + +NS_IMPL_ISUPPORTS(nsMAPISendListener, nsIMsgSendListener) + +nsresult nsMAPISendListener::CreateMAPISendListener( nsIMsgSendListener **ppListener) +{ + NS_ENSURE_ARG_POINTER(ppListener) ; + + *ppListener = new nsMAPISendListener(); + if (! *ppListener) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*ppListener); + return NS_OK; +} + +bool nsMapiHook::isMapiService = false; + +void nsMapiHook::CleanUp() +{ + // This routine will be fully implemented in future + // to cleanup mapi related stuff inside mozilla code. +} + +bool nsMapiHook::DisplayLoginDialog(bool aLogin, char16_t **aUsername, + char16_t **aPassword) +{ + nsresult rv; + bool btnResult = false; + + nsCOMPtr<nsIPromptService> dlgService(do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && dlgService) + { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) return false; + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle(MAPI_PROPERTIES_CHROME, getter_AddRefs(bundle)); + if (NS_FAILED(rv) || !bundle) return false; + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = bundleService->CreateBundle( + "chrome://branding/locale/brand.properties", + getter_AddRefs(brandBundle)); + if (NS_FAILED(rv)) return false; + + nsString brandName; + rv = brandBundle->GetStringFromName( + u"brandFullName", + getter_Copies(brandName)); + if (NS_FAILED(rv)) return false; + + nsString loginTitle; + const char16_t *brandStrings[] = { brandName.get() }; + NS_NAMED_LITERAL_STRING(loginTitlePropertyTag, "loginTitle"); + const char16_t *dTitlePropertyTag = loginTitlePropertyTag.get(); + rv = bundle->FormatStringFromName(dTitlePropertyTag, brandStrings, 1, + getter_Copies(loginTitle)); + if (NS_FAILED(rv)) return false; + + if (aLogin) + { + nsString loginText; + rv = bundle->GetStringFromName(u"loginTextwithName", + getter_Copies(loginText)); + if (NS_FAILED(rv) || loginText.IsEmpty()) return false; + + bool dummyValue = false; + rv = dlgService->PromptUsernameAndPassword(nullptr, loginTitle.get(), + loginText.get(), aUsername, aPassword, + nullptr, &dummyValue, &btnResult); + } + else + { + //nsString loginString; + nsString loginText; + const char16_t *userNameStrings[] = { *aUsername }; + + NS_NAMED_LITERAL_STRING(loginTextPropertyTag, "loginText"); + const char16_t *dpropertyTag = loginTextPropertyTag.get(); + rv = bundle->FormatStringFromName(dpropertyTag, userNameStrings, 1, + getter_Copies(loginText)); + if (NS_FAILED(rv)) return false; + + bool dummyValue = false; + rv = dlgService->PromptPassword(nullptr, loginTitle.get(), loginText.get(), + aPassword, nullptr, &dummyValue, &btnResult); + } + } + + return btnResult; +} + +bool nsMapiHook::VerifyUserName(const nsString& aUsername, nsCString& aIdKey) +{ + nsresult rv; + + if (aUsername.IsEmpty()) + return false; + + nsCOMPtr<nsIMsgAccountManager> accountManager(do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return false; + nsCOMPtr<nsIArray> identities; + rv = accountManager->GetAllIdentities(getter_AddRefs(identities)); + if (NS_FAILED(rv)) return false; + + uint32_t numIndentities = 0; + identities->GetLength(&numIndentities); + + for (uint32_t i = 0; i < numIndentities; i++) + { + nsCOMPtr<nsIMsgIdentity> thisIdentity(do_QueryElementAt(identities, i, &rv)); + if (NS_SUCCEEDED(rv) && thisIdentity) + { + nsCString email; + rv = thisIdentity->GetEmail(email); + if (NS_FAILED(rv)) continue; + + // get the username from the email and compare with the username + int32_t index = email.FindChar('@'); + if (index != -1) + email.SetLength(index); + + if (aUsername.Equals(NS_ConvertASCIItoUTF16(email))) + return NS_SUCCEEDED(thisIdentity->GetKey(aIdKey)); + } + } + + return false; +} + +bool +nsMapiHook::IsBlindSendAllowed() +{ + bool enabled = false; + bool warn = true; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefBranch) { + prefBranch->GetBoolPref(PREF_MAPI_WARN_PRIOR_TO_BLIND_SEND, &warn); + prefBranch->GetBoolPref(PREF_MAPI_BLIND_SEND_ENABLED, &enabled); + } + if (!enabled) + return false; + + if (!warn) + return true; // Everything is okay. + + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) return false; + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle(MAPI_PROPERTIES_CHROME, getter_AddRefs(bundle)); + if (NS_FAILED(rv) || !bundle) return false; + + nsString warningMsg; + rv = bundle->GetStringFromName(u"mapiBlindSendWarning", + getter_Copies(warningMsg)); + if (NS_FAILED(rv)) return false; + + nsString dontShowAgainMessage; + rv = bundle->GetStringFromName(u"mapiBlindSendDontShowAgain", + getter_Copies(dontShowAgainMessage)); + if (NS_FAILED(rv)) return false; + + nsCOMPtr<nsIPromptService> dlgService(do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv) || !dlgService) return false; + + bool continueToWarn = true; + bool okayToContinue = false; + dlgService->ConfirmCheck(nullptr, nullptr, warningMsg.get(), dontShowAgainMessage.get(), &continueToWarn, &okayToContinue); + + if (!continueToWarn && okayToContinue && prefBranch) + prefBranch->SetBoolPref(PREF_MAPI_WARN_PRIOR_TO_BLIND_SEND, false); + + return okayToContinue; +} + +// this is used for Send without UI +nsresult nsMapiHook::BlindSendMail (unsigned long aSession, nsIMsgCompFields * aCompFields) +{ + nsresult rv = NS_OK ; + + if (!IsBlindSendAllowed()) + return NS_ERROR_FAILURE; + + /** create nsIMsgComposeParams obj and other fields to populate it **/ + + nsCOMPtr<mozIDOMWindowProxy> hiddenWindow; + // get parent window + nsCOMPtr<nsIAppShellService> appService = do_GetService( "@mozilla.org/appshell/appShellService;1", &rv); + if (NS_FAILED(rv)|| (!appService) ) return rv ; + + rv = appService->GetHiddenDOMWindow(getter_AddRefs(hiddenWindow)); + if ( NS_FAILED(rv) ) return rv ; + // smtp password and Logged in used IdKey from MapiConfig (session obj) + nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ; + if (!pMapiConfig) return NS_ERROR_FAILURE ; // get the singelton obj + char16_t * password = pMapiConfig->GetPassword(aSession) ; + // password + nsAutoCString smtpPassword; + LossyCopyUTF16toASCII(password, smtpPassword); + + // Id key + nsCString MsgIdKey; + pMapiConfig->GetIdKey(aSession, MsgIdKey); + + // get the MsgIdentity for the above key using AccountManager + nsCOMPtr <nsIMsgAccountManager> accountManager = do_GetService (NS_MSGACCOUNTMANAGER_CONTRACTID) ; + if (NS_FAILED(rv) || (!accountManager) ) return rv ; + + nsCOMPtr <nsIMsgIdentity> pMsgId ; + rv = accountManager->GetIdentity (MsgIdKey, getter_AddRefs(pMsgId)) ; + if (NS_FAILED(rv) ) return rv ; + + // create a send listener to get back the send status + nsCOMPtr <nsIMsgSendListener> sendListener ; + rv = nsMAPISendListener::CreateMAPISendListener(getter_AddRefs(sendListener)) ; + if (NS_FAILED(rv) || (!sendListener) ) return rv; + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv)); + if (NS_FAILED(rv) || (!pMsgComposeParams) ) return rv ; + + // populate the compose params + bool forcePlainText; + aCompFields->GetForcePlainText(&forcePlainText); + pMsgComposeParams->SetType(nsIMsgCompType::New); + pMsgComposeParams->SetFormat(forcePlainText ? nsIMsgCompFormat::PlainText : nsIMsgCompFormat::HTML); + pMsgComposeParams->SetIdentity(pMsgId); + pMsgComposeParams->SetComposeFields(aCompFields); + pMsgComposeParams->SetSendListener(sendListener) ; + pMsgComposeParams->SetSmtpPassword(smtpPassword.get()); + + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv)); + if (NS_FAILED(rv) || (!pMsgCompose) ) return rv ; + + /** initialize nsIMsgCompose, Send the message, wait for send completion response **/ + + rv = pMsgCompose->Initialize(pMsgComposeParams, hiddenWindow, nullptr); + if (NS_FAILED(rv)) return rv ; + + // If we're in offline mode, we'll need to queue it for later. No point in trying to send it. + return pMsgCompose->SendMsg(WeAreOffline() ? nsIMsgSend::nsMsgQueueForLater : nsIMsgSend::nsMsgDeliverNow, + pMsgId, nullptr, nullptr, nullptr); + if (NS_FAILED(rv)) return rv ; + + // assign to interface pointer from nsCOMPtr to facilitate typecast below + nsIMsgSendListener * pSendListener = sendListener ; + + // we need to wait here to make sure that we return only after send is completed + // so we will have a event loop here which will process the events till the Send IsDone. + nsCOMPtr<nsIThread> thread(do_GetCurrentThread()); + while ( !((nsMAPISendListener *) pSendListener)->IsDone() ) + { + PR_CEnterMonitor(pSendListener); + PR_CWait(pSendListener, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(pSendListener); + NS_ProcessPendingEvents(thread); + } + + return rv ; +} + +// this is used to populate comp fields with Unicode data +nsresult nsMapiHook::PopulateCompFields(lpnsMapiMessage aMessage, + nsIMsgCompFields * aCompFields) +{ + nsresult rv = NS_OK ; + + if (aMessage->lpOriginator) + aCompFields->SetFrom (NS_ConvertASCIItoUTF16((char *) aMessage->lpOriginator->lpszAddress)); + + nsAutoString To ; + nsAutoString Cc ; + nsAutoString Bcc ; + + NS_NAMED_LITERAL_STRING(Comma, ","); + + if (aMessage->lpRecips) + { + for (int i=0 ; i < (int) aMessage->nRecipCount ; i++) + { + if (aMessage->lpRecips[i].lpszAddress || aMessage->lpRecips[i].lpszName) + { + const char *addressWithoutType = (aMessage->lpRecips[i].lpszAddress) + ? aMessage->lpRecips[i].lpszAddress : aMessage->lpRecips[i].lpszName; + if (!PL_strncasecmp(addressWithoutType, "SMTP:", 5)) + addressWithoutType += 5; + switch (aMessage->lpRecips[i].ulRecipClass) + { + case MAPI_TO : + if (!To.IsEmpty()) + To += Comma; + To.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + break; + + case MAPI_CC : + if (!Cc.IsEmpty()) + Cc += Comma; + Cc.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + break; + + case MAPI_BCC : + if (!Bcc.IsEmpty()) + Bcc += Comma; + Bcc.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + break; + } + } + } + } + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("to: %s cc: %s bcc: %s \n", NS_ConvertUTF16toUTF8(To).get(), NS_ConvertUTF16toUTF8(Cc).get(), NS_ConvertUTF16toUTF8(Bcc).get())); + // set To, Cc, Bcc + aCompFields->SetTo (To) ; + aCompFields->SetCc (Cc) ; + aCompFields->SetBcc (Bcc) ; + + // set subject + if (aMessage->lpszSubject) + aCompFields->SetSubject(NS_ConvertASCIItoUTF16(aMessage->lpszSubject)); + + // handle attachments as File URL + rv = HandleAttachments (aCompFields, aMessage->nFileCount, aMessage->lpFiles, true) ; + if (NS_FAILED(rv)) return rv ; + + // set body + if (aMessage->lpszNoteText) + { + nsString Body; + CopyASCIItoUTF16(aMessage->lpszNoteText, Body); + if (Body.IsEmpty() || Body.Last() != '\n') + Body.AppendLiteral(CRLF); + + // This is needed when Simple MAPI is used without a compose window. + // See bug 1366196. + if (Body.Find("<html>") == kNotFound) + aCompFields->SetForcePlainText(true); + + rv = aCompFields->SetBody(Body) ; + } + return rv ; +} + +nsresult nsMapiHook::HandleAttachments (nsIMsgCompFields * aCompFields, int32_t aFileCount, + lpnsMapiFileDesc aFiles, BOOL aIsUnicode) +{ + nsresult rv = NS_OK ; + + nsAutoCString Attachments ; + nsAutoCString TempFiles ; + + nsCOMPtr <nsIFile> pFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID, &rv) ; + if (NS_FAILED(rv) || (!pFile) ) return rv ; + nsCOMPtr <nsIFile> pTempDir = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID, &rv) ; + if (NS_FAILED(rv) || (!pTempDir) ) return rv ; + + for (int i=0 ; i < aFileCount ; i++) + { + if (aFiles[i].lpszPathName) + { + // check if attachment exists + if (aIsUnicode) + pFile->InitWithPath (nsDependentString(aFiles[i].lpszPathName)); + else + pFile->InitWithNativePath (nsDependentCString((const char*)aFiles[i].lpszPathName)); + + bool bExist ; + rv = pFile->Exists(&bExist) ; + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("nsMapiHook::HandleAttachments: filename: %s path: %s exists = %s \n", (const char*)aFiles[i].lpszFileName, (const char*)aFiles[i].lpszPathName, bExist ? "true" : "false")); + if (NS_FAILED(rv) || (!bExist) ) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ; + + //Temp Directory + nsCOMPtr <nsIFile> pTempDir; + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pTempDir)); + + // create a new sub directory called moz_mapi underneath the temp directory + pTempDir->AppendRelativePath(NS_LITERAL_STRING("moz_mapi")); + pTempDir->Exists (&bExist) ; + if (!bExist) + { + rv = pTempDir->Create(nsIFile::DIRECTORY_TYPE, 777) ; + if (NS_FAILED(rv)) return rv ; + } + + // rename or copy the existing temp file with the real file name + + nsAutoString leafName ; + // convert to Unicode using Platform charset + // leafName already contains a unicode leafName from lpszPathName. If we were given + // a value for lpszFileName, use it. Otherwise stick with leafName + if (aFiles[i].lpszFileName) + { + nsAutoString wholeFileName; + if (aIsUnicode) + wholeFileName.Assign(aFiles[i].lpszFileName); + else + ConvertToUnicode(nsMsgI18NFileSystemCharset(), (char *) aFiles[i].lpszFileName, wholeFileName); + // need to find the last '\' and find the leafname from that. + int32_t lastSlash = wholeFileName.RFindChar(char16_t('\\')); + if (lastSlash != kNotFound) + leafName.Assign(Substring(wholeFileName, lastSlash + 1)); + else + leafName.Assign(wholeFileName); + } + else + pFile->GetLeafName (leafName); + + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + attachment->SetName(leafName); + + nsCOMPtr<nsIFile> pTempFile; + rv = pTempDir->Clone(getter_AddRefs(pTempFile)); + if (NS_FAILED(rv) || !pTempFile) + return rv; + + pTempFile->Append(leafName); + pTempFile->Exists(&bExist); + if (bExist) + { + rv = pTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0777); + NS_ENSURE_SUCCESS(rv, rv); + pTempFile->Remove(false); // remove so we can copy over it. + pTempFile->GetLeafName(leafName); + } + // copy the file to its new location and file name + pFile->CopyTo(pTempDir, leafName); + // point pFile to the new location of the attachment + pFile->InitWithFile(pTempDir); + pFile->Append(leafName); + + // create MsgCompose attachment object + attachment->SetTemporary(true); // this one is a temp file so set the flag for MsgCompose + + // now set the attachment object + nsAutoCString pURL ; + NS_GetURLSpecFromFile(pFile, pURL); + attachment->SetUrl(pURL); + + // set the file size + int64_t fileSize; + pFile->GetFileSize(&fileSize); + attachment->SetSize(fileSize); + + // add the attachment + rv = aCompFields->AddAttachment (attachment); + if (NS_FAILED(rv)) + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("nsMapiHook::HandleAttachments: AddAttachment rv = %lx\n", rv)); + } + } + return rv ; +} + + +// this is used to convert non Unicode data and then populate comp fields +nsresult nsMapiHook::PopulateCompFieldsWithConversion(lpnsMapiMessage aMessage, + nsIMsgCompFields * aCompFields) +{ + nsresult rv = NS_OK; + + if (aMessage->lpOriginator) + { + nsAutoString From; + From.Append(NS_ConvertASCIItoUTF16((char *) aMessage->lpOriginator->lpszAddress)); + aCompFields->SetFrom (From); + } + + nsAutoString To; + nsAutoString Cc; + nsAutoString Bcc; + NS_NAMED_LITERAL_STRING(Comma, ","); + if (aMessage->lpRecips) + { + for (int i=0 ; i < (int) aMessage->nRecipCount ; i++) + { + if (aMessage->lpRecips[i].lpszAddress || aMessage->lpRecips[i].lpszName) + { + const char *addressWithoutType = (aMessage->lpRecips[i].lpszAddress) + ? aMessage->lpRecips[i].lpszAddress : aMessage->lpRecips[i].lpszName; + if (!PL_strncasecmp(addressWithoutType, "SMTP:", 5)) + addressWithoutType += 5; + + switch (aMessage->lpRecips[i].ulRecipClass) + { + case MAPI_TO : + if (!To.IsEmpty()) + To += Comma ; + To.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + break ; + + case MAPI_CC : + if (!Cc.IsEmpty()) + Cc += Comma ; + Cc.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + break ; + + case MAPI_BCC : + if (!Bcc.IsEmpty()) + Bcc += Comma ; + Bcc.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + break ; + } + } + } + } + + // set To, Cc, Bcc + aCompFields->SetTo (To) ; + aCompFields->SetCc (Cc) ; + aCompFields->SetBcc (Bcc) ; + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("to: %s cc: %s bcc: %s \n", NS_ConvertUTF16toUTF8(To).get(), NS_ConvertUTF16toUTF8(Cc).get(), NS_ConvertUTF16toUTF8(Bcc).get())); + + nsAutoCString platformCharSet; + // set subject + if (aMessage->lpszSubject) + { + nsAutoString Subject ; + if (platformCharSet.IsEmpty()) + platformCharSet.Assign(nsMsgI18NFileSystemCharset()); + rv = ConvertToUnicode(platformCharSet.get(), (char *) aMessage->lpszSubject, Subject); + if (NS_FAILED(rv)) return rv; + aCompFields->SetSubject(Subject); + } + + // handle attachments as File URL + rv = HandleAttachments (aCompFields, aMessage->nFileCount, aMessage->lpFiles, false) ; + if (NS_FAILED(rv)) return rv ; + + // set body + if (aMessage->lpszNoteText) + { + nsAutoString Body ; + if (platformCharSet.IsEmpty()) + platformCharSet.Assign(nsMsgI18NFileSystemCharset()); + rv = ConvertToUnicode(platformCharSet.get(), (char *) aMessage->lpszNoteText, Body); + if (NS_FAILED(rv)) return rv ; + if (Body.IsEmpty() || Body.Last() != '\n') + Body.AppendLiteral(CRLF); + + // This is needed when Simple MAPI is used without a compose window. + // See bug 1366196. + if (Body.Find("<html>") == kNotFound) + aCompFields->SetForcePlainText(true); + + rv = aCompFields->SetBody(Body) ; + } + +#ifdef RAJIV_DEBUG + // testing what all was set in CompFields + printf ("To : %S \n", To.get()) ; + printf ("CC : %S \n", Cc.get() ) ; + printf ("BCC : %S \n", Bcc.get() ) ; +#endif + + return rv ; +} + +// this is used to populate the docs as attachments in the Comp fields for Send Documents +nsresult nsMapiHook::PopulateCompFieldsForSendDocs(nsIMsgCompFields * aCompFields, ULONG aFlags, + LPTSTR aDelimChar, LPTSTR aFilePaths) +{ + nsAutoString strDelimChars ; + nsString strFilePaths; + nsresult rv = NS_OK ; + bool bExist ; + + if (aDelimChar) + strDelimChars.Assign(aDelimChar); + if (aFilePaths) + strFilePaths.Assign(aFilePaths); + + // check for comma in filename + if (strDelimChars.FindChar(',') == kNotFound) // if comma is not in the delimiter specified by user + { + if (strFilePaths.FindChar(',') != kNotFound) // if comma found in filenames return error + return NS_ERROR_FILE_INVALID_PATH; + } + + nsCString Attachments ; + + // only 1 file is to be sent, no delim specified + if (strDelimChars.IsEmpty()) + strDelimChars.AssignLiteral(";"); + + int32_t offset = 0 ; + int32_t FilePathsLen = strFilePaths.Length() ; + if (FilePathsLen) + { + nsAutoString Subject ; + + // multiple files to be sent, delim specified + nsCOMPtr <nsIFile> pFile = do_CreateInstance (NS_LOCAL_FILE_CONTRACTID, &rv) ; + if (NS_FAILED(rv) || (!pFile) ) return rv ; + + char16_t * newFilePaths = (char16_t *) strFilePaths.get() ; + while (offset != kNotFound) + { + //Temp Directory + nsCOMPtr <nsIFile> pTempDir; + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pTempDir)); + + // if not already existing, create another temp dir for mapi within Win temp dir + // this is windows only so we can do "\\" + pTempDir->AppendRelativePath (NS_LITERAL_STRING("moz_mapi")); + pTempDir->Exists(&bExist) ; + if (!bExist) + { + rv = pTempDir->Create(nsIFile::DIRECTORY_TYPE, 777) ; + if (NS_FAILED(rv)) return rv ; + } + + nsString RemainingPaths ; + RemainingPaths.Assign(newFilePaths) ; + offset = RemainingPaths.Find (strDelimChars) ; + if (offset != kNotFound) + { + RemainingPaths.SetLength (offset) ; + if ((offset + (int32_t)strDelimChars.Length()) < FilePathsLen) + newFilePaths += offset + strDelimChars.Length() ; + else + offset = kNotFound; + FilePathsLen -= offset + strDelimChars.Length(); + } + + if (RemainingPaths[1] != ':' && RemainingPaths[1] != '\\') + { + char cwd[MAX_PATH]; + if (_getdcwd(_getdrive(), cwd, MAX_PATH)) + { + nsAutoString cwdStr; + CopyASCIItoUTF16(cwd, cwdStr); + cwdStr.Append('\\'); + RemainingPaths.Insert(cwdStr, 0); + } + } + + pFile->InitWithPath (RemainingPaths) ; + + rv = pFile->Exists(&bExist) ; + if (NS_FAILED(rv) || (!bExist) ) return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST ; + + // filename of the file attachment + nsAutoString leafName ; + pFile->GetLeafName (leafName) ; + if(NS_FAILED(rv) || leafName.IsEmpty()) return rv ; + + if (!Subject.IsEmpty()) + Subject.AppendLiteral(", "); + Subject += leafName; + + // create MsgCompose attachment object + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsDependentString fileNameNative(leafName.get()); + rv = pFile->CopyTo(pTempDir, fileNameNative); + if (NS_FAILED(rv)) return rv; + + // now turn pTempDir into a full file path to the temp file + pTempDir->Append(fileNameNative); + + // this one is a temp file so set the flag for MsgCompose + attachment->SetTemporary(true); + + // now set the attachment object + nsAutoCString pURL; + NS_GetURLSpecFromFile(pTempDir, pURL); + attachment->SetUrl(pURL); + + // set the file size + int64_t fileSize; + pFile->GetFileSize(&fileSize); + attachment->SetSize(fileSize); + + // add the attachment + rv = aCompFields->AddAttachment (attachment); + if (NS_FAILED(rv)) return rv; + } + + rv = aCompFields->SetBody(Subject) ; + } + + return rv ; +} + +// this used for Send with UI +nsresult nsMapiHook::ShowComposerWindow (unsigned long aSession, nsIMsgCompFields * aCompFields) +{ + nsresult rv = NS_OK ; + + // create a send listener to get back the send status + nsCOMPtr <nsIMsgSendListener> sendListener ; + rv = nsMAPISendListener::CreateMAPISendListener(getter_AddRefs(sendListener)) ; + if (NS_FAILED(rv) || (!sendListener) ) return rv ; + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv)); + if (NS_FAILED(rv) || (!pMsgComposeParams) ) return rv ; + + // If we found HTML, compose in HTML. + bool forcePlainText; + aCompFields->GetForcePlainText(&forcePlainText); + pMsgComposeParams->SetFormat(forcePlainText ? nsIMsgCompFormat::Default : nsIMsgCompFormat::HTML); + + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::New); + + // Never force to plain text, the default format will take care of that. + // Undo the forcing that happened in PopulateCompFields/PopulateCompFieldsWithConversion. + // See bug 1095629 and bug 1366196. + aCompFields->SetForcePlainText(false); + pMsgComposeParams->SetComposeFields(aCompFields); + pMsgComposeParams->SetSendListener(sendListener); + + /** get the nsIMsgComposeService object to open the compose window **/ + nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ; + if (NS_FAILED(rv)|| (!compService) ) return rv ; + + rv = compService->OpenComposeWindowWithParams(nullptr, pMsgComposeParams) ; + if (NS_FAILED(rv)) return rv ; + + return rv ; +} diff --git a/mailnews/mapi/mapihook/src/msgMapiHook.h b/mailnews/mapi/mapihook/src/msgMapiHook.h new file mode 100644 index 000000000..8860d5227 --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiHook.h @@ -0,0 +1,33 @@ +/* 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 MSG_MAPI_HOOK_H_ +#define MSG_MAPI_HOOK_H_ + +#include "prtypes.h" + +class nsMapiHook +{ + public : + + static bool DisplayLoginDialog(bool aLogin, char16_t **aUsername, + char16_t **aPassword); + static bool VerifyUserName(const nsString& aUsername, nsCString& aIdKey); + + static bool IsBlindSendAllowed () ; + static nsresult BlindSendMail (unsigned long aSession, nsIMsgCompFields * aCompFields) ; + static nsresult ShowComposerWindow (unsigned long aSession, nsIMsgCompFields * aCompFields) ; + static nsresult PopulateCompFields(lpnsMapiMessage aMessage, nsIMsgCompFields * aCompFields) ; + static nsresult PopulateCompFieldsWithConversion(lpnsMapiMessage aMessage, + nsIMsgCompFields * aCompFields) ; + static nsresult PopulateCompFieldsForSendDocs(nsIMsgCompFields * aCompFields, + ULONG aFlags, LPTSTR aDelimChar, LPTSTR aFilePaths) ; + static nsresult HandleAttachments (nsIMsgCompFields * aCompFields, int32_t aFileCount, + lpnsMapiFileDesc aFiles, BOOL aIsUnicode) ; + static void CleanUp(); + + static bool isMapiService; +}; + +#endif // MSG_MAPI_HOOK_H_ diff --git a/mailnews/mapi/mapihook/src/msgMapiImp.cpp b/mailnews/mapi/mapihook/src/msgMapiImp.cpp new file mode 100644 index 000000000..9139d68c6 --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiImp.cpp @@ -0,0 +1,878 @@ +/* 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 <mapidefs.h> +#include <mapi.h> +#include "msgMapi.h" +#include "msgMapiImp.h" +#include "msgMapiFactory.h" +#include "msgMapiMain.h" + +#include "nsIMsgCompFields.h" +#include "msgMapiHook.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsMsgCompCID.h" +#include "nsIMsgDatabase.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgHdr.h" +#include "MailNewsTypes.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgFolder.h" +#include "nsIMsgImapMailFolder.h" +#include <time.h> +#include "nsIInputStream.h" +#include "nsILineInputStream.h" +#include "nsISeekableStream.h" +#include "nsIFile.h" +#include "nsIFileStreams.h" +#include "nsNetCID.h" +#include "nsMsgMessageFlags.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Logging.h" + +using namespace mozilla::mailnews; + +PRLogModuleInfo *MAPI; + +CMapiImp::CMapiImp() +: m_cRef(1) +{ + m_Lock = PR_NewLock(); + if (!MAPI) + MAPI = PR_NewLogModule("MAPI"); +} + +CMapiImp::~CMapiImp() +{ + if (m_Lock) + PR_DestroyLock(m_Lock); +} + +STDMETHODIMP CMapiImp::QueryInterface(const IID& aIid, void** aPpv) +{ + if (aIid == IID_IUnknown) + { + *aPpv = static_cast<nsIMapi*>(this); + } + else if (aIid == IID_nsIMapi) + { + *aPpv = static_cast<nsIMapi*>(this); + } + else + { + *aPpv = nullptr; + return E_NOINTERFACE; + } + + reinterpret_cast<IUnknown*>(*aPpv)->AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) CMapiImp::AddRef() +{ + return ++m_cRef; +} + +STDMETHODIMP_(ULONG) CMapiImp::Release() +{ + int32_t temp = --m_cRef; + if (m_cRef == 0) + { + delete this; + return 0; + } + + return temp; +} + +STDMETHODIMP CMapiImp::IsValid() +{ + return S_OK; +} + +STDMETHODIMP CMapiImp::IsValidSession(unsigned long aSession) +{ + nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration(); + if (pConfig && pConfig->IsSessionValid(aSession)) + return S_OK; + + return E_FAIL; +} + +STDMETHODIMP CMapiImp::Initialize() +{ + HRESULT hr = E_FAIL; + + if (!m_Lock) + return E_FAIL; + + PR_Lock(m_Lock); + + // Initialize MAPI Configuration + + nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration(); + if (pConfig != nullptr) + hr = S_OK; + + PR_Unlock(m_Lock); + + return hr; +} + +STDMETHODIMP CMapiImp::Login(unsigned long aUIArg, LOGIN_PW_TYPE aLogin, LOGIN_PW_TYPE aPassWord, + unsigned long aFlags, unsigned long *aSessionId) +{ + HRESULT hr = E_FAIL; + bool bNewSession = false; + nsCString id_key; + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login using flags %d\n", aFlags)); + if (aFlags & MAPI_NEW_SESSION) + bNewSession = true; + + // Check For Profile Name + if (aLogin != nullptr && aLogin[0] != '\0') + { + if (!nsMapiHook::VerifyUserName(nsString(aLogin), id_key)) + { + *aSessionId = MAPI_E_LOGIN_FAILURE; + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login failed for username %s\n", aLogin)); + NS_ASSERTION(false, "failed verifying user name"); + return hr; + } + } + else + { + // get default account + nsresult rv; + nsCOMPtr <nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE); + nsCOMPtr <nsIMsgAccount> account; + nsCOMPtr <nsIMsgIdentity> identity; + rv = accountManager->GetDefaultAccount(getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE); + rv = account->GetDefaultIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv,MAPI_E_LOGIN_FAILURE); + if (!identity) + return MAPI_E_LOGIN_FAILURE; + identity->GetKey(id_key); + } + + // finally register(create) the session. + uint32_t nSession_Id; + int16_t nResult = 0; + + nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration(); + if (pConfig != nullptr) + nResult = pConfig->RegisterSession(aUIArg, wwc(aLogin), wwc(aPassWord), + (aFlags & MAPI_FORCE_DOWNLOAD), bNewSession, + &nSession_Id, id_key.get()); + switch (nResult) + { + case -1 : + { + *aSessionId = MAPI_E_TOO_MANY_SESSIONS; + return hr; + } + case 0 : + { + *aSessionId = MAPI_E_INSUFFICIENT_MEMORY; + return hr; + } + default : + { + *aSessionId = nSession_Id; + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::Login succeeded\n")); + break; + } + } + + return S_OK; +} + +STDMETHODIMP CMapiImp::SendMail( unsigned long aSession, lpnsMapiMessage aMessage, + short aRecipCount, lpnsMapiRecipDesc aRecips , short aFileCount, lpnsMapiFileDesc aFiles , + unsigned long aFlags, unsigned long aReserved) +{ + nsresult rv = NS_OK ; + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendMail using flags %d\n", aFlags)); + // Assign the pointers in the aMessage struct to the array of Recips and Files + // received here from MS COM. These are used in BlindSendMail and ShowCompWin fns + aMessage->lpRecips = aRecips ; + aMessage->lpFiles = aFiles ; + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendMail flags=%x subject: %s sender: %s\n", + aFlags, (char *) aMessage->lpszSubject, (aMessage->lpOriginator) ? aMessage->lpOriginator->lpszAddress : "")); + + /** create nsIMsgCompFields obj and populate it **/ + nsCOMPtr<nsIMsgCompFields> pCompFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ; + if (NS_FAILED(rv) || (!pCompFields) ) return MAPI_E_INSUFFICIENT_MEMORY ; + + if (aFlags & MAPI_UNICODE) + rv = nsMapiHook::PopulateCompFields(aMessage, pCompFields) ; + else + rv = nsMapiHook::PopulateCompFieldsWithConversion(aMessage, pCompFields) ; + + if (NS_SUCCEEDED (rv)) + { + // see flag to see if UI needs to be brought up + if (!(aFlags & MAPI_DIALOG)) + { + rv = nsMapiHook::BlindSendMail(aSession, pCompFields); + } + else + { + rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields); + } + } + + return nsMAPIConfiguration::GetMAPIErrorFromNSError (rv) ; +} + + +STDMETHODIMP CMapiImp::SendDocuments( unsigned long aSession, LPTSTR aDelimChar, + LPTSTR aFilePaths, LPTSTR aFileNames, ULONG aFlags) +{ + nsresult rv = NS_OK ; + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendDocument using flags %d\n", aFlags)); + /** create nsIMsgCompFields obj and populate it **/ + nsCOMPtr<nsIMsgCompFields> pCompFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ; + if (NS_FAILED(rv) || (!pCompFields) ) return MAPI_E_INSUFFICIENT_MEMORY ; + + if (aFilePaths) + { + rv = nsMapiHook::PopulateCompFieldsForSendDocs(pCompFields, aFlags, aDelimChar, aFilePaths) ; + } + + if (NS_SUCCEEDED (rv)) + rv = nsMapiHook::ShowComposerWindow(aSession, pCompFields); + else + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::SendDocument error rv = %lx, paths = %s names = %s\n", rv, aFilePaths, aFileNames)); + + return nsMAPIConfiguration::GetMAPIErrorFromNSError (rv) ; +} + +nsresult CMapiImp::GetDefaultInbox(nsIMsgFolder **inboxFolder) +{ + // get default account + nsresult rv; + nsCOMPtr <nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIMsgAccount> account; + rv = accountManager->GetDefaultAccount(getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv,rv); + + // get incoming server + nsCOMPtr <nsIMsgIncomingServer> server; + rv = account->GetIncomingServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString type; + rv = server->GetType(type); + NS_ENSURE_SUCCESS(rv,rv); + + // we only care about imap and pop3 + if (type.EqualsLiteral("imap") || type.EqualsLiteral("pop3")) + { + // imap and pop3 account should have an Inbox + nsCOMPtr<nsIMsgFolder> rootMsgFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_SUCCESS(rv,rv); + + if (!rootMsgFolder) + return NS_ERROR_FAILURE; + + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, inboxFolder); + if (!*inboxFolder) + return NS_ERROR_FAILURE; + + } + return NS_OK; +} + +//***************************************************************************** +// Encapsulate the XP DB stuff required to enumerate messages + +class MsgMapiListContext +{ +public: + MsgMapiListContext () {} + ~MsgMapiListContext (); + + nsresult OpenDatabase (nsIMsgFolder *folder); + + nsMsgKey GetNext (); + nsresult MarkRead (nsMsgKey key, bool read); + + lpnsMapiMessage GetMessage (nsMsgKey, unsigned long flFlags); + bool IsIMAPHost(void); + bool DeleteMessage(nsMsgKey key); + +protected: + + char *ConvertDateToMapiFormat (time_t); + char *ConvertBodyToMapiFormat (nsIMsgDBHdr *hdr); + void ConvertRecipientsToMapiFormat(const nsCOMArray<msgIAddressObject> &ourRecips, + lpnsMapiRecipDesc mapiRecips, + int mapiRecipClass); + + nsCOMPtr <nsIMsgFolder> m_folder; + nsCOMPtr <nsIMsgDatabase> m_db; + nsCOMPtr <nsISimpleEnumerator> m_msgEnumerator; +}; + + +LONG CMapiImp::InitContext(unsigned long session, MsgMapiListContext **listContext) +{ + nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ; + if (!pMapiConfig) + return MAPI_E_FAILURE ; // get the singelton obj + *listContext = (MsgMapiListContext *) pMapiConfig->GetMapiListContext(session); + // This is the first message + if (!*listContext) + { + nsCOMPtr <nsIMsgFolder> inboxFolder; + nsresult rv = GetDefaultInbox(getter_AddRefs(inboxFolder)); + if (NS_FAILED(rv)) + { + NS_ASSERTION(false, "in init context, no inbox"); + return(MAPI_E_NO_MESSAGES); + } + + *listContext = new MsgMapiListContext; + if (!*listContext) + return MAPI_E_INSUFFICIENT_MEMORY; + + rv = (*listContext)->OpenDatabase(inboxFolder); + if (NS_FAILED(rv)) + { + pMapiConfig->SetMapiListContext(session, NULL); + delete *listContext; + NS_ASSERTION(false, "in init context, unable to open db"); + return MAPI_E_NO_MESSAGES; + } + else + pMapiConfig->SetMapiListContext(session, *listContext); + } + return SUCCESS_SUCCESS; +} + +STDMETHODIMP CMapiImp::FindNext(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageType, + LPTSTR lpszSeedMessageID, unsigned long flFlags, unsigned long ulReserved, + unsigned char lpszMessageID[64]) + +{ + // + // If this is true, then this is the first call to this FindNext function + // and we should start the enumeration operation. + // + + *lpszMessageID = '\0'; + nsMAPIConfiguration * pMapiConfig = nsMAPIConfiguration::GetMAPIConfiguration() ; + if (!pMapiConfig) + { + NS_ASSERTION(false, "failed to get config in findnext"); + return MAPI_E_FAILURE ; // get the singelton obj + } + MsgMapiListContext *listContext; + LONG ret = InitContext(aSession, &listContext); + if (ret != SUCCESS_SUCCESS) + { + NS_ASSERTION(false, "init context failed"); + return ret; + } + NS_ASSERTION(listContext, "initContext returned null context"); + if (listContext) + { +// NS_ASSERTION(false, "find next init context succeeded"); + nsMsgKey nextKey = listContext->GetNext(); + if (nextKey == nsMsgKey_None) + { + pMapiConfig->SetMapiListContext(aSession, NULL); + delete listContext; + return(MAPI_E_NO_MESSAGES); + } + +// TRACE("MAPI: ProcessMAPIFindNext() Found message id = %d\n", nextKey); + + sprintf((char *) lpszMessageID, "%d", nextKey); + } + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::FindNext returning key %s\n", (char *) lpszMessageID)); + return(SUCCESS_SUCCESS); +} + +STDMETHODIMP CMapiImp::ReadMail(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageID, + unsigned long flFlags, unsigned long ulReserved, lpnsMapiMessage *lppMessage) +{ + nsresult irv; + nsAutoCString keyString((char *) lpszMessageID); + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("CMapiImp::ReadMail asking for key %s\n", (char *) lpszMessageID)); + nsMsgKey msgKey = keyString.ToInteger(&irv); + if (NS_FAILED(irv)) + { + NS_ASSERTION(false, "invalid lpszMessageID"); + return MAPI_E_INVALID_MESSAGE; + } + MsgMapiListContext *listContext; + LONG ret = InitContext(aSession, &listContext); + if (ret != SUCCESS_SUCCESS) + { + NS_ASSERTION(false, "init context failed in ReadMail"); + return ret; + } + *lppMessage = listContext->GetMessage (msgKey, flFlags); + NS_ASSERTION(*lppMessage, "get message failed"); + + return (*lppMessage) ? SUCCESS_SUCCESS : E_FAIL; +} + + +STDMETHODIMP CMapiImp::DeleteMail(unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageID, + unsigned long flFlags, unsigned long ulReserved) +{ + nsresult irv; + nsAutoCString keyString((char *) lpszMessageID); + nsMsgKey msgKey = keyString.ToInteger(&irv); + // XXX Why do we return success on failure? + if (NS_FAILED(irv)) + return SUCCESS_SUCCESS; + MsgMapiListContext *listContext; + LONG ret = InitContext(aSession, &listContext); + if (ret != SUCCESS_SUCCESS) + return ret; + return (listContext->DeleteMessage(msgKey)) ? SUCCESS_SUCCESS : MAPI_E_INVALID_MESSAGE; +} + +STDMETHODIMP CMapiImp::SaveMail(unsigned long aSession, unsigned long ulUIParam, lpnsMapiMessage lppMessage, + unsigned long flFlags, unsigned long ulReserved, LPTSTR lpszMessageID) +{ + MsgMapiListContext *listContext; + LONG ret = InitContext(aSession, &listContext); + if (ret != SUCCESS_SUCCESS) + return ret; + return S_OK; +} + + +STDMETHODIMP CMapiImp::Logoff (unsigned long aSession) +{ + nsMAPIConfiguration *pConfig = nsMAPIConfiguration::GetMAPIConfiguration(); + + if (pConfig->UnRegisterSession((uint32_t)aSession)) + return S_OK; + + return E_FAIL; +} + +STDMETHODIMP CMapiImp::CleanUp() +{ + nsMapiHook::CleanUp(); + return S_OK; +} + + +#define MAX_NAME_LEN 256 + + +MsgMapiListContext::~MsgMapiListContext () +{ + if (m_db) + m_db->Close(false); +} + + +nsresult MsgMapiListContext::OpenDatabase (nsIMsgFolder *folder) +{ + nsresult dbErr = NS_ERROR_FAILURE; + if (folder) + { + m_folder = folder; + dbErr = folder->GetMsgDatabase(getter_AddRefs(m_db)); + if (m_db) + dbErr = m_db->EnumerateMessages(getter_AddRefs(m_msgEnumerator)); + } + return dbErr; +} + +bool +MsgMapiListContext::IsIMAPHost(void) +{ + if (!m_folder) + return FALSE; + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder); + + return imapFolder != nullptr; +} + +nsMsgKey MsgMapiListContext::GetNext () +{ + nsMsgKey key = nsMsgKey_None; + bool keepTrying = TRUE; + +// NS_ASSERTION (m_msgEnumerator && m_db, "need enumerator and db"); + if (m_msgEnumerator && m_db) + { + + do + { + keepTrying = FALSE; + nsCOMPtr <nsISupports> hdrISupports; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + if (NS_SUCCEEDED(m_msgEnumerator->GetNext(getter_AddRefs(hdrISupports))) && hdrISupports) + { + msgHdr = do_QueryInterface(hdrISupports); + msgHdr->GetMessageKey(&key); + + // Check here for IMAP message...if not, just return... + if (!IsIMAPHost()) + return key; + + // If this is an IMAP message, we have to make sure we have a valid + // body to work with. + uint32_t flags = 0; + + (void) msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Offline) + return key; + + // Ok, if we get here, we have an IMAP message without a body! + // We need to keep trying by calling the GetNext member recursively... + keepTrying = TRUE; + } + } while (keepTrying); + } + + return key; +} + + +nsresult MsgMapiListContext::MarkRead (nsMsgKey key, bool read) +{ + nsresult err = NS_ERROR_FAILURE; + NS_ASSERTION(m_db, "no db"); + if (m_db) + err = m_db->MarkRead (key, read, nullptr); + return err; +} + + +lpnsMapiMessage MsgMapiListContext::GetMessage (nsMsgKey key, unsigned long flFlags) +{ + lpnsMapiMessage message = (lpnsMapiMessage) CoTaskMemAlloc (sizeof(nsMapiMessage)); + memset(message, 0, sizeof(nsMapiMessage)); + if (message) + { + nsCString subject; + nsCString author; + nsCOMPtr <nsIMsgDBHdr> msgHdr; + + nsresult rv = m_db->GetMsgHdrForKey (key, getter_AddRefs(msgHdr)); + if (msgHdr) + { + msgHdr->GetSubject (getter_Copies(subject)); + message->lpszSubject = (char *) CoTaskMemAlloc(subject.Length() + 1); + strcpy((char *) message->lpszSubject, subject.get()); + uint32_t date; + (void) msgHdr->GetDateInSeconds(&date); + message->lpszDateReceived = ConvertDateToMapiFormat (date); + + // Pull out the flags info + // anything to do with MAPI_SENT? Since we're only reading the Inbox, I guess not + uint32_t ourFlags; + (void) msgHdr->GetFlags(&ourFlags); + if (!(ourFlags & nsMsgMessageFlags::Read)) + message->flFlags |= MAPI_UNREAD; + if (ourFlags & (nsMsgMessageFlags::MDNReportNeeded | nsMsgMessageFlags::MDNReportSent)) + message->flFlags |= MAPI_RECEIPT_REQUESTED; + + // Pull out the author/originator info + message->lpOriginator = (lpnsMapiRecipDesc) CoTaskMemAlloc (sizeof(nsMapiRecipDesc)); + memset(message->lpOriginator, 0, sizeof(nsMapiRecipDesc)); + if (message->lpOriginator) + { + msgHdr->GetAuthor (getter_Copies(author)); + ConvertRecipientsToMapiFormat(EncodedHeader(author), + message->lpOriginator, MAPI_ORIG); + } + // Pull out the To/CC info + nsCString recipients, ccList; + msgHdr->GetRecipients(getter_Copies(recipients)); + msgHdr->GetCcList(getter_Copies(ccList)); + + nsCOMArray<msgIAddressObject> parsedToRecips = EncodedHeader(recipients); + nsCOMArray<msgIAddressObject> parsedCCRecips = EncodedHeader(ccList); + uint32_t numToRecips = parsedToRecips.Length(); + uint32_t numCCRecips = parsedCCRecips.Length(); + + message->lpRecips = (lpnsMapiRecipDesc) CoTaskMemAlloc ((numToRecips + numCCRecips) * sizeof(MapiRecipDesc)); + memset(message->lpRecips, 0, (numToRecips + numCCRecips) * sizeof(MapiRecipDesc)); + if (message->lpRecips) + { + ConvertRecipientsToMapiFormat(parsedToRecips, message->lpRecips, + MAPI_TO); + ConvertRecipientsToMapiFormat(parsedCCRecips, + &message->lpRecips[numToRecips], MAPI_CC); + } + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("MsgMapiListContext::GetMessage flags=%x subject %s date %s sender %s\n", + flFlags, (char *) message->lpszSubject,(char *) message->lpszDateReceived, author.get()) ); + + // Convert any body text that we have locally + if (!(flFlags & MAPI_ENVELOPE_ONLY)) + message->lpszNoteText = (char *) ConvertBodyToMapiFormat (msgHdr); + + } + if (! (flFlags & (MAPI_PEEK | MAPI_ENVELOPE_ONLY))) + m_db->MarkRead(key, true, nullptr); + } + return message; +} + + +char *MsgMapiListContext::ConvertDateToMapiFormat (time_t ourTime) +{ + char *date = (char*) CoTaskMemAlloc(32); + if (date) + { + // MAPI time format is YYYY/MM/DD HH:MM + // Note that we're not using XP_StrfTime because that localizes the time format, + // and the way I read the MAPI spec is that their format is canonical, not localized. + struct tm *local = localtime (&ourTime); + if (local) + strftime (date, 32, "%Y/%m/%d %I:%M", local); //use %H if hours should be 24 hour format + } + return date; +} + + +void MsgMapiListContext::ConvertRecipientsToMapiFormat( + const nsCOMArray<msgIAddressObject> &recipients, + lpnsMapiRecipDesc mapiRecips, int mapiRecipClass) +{ + nsTArray<nsCString> names, addresses; + ExtractAllAddresses(recipients, UTF16ArrayAdapter<>(names), + UTF16ArrayAdapter<>(addresses)); + + size_t numAddresses = names.Length(); + for (size_t i = 0; i < numAddresses; i++) + { + if (!names[i].IsEmpty()) + { + mapiRecips[i].lpszName = (char *) CoTaskMemAlloc(names[i].Length() + 1); + if (mapiRecips[i].lpszName) + strcpy((char *)mapiRecips[i].lpszName, names[i].get()); + } + if (!addresses[i].IsEmpty()) + { + mapiRecips[i].lpszName = (char *) CoTaskMemAlloc(addresses[i].Length() + 1); + if (mapiRecips[i].lpszName) + strcpy((char *)mapiRecips[i].lpszName, addresses[i].get()); + } + mapiRecips[i].ulRecipClass = mapiRecipClass; + } +} + + +char *MsgMapiListContext::ConvertBodyToMapiFormat (nsIMsgDBHdr *hdr) +{ + const int kBufLen = 64000; // I guess we only return the first 64K of a message. +#define EMPTY_MESSAGE_LINE(buf) (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0') + + nsCOMPtr <nsIMsgFolder> folder; + hdr->GetFolder(getter_AddRefs(folder)); + if (!folder) + return nullptr; + + nsCOMPtr <nsIInputStream> inputStream; + nsCOMPtr <nsIFile> localFile; + folder->GetFilePath(getter_AddRefs(localFile)); + + nsresult rv; + nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = fileStream->Init(localFile, PR_RDONLY, 0664, false); //just have to read the messages + inputStream = do_QueryInterface(fileStream); + + if (inputStream) + { + nsCOMPtr <nsILineInputStream> fileLineStream = do_QueryInterface(inputStream); + if (!fileLineStream) + return nullptr; + // ### really want to skip past headers... + uint64_t messageOffset; + uint32_t lineCount; + hdr->GetMessageOffset(&messageOffset); + hdr->GetLineCount(&lineCount); + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream); + seekableStream->Seek(PR_SEEK_SET, messageOffset); + bool hasMore = true; + nsAutoCString curLine; + nsresult rv = NS_OK; + while (hasMore) // advance past message headers + { + nsresult rv = fileLineStream->ReadLine(curLine, &hasMore); + if (NS_FAILED(rv) || EMPTY_MESSAGE_LINE(curLine)) + break; + } + uint32_t msgSize; + hdr->GetMessageSize(&msgSize); + if (msgSize > kBufLen) + msgSize = kBufLen - 1; + // this is too big, since it includes the msg hdr size...oh well + char *body = (char*) CoTaskMemAlloc (msgSize + 1); + + if (!body) + return nullptr; + int32_t bytesCopied = 0; + for (hasMore = TRUE; lineCount > 0 && hasMore && NS_SUCCEEDED(rv); lineCount--) + { + rv = fileLineStream->ReadLine(curLine, &hasMore); + if (NS_FAILED(rv)) + break; + curLine.Append(CRLF); + // make sure we have room left + if (bytesCopied + curLine.Length() < msgSize) + { + strcpy(body + bytesCopied, curLine.get()); + bytesCopied += curLine.Length(); + } + } + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, ("ConvertBodyToMapiFormat size=%x allocated size %x body = %100.100s\n", + bytesCopied, msgSize + 1, (char *) body) ); + body[bytesCopied] = '\0'; // rhp - fix last line garbage... + return body; + } + return nullptr; +} + + +//***************************************************************************** +// MSGMAPI API implementation + + + +static void msg_FreeMAPIFile(lpMapiFileDesc f) +{ + if (f) + { + CoTaskMemFree(f->lpszPathName); + CoTaskMemFree(f->lpszFileName); + } +} + +static void msg_FreeMAPIRecipient(lpMapiRecipDesc rd) +{ + if (rd) + { + if (rd->lpszName) + CoTaskMemFree(rd->lpszName); + if (rd->lpszAddress) + CoTaskMemFree(rd->lpszAddress); + // CoTaskMemFree(rd->lpEntryID); + } +} + +extern "C" void MSG_FreeMapiMessage (lpMapiMessage msg) +{ + ULONG i; + + if (msg) + { + CoTaskMemFree(msg->lpszSubject); + CoTaskMemFree(msg->lpszNoteText); + CoTaskMemFree(msg->lpszMessageType); + CoTaskMemFree(msg->lpszDateReceived); + CoTaskMemFree(msg->lpszConversationID); + + if (msg->lpOriginator) + msg_FreeMAPIRecipient(msg->lpOriginator); + + for (i=0; i<msg->nRecipCount; i++) + if (&(msg->lpRecips[i]) != nullptr) + msg_FreeMAPIRecipient(&(msg->lpRecips[i])); + + CoTaskMemFree(msg->lpRecips); + + for (i=0; i<msg->nFileCount; i++) + if (&(msg->lpFiles[i]) != nullptr) + msg_FreeMAPIFile(&(msg->lpFiles[i])); + + CoTaskMemFree(msg->lpFiles); + + CoTaskMemFree(msg); + } +} + + +extern "C" bool MsgMarkMapiMessageRead (nsIMsgFolder *folder, nsMsgKey key, bool read) +{ + bool success = FALSE; + MsgMapiListContext *context = new MsgMapiListContext(); + if (context) + { + if (NS_SUCCEEDED(context->OpenDatabase(folder))) + { + if (NS_SUCCEEDED(context->MarkRead (key, read))) + success = TRUE; + } + delete context; + } + return success; +} + +bool +MsgMapiListContext::DeleteMessage(nsMsgKey key) +{ + if (!m_db) + return FALSE; + + if ( !IsIMAPHost() ) + { + return NS_SUCCEEDED((m_db->DeleteMessages(1, &key, nullptr))); + } +#if 0 + else if ( m_folder->GetIMAPFolderInfoMail() ) + { + AutoTArray<nsMsgKey, 1> messageKeys; + messageKeys.AppendElement(key); + + (m_folder->GetIMAPFolderInfoMail())->DeleteSpecifiedMessages(pane, messageKeys, nsMsgKey_None); + m_db->DeleteMessage(key, nullptr, FALSE); + return TRUE; + } +#endif + else + { + return FALSE; + } +} + +/* Return TRUE on success, FALSE on failure */ +extern "C" bool MSG_DeleteMapiMessage(nsIMsgFolder *folder, nsMsgKey key) +{ + bool success = FALSE; + MsgMapiListContext *context = new MsgMapiListContext(); + if (context) + { + if (NS_SUCCEEDED(context->OpenDatabase(folder))) + { + success = context->DeleteMessage(key); + } + + delete context; + } + + return success; +} + diff --git a/mailnews/mapi/mapihook/src/msgMapiImp.h b/mailnews/mapi/mapihook/src/msgMapiImp.h new file mode 100644 index 000000000..875e9c6cf --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiImp.h @@ -0,0 +1,77 @@ +/* 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 MSG_MAPI_IMP_H +#define MSG_MAPI_IMP_H + +#include <windows.h> +#include <mapi.h> +#include "msgMapi.h" +#include "nspr.h" +#include "nscore.h" +#include "nsISupportsImpl.h" // ThreadSafeAutoRefCnt + +class nsIMsgFolder; +class MsgMapiListContext; + +const CLSID CLSID_CMapiImp = {0x29f458be, 0x8866, 0x11d5, {0xa3, 0xdd, 0x0, 0xb0, 0xd0, 0xf3, 0xba, 0xa7}}; + +// this class implements the MS COM interface nsIMapi that provides the methods +// called by mapi32.dll to perform the mail operations as specified by MAPI. +// These class methods in turn use the Mozilla Mail XPCOM interfaces to do so. +class CMapiImp : public nsIMapi +{ + +public : + + // IUnknown + + STDMETHODIMP QueryInterface(const IID& aIid, void** aPpv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // Interface INsMapi + + STDMETHODIMP Login(unsigned long aUIArg, LOGIN_PW_TYPE aLogin, + LOGIN_PW_TYPE aPassWord, unsigned long aFlags, + unsigned long *aSessionId); + + STDMETHODIMP SendMail( unsigned long aSession, lpnsMapiMessage aMessage, + short aRecipCount, lpnsMapiRecipDesc aRecips , + short aFileCount, lpnsMapiFileDesc aFiles , + unsigned long aFlags, unsigned long aReserved) ; + + STDMETHODIMP SendDocuments( unsigned long aSession, LPTSTR aDelimChar, + LPTSTR aFilePaths, LPTSTR aFileNames, ULONG aFlags); + + STDMETHODIMP FindNext( unsigned long aSession, unsigned long ulUIParam, LPTSTR lpszMessageType, + LPTSTR lpszSeedMessageID, unsigned long flFlags, unsigned long ulReserved, + unsigned char lpszMessageID[64] ); + + STDMETHODIMP ReadMail(unsigned long lhSession, unsigned long ulUIParam, LPTSTR lpszMessageID, + unsigned long flFlags, unsigned long ulReserved, lpnsMapiMessage *lppMessage); + STDMETHODIMP DeleteMail(unsigned long lhSession, unsigned long ulUIParam, LPTSTR lpszMessageID, + unsigned long flFlags, unsigned long ulReserved); + STDMETHODIMP SaveMail(unsigned long lhSession, unsigned long ulUIParam, lpnsMapiMessage lppMessage, + unsigned long flFlags, unsigned long ulReserved, LPTSTR lpszMessageID); + + STDMETHODIMP Initialize(); + STDMETHODIMP IsValid(); + STDMETHODIMP IsValidSession(unsigned long aSession); + + STDMETHODIMP Logoff (unsigned long aSession); + STDMETHODIMP CleanUp(); + + CMapiImp(); + ~CMapiImp(); + + LONG InitContext(unsigned long session, MsgMapiListContext **listContext); + nsresult GetDefaultInbox(nsIMsgFolder **inboxFolder); + +private : + PRLock *m_Lock; + mozilla::ThreadSafeAutoRefCnt m_cRef; +}; + +#endif // MSG_MAPI_IMP_H diff --git a/mailnews/mapi/mapihook/src/msgMapiMain.cpp b/mailnews/mapi/mapihook/src/msgMapiMain.cpp new file mode 100644 index 000000000..adb5cc14c --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiMain.cpp @@ -0,0 +1,306 @@ +/* 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 <mapidefs.h> +#include <mapi.h> + +#include "msgCore.h" +#include "nsComposeStrings.h" +#include "msgMapiMain.h" +#include "nsIServiceManager.h" +#include "nsCOMPtr.h" + +nsMAPIConfiguration *nsMAPIConfiguration::m_pSelfRef = nullptr; +uint32_t nsMAPIConfiguration::session_generator = 0; +uint32_t nsMAPIConfiguration::sessionCount = 0; + +nsMAPIConfiguration *nsMAPIConfiguration::GetMAPIConfiguration() +{ + if (m_pSelfRef == nullptr) + m_pSelfRef = new nsMAPIConfiguration(); + + return m_pSelfRef; +} + +nsMAPIConfiguration::nsMAPIConfiguration() +: m_nMaxSessions(MAX_SESSIONS) +{ + m_Lock = PR_NewLock(); +} + +nsMAPIConfiguration::~nsMAPIConfiguration() +{ + if (m_Lock) + PR_DestroyLock(m_Lock); +} + +void nsMAPIConfiguration::OpenConfiguration() +{ + // No. of max. sessions is set to MAX_SESSIONS. In future + // if it is decided to have configuration (registry) + // parameter, this function can be used to set the + // max sessions; + + return; +} + +int16_t nsMAPIConfiguration::RegisterSession(uint32_t aHwnd, + const char16_t *aUserName, const char16_t *aPassword, + bool aForceDownLoad, bool aNewSession, + uint32_t *aSession, const char *aIdKey) +{ + int16_t nResult = 0; + uint32_t n_SessionId = 0; + + PR_Lock(m_Lock); + + // Check whether max sessions is exceeded + + if (sessionCount >= m_nMaxSessions) + { + PR_Unlock(m_Lock); + return -1; + } + + if (aUserName != nullptr && aUserName[0] != '\0') + m_ProfileMap.Get(nsDependentString(aUserName), &n_SessionId); + + // try to share a session; if not create a session + if (n_SessionId > 0) + { + nsMAPISession *pTemp = nullptr; + m_SessionMap.Get(n_SessionId, &pTemp); + if (pTemp != nullptr) + { + pTemp->IncrementSession(); + *aSession = n_SessionId; + nResult = 1; + } + } + else if (aNewSession || n_SessionId == 0) // checking for n_SessionId is a concession + { + // create a new session; if new session is specified OR there is no session + nsMAPISession *pTemp = nullptr; + pTemp = new nsMAPISession(aHwnd, aUserName, + aPassword, aForceDownLoad, aIdKey); + + if (pTemp != nullptr) + { + session_generator++; + + // I don't think there will be (2 power 32) sessions alive + // in a cycle. This is an assumption + + if (session_generator == 0) + session_generator++; + m_SessionMap.Put(session_generator, pTemp); + if (aUserName != nullptr && aUserName[0] != '\0') + m_ProfileMap.Put(nsDependentString(aUserName), session_generator); + *aSession = session_generator; + sessionCount++; + nResult = 1; + } + } + + PR_Unlock(m_Lock); + return nResult; +} + +bool nsMAPIConfiguration::UnRegisterSession(uint32_t aSessionID) +{ + bool bResult = false; + + PR_Lock(m_Lock); + + if (aSessionID != 0) + { + nsMAPISession *pTemp = nullptr; + m_SessionMap.Get(aSessionID, &pTemp); + + if (pTemp != nullptr) + { + if (pTemp->DecrementSession() == 0) + { + if (pTemp->m_pProfileName.get() != nullptr) + m_ProfileMap.Remove(pTemp->m_pProfileName); + m_SessionMap.Remove(aSessionID); + sessionCount--; + bResult = true; + } + } + } + + PR_Unlock(m_Lock); + return bResult; +} + +bool nsMAPIConfiguration::IsSessionValid(uint32_t aSessionID) +{ + if (aSessionID == 0) + return false; + bool retValue = false; + PR_Lock(m_Lock); + retValue = m_SessionMap.Get(aSessionID, NULL); + PR_Unlock(m_Lock); + return retValue; +} + +char16_t *nsMAPIConfiguration::GetPassword(uint32_t aSessionID) +{ + char16_t *pResult = nullptr; + + PR_Lock(m_Lock); + + if (aSessionID != 0) + { + nsMAPISession *pTemp = nullptr; + m_SessionMap.Get(aSessionID, &pTemp); + + if (pTemp) + pResult = pTemp->GetPassword(); + } + PR_Unlock(m_Lock); + return pResult; +} + +void *nsMAPIConfiguration::GetMapiListContext(uint32_t aSessionID) +{ + void *pResult = nullptr; + + PR_Lock(m_Lock); + + if (aSessionID != 0) + { + nsMAPISession *pTemp = nullptr; + m_SessionMap.Get(aSessionID, &pTemp); + if (pTemp) + pResult = pTemp->GetMapiListContext(); + } + + PR_Unlock(m_Lock); + return pResult; +} + +void nsMAPIConfiguration::SetMapiListContext(uint32_t aSessionID, void *mapiListContext) +{ + PR_Lock(m_Lock); + + if (aSessionID != 0) + { + nsMAPISession *pTemp = nullptr; + m_SessionMap.Get(aSessionID, &pTemp); + if (pTemp) + pTemp->SetMapiListContext(mapiListContext); + } + + PR_Unlock(m_Lock); +} + +void nsMAPIConfiguration::GetIdKey(uint32_t aSessionID, nsCString& aKey) +{ + PR_Lock(m_Lock); + if (aSessionID != 0) + { + nsMAPISession *pTemp = nullptr; + m_SessionMap.Get(aSessionID, &pTemp); + if (pTemp) + pTemp->GetIdKey(aKey); + } + PR_Unlock(m_Lock); + return; +} + +// util func +HRESULT nsMAPIConfiguration::GetMAPIErrorFromNSError (nsresult res) +{ + HRESULT hr = SUCCESS_SUCCESS; + + if (NS_SUCCEEDED (res)) return hr; + + // if failure return the related MAPI failure code + switch (res) + { + case NS_MSG_NO_RECIPIENTS : + hr = MAPI_E_BAD_RECIPTYPE; + break; + case NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS : + hr = MAPI_E_INVALID_RECIPS; + break; + case NS_ERROR_SMTP_AUTH_FAILURE : + case NS_ERROR_SMTP_AUTH_GSSAPI : + case NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED : + case NS_ERROR_SMTP_AUTH_NOT_SUPPORTED : + case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL : + case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL : + case NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT : + hr = MAPI_E_LOGIN_FAILURE; + break; + case NS_MSG_UNABLE_TO_OPEN_FILE : + case NS_MSG_UNABLE_TO_OPEN_TMP_FILE : + case NS_MSG_COULDNT_OPEN_FCC_FOLDER : + case NS_ERROR_FILE_INVALID_PATH : + hr = MAPI_E_ATTACHMENT_OPEN_FAILURE; + break; + case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST : + hr = MAPI_E_ATTACHMENT_NOT_FOUND; + break; + case NS_MSG_CANCELLING : + hr = MAPI_E_USER_ABORT; + break; + case NS_MSG_ERROR_WRITING_FILE : + case NS_MSG_UNABLE_TO_SAVE_TEMPLATE : + case NS_MSG_UNABLE_TO_SAVE_DRAFT : + hr = MAPI_E_ATTACHMENT_WRITE_FAILURE; + break; + default: + hr = MAPI_E_FAILURE; + break; + } + + return hr; +} + + +nsMAPISession::nsMAPISession(uint32_t aHwnd, const char16_t *aUserName, + const char16_t *aPassword, + bool aForceDownLoad, const char *aKey) +: m_bIsForcedDownLoad(aForceDownLoad), + m_hAppHandle(aHwnd), + m_nShared(1), + m_pIdKey(aKey) +{ + m_listContext = NULL; + m_pProfileName.Assign(aUserName); + m_pPassword.Assign(aPassword); +} + +nsMAPISession::~nsMAPISession() +{ +} + +uint32_t nsMAPISession::IncrementSession() +{ + return ++m_nShared; +} + +uint32_t nsMAPISession::DecrementSession() +{ + return --m_nShared; +} + +uint32_t nsMAPISession::GetSessionCount() +{ + return m_nShared; +} + +char16_t *nsMAPISession::GetPassword() +{ + return (char16_t *)m_pPassword.get(); +} + +void nsMAPISession::GetIdKey(nsCString& aKey) +{ + aKey = m_pIdKey; + return; +} diff --git a/mailnews/mapi/mapihook/src/msgMapiMain.h b/mailnews/mapi/mapihook/src/msgMapiMain.h new file mode 100644 index 000000000..be74c5db1 --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiMain.h @@ -0,0 +1,86 @@ +/* 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 MSG_MAPI_MAIN_H_ +#define NSG_MAPI_MAIN_H_ + +#define MAX_NAME_LEN 256 +#define MAX_PW_LEN 256 +#define MAX_SESSIONS 50 +#define MAPI_SENDCOMPLETE_EVENT "SendCompletionEvent" + +#define MAPI_PROPERTIES_CHROME "chrome://messenger-mapi/locale/mapi.properties" +#define PREF_MAPI_WARN_PRIOR_TO_BLIND_SEND "mapi.blind-send.warn" +#define PREF_MAPI_BLIND_SEND_ENABLED "mapi.blind-send.enabled" + +#include "nspr.h" +#include "nsDataHashtable.h" +#include "nsClassHashtable.h" +#include "nsStringGlue.h" + +class nsMAPISession; + +class nsMAPIConfiguration +{ +private : + + static uint32_t session_generator; + static uint32_t sessionCount; + static nsMAPIConfiguration *m_pSelfRef; + PRLock *m_Lock; + uint32_t m_nMaxSessions; + + nsDataHashtable<nsStringHashKey, uint32_t> m_ProfileMap; + nsClassHashtable<nsUint32HashKey, nsMAPISession> m_SessionMap; + nsMAPIConfiguration(); + ~nsMAPIConfiguration(); + +public : + static nsMAPIConfiguration *GetMAPIConfiguration(); + void OpenConfiguration(); + int16_t RegisterSession(uint32_t aHwnd, const char16_t *aUserName, \ + const char16_t *aPassword, bool aForceDownLoad, \ + bool aNewSession, uint32_t *aSession, const char *aIdKey); + bool IsSessionValid(uint32_t aSessionID); + bool UnRegisterSession(uint32_t aSessionID); + char16_t *GetPassword(uint32_t aSessionID); + void GetIdKey(uint32_t aSessionID, nsCString& aKey); + void *GetMapiListContext(uint32_t aSessionID); + void SetMapiListContext(uint32_t aSessionID, void *mapiListContext); + + // a util func + static HRESULT GetMAPIErrorFromNSError (nsresult res) ; +}; + +class nsMAPISession +{ + friend class nsMAPIConfiguration; + + private : + bool m_bIsForcedDownLoad; + bool m_bApp_or_Service; + uint32_t m_hAppHandle; + uint32_t m_nShared; + nsCString m_pIdKey; + nsString m_pProfileName; + nsString m_pPassword; + int32_t m_messageIndex; + void *m_listContext; // used by findNext + + public : + nsMAPISession(uint32_t aHwnd, const char16_t *aUserName, \ + const char16_t *aPassword, \ + bool aForceDownLoad, const char *aKey); + uint32_t IncrementSession(); + uint32_t DecrementSession(); + uint32_t GetSessionCount(); + char16_t *nsMAPISession::GetPassword(); + void GetIdKey(nsCString& aKey); + ~nsMAPISession(); + // For enumerating Messages... + void SetMapiListContext( void *listContext) { m_listContext = listContext; } + void *GetMapiListContext( ) { return m_listContext; } +}; + +#endif // MSG_MAPI_MAIN_H_ diff --git a/mailnews/mapi/mapihook/src/msgMapiSupport.cpp b/mailnews/mapi/mapihook/src/msgMapiSupport.cpp new file mode 100644 index 000000000..5c57f087d --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiSupport.cpp @@ -0,0 +1,151 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "objbase.h" +#include "nsISupports.h" + +#include "mozilla/ModuleUtils.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "nsIAppStartupNotifier.h" +#include "nsIServiceManager.h" +#include "nsIComponentManager.h" +#include "nsICategoryManager.h" +#include "Registry.h" +#include "msgMapiSupport.h" + +#include "msgMapiImp.h" + +/** Implementation of the nsIMapiSupport interface. + * Use standard implementation of nsISupports stuff. + */ + +NS_IMPL_ISUPPORTS(nsMapiSupport, nsIMapiSupport, nsIObserver) + +NS_IMETHODIMP +nsMapiSupport::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + nsresult rv = NS_OK ; + + if (!strcmp(aTopic, "profile-after-change")) + return InitializeMAPISupport(); + + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) + return ShutdownMAPISupport(); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + rv = observerService->AddObserver(this,"profile-after-change", false); + if (NS_FAILED(rv)) return rv; + + rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_FAILED(rv)) return rv; + + return rv; +} + + +nsMapiSupport::nsMapiSupport() +: m_dwRegister(0), + m_nsMapiFactory(nullptr) +{ +} + +nsMapiSupport::~nsMapiSupport() +{ +} + +NS_IMETHODIMP +nsMapiSupport::InitializeMAPISupport() +{ + ::OleInitialize(nullptr) ; + + if (m_nsMapiFactory == nullptr) // No Registering if already done. Sanity Check!! + { + m_nsMapiFactory = new CMapiFactory(); + + if (m_nsMapiFactory != nullptr) + { + HRESULT hr = ::CoRegisterClassObject(CLSID_CMapiImp, \ + m_nsMapiFactory, \ + CLSCTX_LOCAL_SERVER, \ + REGCLS_MULTIPLEUSE, \ + &m_dwRegister); + + if (FAILED(hr)) + { + m_nsMapiFactory->Release() ; + m_nsMapiFactory = nullptr; + return NS_ERROR_FAILURE; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMapiSupport::ShutdownMAPISupport() +{ + if (m_dwRegister != 0) + ::CoRevokeClassObject(m_dwRegister); + + if (m_nsMapiFactory != nullptr) + { + m_nsMapiFactory->Release(); + m_nsMapiFactory = nullptr; + } + + ::OleUninitialize(); + + return NS_OK ; +} + +NS_IMETHODIMP +nsMapiSupport::RegisterServer() +{ + // TODO: Figure out what kind of error propogation to pass back + ::RegisterServer(CLSID_CMapiImp, "Mozilla MAPI", "MozillaMapi", "MozillaMapi.1"); + return NS_OK; +} + +NS_IMETHODIMP +nsMapiSupport::UnRegisterServer() +{ + // TODO: Figure out what kind of error propogation to pass back + ::UnregisterServer(CLSID_CMapiImp, "MozillaMapi", "MozillaMapi.1"); + return NS_OK; +} + +NS_DEFINE_NAMED_CID(NS_IMAPISUPPORT_CID); + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsMapiSupport) + +static const mozilla::Module::CategoryEntry kMAPICategories[] = { + { APPSTARTUP_CATEGORY, "Mapi Support", "service," NS_IMAPISUPPORT_CONTRACTID, }, + { NULL } +}; + +const mozilla::Module::CIDEntry kMAPICIDs[] = { + { &kNS_IMAPISUPPORT_CID, false, NULL, nsMapiSupportConstructor }, + { NULL } +}; + +const mozilla::Module::ContractIDEntry kMAPIContracts[] = { + { NS_IMAPISUPPORT_CONTRACTID, &kNS_IMAPISUPPORT_CID }, + { NULL } +}; + +static const mozilla::Module kMAPIModule = { + mozilla::Module::kVersion, + kMAPICIDs, + kMAPIContracts, + kMAPICategories +}; + +NSMODULE_DEFN(msgMapiModule) = &kMAPIModule; + + diff --git a/mailnews/mapi/mapihook/src/msgMapiSupport.h b/mailnews/mapi/mapihook/src/msgMapiSupport.h new file mode 100644 index 000000000..ff7ffca9f --- /dev/null +++ b/mailnews/mapi/mapihook/src/msgMapiSupport.h @@ -0,0 +1,34 @@ +/* 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 MSG_MAPI_SUPPORT_H_ +#define MSG_MAPI_SUPPORT_H_ + +#include "nsIObserver.h" +#include "nsIMapiSupport.h" +#include "msgMapiFactory.h" + +#define NS_IMAPISUPPORT_CID \ + {0x8967fed2, 0xc8bb, 0x11d5, \ + { 0xa3, 0xe9, 0x00, 0xb0, 0xd0, 0xf3, 0xba, 0xa7 }} + +class nsMapiSupport : public nsIMapiSupport, + public nsIObserver +{ + public : + nsMapiSupport(); + + // Declare all interface methods we must implement. + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIMAPISUPPORT + + private : + ~nsMapiSupport(); + + DWORD m_dwRegister; + CMapiFactory *m_nsMapiFactory; +}; + +#endif // MSG_MAPI_SUPPORT_H_ |