diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/mapi/mapihook/src | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/mapi/mapihook/src')
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/Registry.cpp | 249 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/Registry.h | 20 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/components.conf | 18 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/moz.build | 33 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiFactory.cpp | 64 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiFactory.h | 35 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp | 934 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiHook.h | 39 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiImp.cpp | 801 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiImp.h | 79 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiMain.cpp | 248 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiMain.h | 80 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiSupport.cpp | 101 | ||||
-rw-r--r-- | comm/mailnews/mapi/mapihook/src/msgMapiSupport.h | 35 |
14 files changed, 2736 insertions, 0 deletions
diff --git a/comm/mailnews/mapi/mapihook/src/Registry.cpp b/comm/mailnews/mapi/mapihook/src/Registry.cpp new file mode 100644 index 0000000000..bf57e31e57 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/Registry.cpp @@ -0,0 +1,249 @@ +/* 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 "nsString.h" +#include "Registry.h" + +#define MAPI_PROXY_DLL_NAME u"MapiProxy.dll" +#define MAPI_STARTUP_ARG L" /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 WCHAR string. + +BOOL CLSIDtoWchar(const CLSID& clsid, WCHAR* szCLSID) { + // Get CLSID + HRESULT hr = StringFromCLSID(clsid, &szCLSID); + if (FAILED(hr)) return FALSE; + return TRUE; +} + +// Create a key and set its value. + +BOOL setKeyAndValue(nsAutoString keyName, const WCHAR* subKey, + const WCHAR* theValue) { + HKEY hKey; + BOOL retValue = TRUE; + + nsAutoString theKey(keyName); + if (subKey != NULL) { + theKey += L"\\"; + theKey += subKey; + } + + // Create and open key and subkey. + long lResult = RegCreateKeyExW(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 = RegSetValueExW(hKey, NULL, 0, REG_SZ, (BYTE*)theValue, + wcslen(theValue) + 1); + if (lResult != ERROR_SUCCESS) retValue = FALSE; + } + + RegCloseKey(hKey); + return retValue; +} + +// Delete a key and all of its descendents. + +LONG recursiveDeleteKey(HKEY hKeyParent, // Parent of key to delete + const WCHAR* lpszKeyChild) // Key to delete +{ + // Open the child. + HKEY hKeyChild; + LONG lRes = + RegOpenKeyExW(hKeyParent, lpszKeyChild, 0, KEY_ALL_ACCESS, &hKeyChild); + if (lRes != ERROR_SUCCESS) { + return lRes; + } + + // Enumerate all of the descendants of this child. + FILETIME time; + WCHAR szBuffer[MAX_SIZE]; + DWORD dwSize = MAX_SIZE; + while (RegEnumKeyExW(hKeyChild, 0, szBuffer, &dwSize, NULL, NULL, NULL, + &time) == S_OK) { + // Delete the descendants 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 RegDeleteKeyW(hKeyParent, lpszKeyChild); +} + +void RegisterProxy() { + HINSTANCE h = NULL; + ProxyServer* RegisterFunc = NULL; + + WCHAR szModule[MAX_SIZE]; + WCHAR* pTemp = NULL; + + HMODULE hModule = GetModuleHandleW(NULL); + DWORD dwResult = + ::GetModuleFileNameW(hModule, szModule, sizeof(szModule) / sizeof(WCHAR)); + if (dwResult == 0) return; + + pTemp = wcsrchr(szModule, L'\\'); + if (pTemp == NULL) return; + + *pTemp = '\0'; + nsAutoString proxyPath(szModule); + + proxyPath += u"\\"; + proxyPath += MAPI_PROXY_DLL_NAME; + + h = LoadLibraryW(proxyPath.get()); + if (h == NULL) return; + + RegisterFunc = (ProxyServer*)GetProcAddress(h, "DllRegisterServer"); + if (RegisterFunc) RegisterFunc(); + + FreeLibrary(h); +} + +void UnRegisterProxy() { + HINSTANCE h = NULL; + ProxyServer* UnRegisterFunc = NULL; + + WCHAR szModule[MAX_SIZE]; + WCHAR* pTemp = NULL; + + HMODULE hModule = GetModuleHandleW(NULL); + DWORD dwResult = + ::GetModuleFileNameW(hModule, szModule, sizeof(szModule) / sizeof(WCHAR)); + if (dwResult == 0) return; + + pTemp = wcsrchr(szModule, L'\\'); + if (pTemp == NULL) return; + + *pTemp = '\0'; + nsAutoString proxyPath(szModule); + + proxyPath += u"\\"; + proxyPath += MAPI_PROXY_DLL_NAME; + + h = LoadLibraryW(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 WCHAR* szFriendlyName, // Friendly Name + const WCHAR* szVerIndProgID, // Programmatic + const WCHAR* szProgID) // IDs +{ + HMODULE hModule = GetModuleHandleW(NULL); + WCHAR szModuleName[MAX_SIZE]; + WCHAR szCLSID[CLSID_STRING_SIZE]; + + nsAutoString independentProgId(szVerIndProgID); + nsAutoString progId(szProgID); + + DWORD dwResult = ::GetModuleFileNameW(hModule, szModuleName, + sizeof(szModuleName) / sizeof(WCHAR)); + + if (dwResult == 0) return S_FALSE; + + nsAutoString moduleName(szModuleName); + nsAutoString registryKey(L"CLSID\\"); + + moduleName += MAPI_STARTUP_ARG; + + // Convert the CLSID into a WCHAR. + if (!CLSIDtoWchar(clsid, szCLSID)) return S_FALSE; + registryKey += szCLSID; + + // Add the CLSID to the registry. + if (!setKeyAndValue(registryKey, NULL, szFriendlyName)) return S_FALSE; + + if (!setKeyAndValue(registryKey, L"LocalServer32", moduleName.get())) + return S_FALSE; + + // Add the ProgID subkey under the CLSID key. + if (!setKeyAndValue(registryKey, L"ProgID", szProgID)) return S_FALSE; + + // Add the version-independent ProgID subkey under CLSID key. + if (!setKeyAndValue(registryKey, L"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, L"CLSID", szCLSID)) return S_FALSE; + if (!setKeyAndValue(independentProgId, L"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, L"CLSID", szCLSID)) return S_FALSE; + + RegisterProxy(); + + return S_OK; +} + +LONG UnregisterServer(const CLSID& clsid, // Class ID + const WCHAR* szVerIndProgID, // Programmatic + const WCHAR* szProgID) // IDs +{ + LONG lResult = S_OK; + + // Convert the CLSID into a char. + + WCHAR szCLSID[CLSID_STRING_SIZE]; + if (!CLSIDtoWchar(clsid, szCLSID)) return S_FALSE; + + UnRegisterProxy(); + + nsAutoString registryKey(L"CLSID\\"); + registryKey += szCLSID; + + lResult = recursiveDeleteKey(HKEY_CLASSES_ROOT, registryKey.get()); + if (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND) + return lResult; + + registryKey += L"\\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/comm/mailnews/mapi/mapihook/src/Registry.h b/comm/mailnews/mapi/mapihook/src/Registry.h new file mode 100644 index 0000000000..7207694e55 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/Registry.h @@ -0,0 +1,20 @@ +/* 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 WCHAR* szFriendlyName, + const WCHAR* szVerIndProgID, const WCHAR* szProgID); + +// This function will unregister a component. + +HRESULT UnregisterServer(const CLSID& clsid, const WCHAR* szVerIndProgID, + const WCHAR* szProgID); + +#endif diff --git a/comm/mailnews/mapi/mapihook/src/components.conf b/comm/mailnews/mapi/mapihook/src/components.conf new file mode 100644 index 0000000000..d69a1df0d5 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/components.conf @@ -0,0 +1,18 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + "cid": "{8967fed2-c8bb-11d5-a3e9-00b0d0f3baa7}", + "contract_ids": ["@mozilla.org/mapisupport;1"], + "type": "nsMapiSupport", + "headers": ["/comm/mailnews/mapi/mapihook/src/msgMapiSupport.h"], + } +] + +Categories = { + "app-startup": { + "Mapi Support": "@mozilla.org/mapisupport;1", + } +} diff --git a/comm/mailnews/mapi/mapihook/src/moz.build b/comm/mailnews/mapi/mapihook/src/moz.build new file mode 100644 index 0000000000..f2ef529008 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/moz.build @@ -0,0 +1,33 @@ +# 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 += [ + "!../build/msgMapi_i.c", + "msgMapiFactory.cpp", + "msgMapiHook.cpp", + "msgMapiImp.cpp", + "msgMapiMain.cpp", + "msgMapiSupport.cpp", + "Registry.cpp", +] + +LOCAL_INCLUDES += ["/comm/mailnews/mapi/include"] + +FINAL_LIBRARY = "xul" + +OS_LIBS += [ + "ole32", +] + +DEFINES["UNICODE"] = True +DEFINES["_UNICODE"] = True + +# clang-cl rightly complains about switch on nsresult. +if CONFIG["CC_TYPE"] == "clang-cl": + CXXFLAGS += ["-Wno-switch"] + +XPCOM_MANIFESTS += [ + "components.conf", +] diff --git a/comm/mailnews/mapi/mapihook/src/msgMapiFactory.cpp b/comm/mailnews/mapi/mapihook/src/msgMapiFactory.cpp new file mode 100644 index 0000000000..cb99fc04fd --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiFactory.cpp @@ -0,0 +1,64 @@ +/* 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/comm/mailnews/mapi/mapihook/src/msgMapiFactory.h b/comm/mailnews/mapi/mapihook/src/msgMapiFactory.h new file mode 100644 index 0000000000..3970b3fa99 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiFactory.h @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef 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; + + virtual ~CMapiFactory(); +}; + +#endif // MSG_MAPI_FACTORY_H diff --git a/comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp b/comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp new file mode 100644 index 0000000000..eb8c9b9fe8 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp @@ -0,0 +1,934 @@ +/* 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 <direct.h> +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIPromptService.h" +#include "nsIAppShellService.h" +#include "mozIDOMWindow.h" +#include "nsIMsgAccountManager.h" +#include "nsIStringBundle.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsString.h" +#include "nsUnicharUtils.h" +#include "nsNativeCharsetUtils.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgCompFields.h" +#include "nsIMsgComposeParams.h" +#include "nsIMsgCompose.h" +#include "nsIMsgSend.h" +#include "nsIMsgComposeService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "msgMapi.h" +#include "msgMapiHook.h" +#include "msgMapiSupport.h" +#include "msgMapiMain.h" +#include "nsThreadUtils.h" +#include "nsMsgUtils.h" +#include "nsNetUtil.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Components.h" +#include "nsEmbedCID.h" +#include "mozilla/Logging.h" +#include "mozilla/SpinEventLoopUntil.h" + +using namespace mozilla::dom; + +extern mozilla::LazyLogModule MAPI; // defined in msgMapiImp.cpp + +class MAPISendListener : public nsIMsgSendListener, + public mozilla::ReentrantMonitor { + public: + MAPISendListener() + : ReentrantMonitor("MAPISendListener monitor"), m_done(false) {} + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */ + NS_IMETHOD OnStartSending(const char* aMsgID, uint32_t aMsgSize) { + return NS_OK; + } + + /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t + * aProgressMax); */ + NS_IMETHOD OnProgress(const char* aMsgID, uint32_t aProgress, + uint32_t aProgressMax) { + return NS_OK; + } + + /* void OnStatus (in string aMsgID, in wstring aMsg); */ + NS_IMETHOD OnStatus(const char* aMsgID, const char16_t* aMsg) { + return NS_OK; + } + + /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg, + * in nsIFile returnFile); */ + NS_IMETHOD OnStopSending(const char* aMsgID, nsresult aStatus, + const char16_t* aMsg, nsIFile* returnFile) { + mozilla::ReentrantMonitorAutoEnter mon(*this); + m_done = true; + NotifyAll(); + return NS_OK; + } + + /* void OnTransportSecurityError( in string msgID, in nsresult status, in + * nsITransportSecurityInfo secInfo, in ACString location); */ + NS_IMETHOD OnTransportSecurityError(const char* msgID, nsresult status, + nsITransportSecurityInfo* secInfo, + nsACString const& location) { + return NS_OK; + } + + /* void OnSendNotPerformed */ + NS_IMETHOD OnSendNotPerformed(const char* aMsgID, nsresult aStatus) { + return OnStopSending(aMsgID, aStatus, nullptr, nullptr); + } + + /* void OnGetDraftFolderURI (); */ + NS_IMETHOD OnGetDraftFolderURI(const char* aMsgID, + const nsACString& aFolderURI) { + return NS_OK; + } + + bool IsDone() { return m_done; } + + private: + bool m_done; + virtual ~MAPISendListener() {} +}; + +/// Helper for setting up the hidden window for blind MAPI. +class MOZ_STACK_CLASS AutoHiddenWindow { + public: + explicit AutoHiddenWindow(nsresult& rv) + : mAppService(do_GetService("@mozilla.org/appshell/appShellService;1")) { + mCreatedHiddenWindow = false; + rv = mAppService->GetHiddenDOMWindow(getter_AddRefs(mHiddenWindow)); + if (rv == NS_ERROR_FAILURE) { + // Try to get a hidden window. If it doesn't exist, create a hidden + // window for us to use. + rv = mAppService->CreateHiddenWindow(); + NS_ENSURE_SUCCESS_VOID(rv); + mCreatedHiddenWindow = true; + rv = mAppService->GetHiddenDOMWindow(getter_AddRefs(mHiddenWindow)); + } + NS_ENSURE_SUCCESS_VOID(rv); + } + ~AutoHiddenWindow() { + if (mCreatedHiddenWindow) mAppService->DestroyHiddenWindow(); + } + mozIDOMWindowProxy* operator->() { return mHiddenWindow; } + operator mozIDOMWindowProxy*() { return mHiddenWindow; } + + private: + nsCOMPtr<nsIAppShellService> mAppService; + nsCOMPtr<mozIDOMWindowProxy> mHiddenWindow; + bool mCreatedHiddenWindow; +}; + +NS_IMPL_ISUPPORTS(MAPISendListener, nsIMsgSendListener) + +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::components::StringBundle::Service(); + 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("brandFullName", brandName); + if (NS_FAILED(rv)) return false; + + nsString loginTitle; + AutoTArray<nsString, 1> brandStrings = {brandName}; + rv = bundle->FormatStringFromName("loginTitle", brandStrings, loginTitle); + if (NS_FAILED(rv)) return false; + + if (aLogin) { + nsString loginText; + rv = bundle->GetStringFromName("loginTextwithName", loginText); + if (NS_FAILED(rv) || loginText.IsEmpty()) return false; + + rv = dlgService->PromptUsernameAndPassword(nullptr, loginTitle.get(), + loginText.get(), aUsername, + aPassword, &btnResult); + } else { + // nsString loginString; + nsString loginText; + AutoTArray<nsString, 1> userNameStrings = {nsDependentString(*aUsername)}; + rv = + bundle->FormatStringFromName("loginText", userNameStrings, loginText); + if (NS_FAILED(rv)) return false; + + rv = dlgService->PromptPassword(nullptr, loginTitle.get(), + loginText.get(), aPassword, &btnResult); + } + } + + return btnResult; +} + +bool nsMapiHook::VerifyUserName(const nsCString& aUsername, nsCString& aIdKey) { + nsresult rv; + + if (aUsername.IsEmpty()) return false; + + nsCOMPtr<nsIMsgAccountManager> accountManager( + do_GetService("@mozilla.org/messenger/account-manager;1", &rv)); + if (NS_FAILED(rv)) return false; + nsTArray<RefPtr<nsIMsgIdentity>> identities; + rv = accountManager->GetAllIdentities(identities); + if (NS_FAILED(rv)) return false; + + for (auto thisIdentity : identities) { + if (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(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::components::StringBundle::Service(); + 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("mapiBlindSendWarning", warningMsg); + if (NS_FAILED(rv)) return false; + + nsString dontShowAgainMessage; + rv = bundle->GetStringFromName("mapiBlindSendDontShowAgain", + 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; + + // Get a hidden window to use for compose. + AutoHiddenWindow hiddenWindow(rv); + NS_ENSURE_SUCCESS(rv, rv); + + // smtp password and Logged in used IdKey from MapiConfig (session obj) + nsMAPIConfiguration* pMapiConfig = + nsMAPIConfiguration::GetMAPIConfiguration(); + if (!pMapiConfig) return NS_ERROR_FAILURE; // get the singleton obj + char16_t* password = pMapiConfig->GetPassword(aSession); + + // Id key + nsCString MsgIdKey; + pMapiConfig->GetIdKey(aSession, MsgIdKey); + + // get the MsgIdentity for the above key using AccountManager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1"); + 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 + RefPtr<MAPISendListener> sendListener = new MAPISendListener; + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &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); + if (password) pMsgComposeParams->SetSmtpPassword(nsDependentString(password)); + + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose( + do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = pMsgCompose->Initialize(pMsgComposeParams, hiddenWindow, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // If we're in offline mode, we'll need to queue it for later. + RefPtr<Promise> promise; + rv = pMsgCompose->SendMsg(WeAreOffline() ? nsIMsgSend::nsMsgQueueForLater + : nsIMsgSend::nsMsgDeliverNow, + pMsgId, nullptr, nullptr, nullptr, + getter_AddRefs(promise)); + NS_ENSURE_SUCCESS(rv, rv); + + // When offline, the message is saved to Outbox and OnStopSending won't be + // called. + if (WeAreOffline()) return NS_OK; + + // Wait for OnStopSending to be called. + mozilla::SpinEventLoopUntil("nsIMsgCompose::SendMsg is async"_ns, + [=]() { return sendListener->IsDone(); }); + + return rv; +} + +nsresult nsMapiHook::HandleAttachments(nsIMsgCompFields* aCompFields, + int32_t aFileCount, + lpnsMapiFileDesc aFiles, bool aIsUTF8) { + nsresult rv = NS_OK; + // Do nothing if there are no files to process. + if (!aFiles || aFileCount <= 0) return 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 (!aIsUTF8) + pFile->InitWithNativePath(nsDependentCString(aFiles[i].lpszPathName)); + else + pFile->InitWithPath(NS_ConvertUTF8toUTF16(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_NOT_FOUND; + + // 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(u"moz_mapi"_ns); + 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 (!aIsUTF8) + NS_CopyNativeToUnicode(nsDependentCString(aFiles[i].lpszFileName), + wholeFileName); + else + wholeFileName.Append(NS_ConvertUTF8toUTF16(aFiles[i].lpszFileName)); + // 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("@mozilla.org/messengercompose/attachment;1", &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 = %x\n", rv)); + } + } + return rv; +} + +nsresult nsMapiHook::HandleAttachmentsW(nsIMsgCompFields* aCompFields, + int32_t aFileCount, + lpnsMapiFileDescW aFiles) { + nsresult rv = NS_OK; + // Do nothing if there are no files to process. + if (!aFiles || aFileCount <= 0) return 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. + pFile->InitWithPath(nsDependentString(aFiles[i].lpszPathName)); + + bool bExist; + rv = pFile->Exists(&bExist); + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, + ("nsMapiHook::HandleAttachmentsW: filename: %s path: %s exists = " + "%s \n", + NS_ConvertUTF16toUTF8(aFiles[i].lpszFileName).get(), + NS_ConvertUTF16toUTF8(aFiles[i].lpszPathName).get(), + bExist ? "true" : "false")); + if (NS_FAILED(rv) || (!bExist)) return NS_ERROR_FILE_NOT_FOUND; + + // 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(u"moz_mapi"_ns); + 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; + // 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(aFiles[i].lpszFileName); + // 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("@mozilla.org/messengercompose/attachment;1", &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::HandleAttachmentsW: AddAttachment rv = %x\n", rv)); + } + } + return rv; +} + +// this is used to convert non Unicode data and then populate comp fields +nsresult nsMapiHook::PopulateCompFieldsWithConversion( + lpnsMapiMessage aMessage, nsIMsgCompFields* aCompFields) { + bool isUTF8 = aMessage->ulReserved == CP_UTF8; + + if (aMessage->lpOriginator && aMessage->lpOriginator->lpszAddress) { + nsAutoString From; + if (!isUTF8) + From.Append(NS_ConvertASCIItoUTF16(aMessage->lpOriginator->lpszAddress)); + else + From.Append(NS_ConvertUTF8toUTF16(aMessage->lpOriginator->lpszAddress)); + aCompFields->SetFrom(From); + } + + nsAutoString To; + nsAutoString Cc; + nsAutoString Bcc; + constexpr auto Comma = u","_ns; + 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; + if (!isUTF8) + To.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + else + To.Append(NS_ConvertUTF8toUTF16(addressWithoutType)); + break; + + case MAPI_CC: + if (!Cc.IsEmpty()) Cc += Comma; + if (!isUTF8) + Cc.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + else + Cc.Append(NS_ConvertUTF8toUTF16(addressWithoutType)); + break; + + case MAPI_BCC: + if (!Bcc.IsEmpty()) Bcc += Comma; + if (!isUTF8) + Bcc.Append(NS_ConvertASCIItoUTF16(addressWithoutType)); + else + Bcc.Append(NS_ConvertUTF8toUTF16(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())); + + // set subject + nsresult rv = NS_OK; + if (aMessage->lpszSubject) { + nsAutoString Subject; + if (!isUTF8) + rv = NS_CopyNativeToUnicode(nsDependentCString(aMessage->lpszSubject), + Subject); + else + Subject.Append(NS_ConvertUTF8toUTF16(aMessage->lpszSubject)); + if (NS_FAILED(rv)) return rv; + aCompFields->SetSubject(Subject); + } + + // handle attachments as File URL + rv = HandleAttachments(aCompFields, aMessage->nFileCount, aMessage->lpFiles, + isUTF8); + if (NS_FAILED(rv)) return rv; + + // set body + if (aMessage->lpszNoteText) { + nsAutoString Body; + if (!isUTF8) + rv = NS_CopyNativeToUnicode(nsDependentCString(aMessage->lpszNoteText), + Body); + else + Body.Append(NS_ConvertUTF8toUTF16(aMessage->lpszNoteText)); + 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(u"<html>") == kNotFound) aCompFields->SetForcePlainText(true); + + rv = aCompFields->SetBody(Body); + } else { + // No body: Assume that we can do plaintext. This will trigger the default + // compose format in ShowComposerWindow(). + aCompFields->SetForcePlainText(true); + } + +#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 comp fields with UTF-16 data from MAPISendMailW +// function. +nsresult nsMapiHook::PopulateCompFieldsW(lpnsMapiMessageW aMessage, + nsIMsgCompFields* aCompFields) { + if (aMessage->lpOriginator && aMessage->lpOriginator->lpszAddress) + aCompFields->SetFrom( + nsDependentString(aMessage->lpOriginator->lpszAddress)); + + nsAutoString To; + nsAutoString Cc; + nsAutoString Bcc; + + constexpr auto Comma = u","_ns; + + if (aMessage->lpRecips) { + for (int i = 0; i < (int)aMessage->nRecipCount; i++) { + if (aMessage->lpRecips[i].lpszAddress || aMessage->lpRecips[i].lpszName) { + const wchar_t* addressWithoutType = + (aMessage->lpRecips[i].lpszAddress) + ? aMessage->lpRecips[i].lpszAddress + : aMessage->lpRecips[i].lpszName; + if (_wcsnicmp(addressWithoutType, L"SMTP:", 5) == 0) + addressWithoutType += 5; + switch (aMessage->lpRecips[i].ulRecipClass) { + case MAPI_TO: + if (!To.IsEmpty()) To += Comma; + To.Append(nsDependentString(addressWithoutType)); + break; + + case MAPI_CC: + if (!Cc.IsEmpty()) Cc += Comma; + Cc.Append(nsDependentString(addressWithoutType)); + break; + + case MAPI_BCC: + if (!Bcc.IsEmpty()) Bcc += Comma; + Bcc.Append(nsDependentString(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(nsDependentString(aMessage->lpszSubject)); + + // handle attachments as File URL + nsresult rv = + HandleAttachmentsW(aCompFields, aMessage->nFileCount, aMessage->lpFiles); + if (NS_FAILED(rv)) return rv; + + // Set body. + if (aMessage->lpszNoteText) { + nsString Body(aMessage->lpszNoteText); + 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(u"<html>") == kNotFound) aCompFields->SetForcePlainText(true); + + rv = aCompFields->SetBody(Body); + } else { + // No body: Assume that we can do plaintext. This will trigger the default + // compose format in ShowComposerWindow(). + aCompFields->SetForcePlainText(true); + } + 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, LPSTR aDelimChar, + LPSTR aFilePaths) { + nsAutoCString strDelimChars; + nsAutoCString 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.Assign(';'); + + 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; + + char* newFilePaths = (char*)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(u"moz_mapi"_ns); + pTempDir->Exists(&bExist); + if (!bExist) { + rv = pTempDir->Create(nsIFile::DIRECTORY_TYPE, 777); + if (NS_FAILED(rv)) return rv; + } + + nsAutoCString 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)) { + nsAutoCString cwdStr; + cwdStr.Assign(cwd); + cwdStr.Append('\\'); + RemainingPaths.Insert(cwdStr, 0); + } + } + + pFile->InitWithNativePath(RemainingPaths); + + rv = pFile->Exists(&bExist); + if (NS_FAILED(rv) || (!bExist)) return NS_ERROR_FILE_NOT_FOUND; + + // 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("@mozilla.org/messengercompose/attachment;1", &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 + RefPtr<MAPISendListener> sendListener = new MAPISendListener; + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams( + do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &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("@mozilla.org/messengercompose;1"); + if (NS_FAILED(rv) || (!compService)) return rv; + + rv = compService->OpenComposeWindowWithParams(nullptr, pMsgComposeParams); + if (NS_FAILED(rv)) return rv; + + return rv; +} diff --git a/comm/mailnews/mapi/mapihook/src/msgMapiHook.h b/comm/mailnews/mapi/mapihook/src/msgMapiHook.h new file mode 100644 index 0000000000..aa6ad1d66f --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiHook.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_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 nsCString& aUsername, nsCString& aIdKey); + + static bool IsBlindSendAllowed(); + static nsresult BlindSendMail(unsigned long aSession, + nsIMsgCompFields* aCompFields); + static nsresult ShowComposerWindow(unsigned long aSession, + nsIMsgCompFields* aCompFields); + static nsresult PopulateCompFieldsWithConversion( + lpnsMapiMessage aMessage, nsIMsgCompFields* aCompFields); + static nsresult PopulateCompFieldsW(lpnsMapiMessageW aMessage, + nsIMsgCompFields* aCompFields); + static nsresult PopulateCompFieldsForSendDocs(nsIMsgCompFields* aCompFields, + ULONG aFlags, LPSTR aDelimChar, + LPSTR aFilePaths); + static nsresult HandleAttachments(nsIMsgCompFields* aCompFields, + int32_t aFileCount, lpnsMapiFileDesc aFiles, + bool aIsUTF8); + static nsresult HandleAttachmentsW(nsIMsgCompFields* aCompFields, + int32_t aFileCount, + lpnsMapiFileDescW aFiles); + static void CleanUp(); + + static bool isMapiService; +}; + +#endif // MSG_MAPI_HOOK_H_ diff --git a/comm/mailnews/mapi/mapihook/src/msgMapiImp.cpp b/comm/mailnews/mapi/mapihook/src/msgMapiImp.cpp new file mode 100644 index 0000000000..62abed1f7b --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiImp.cpp @@ -0,0 +1,801 @@ +/* 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 <winstring.h> +#include "msgMapiImp.h" +#include "msgMapiFactory.h" +#include "msgMapiMain.h" + +#include "nsIMsgCompFields.h" +#include "msgMapiHook.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIMsgDatabase.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgHdr.h" +#include "MailNewsTypes.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; + +mozilla::LazyLogModule MAPI("MAPI"); + +CMapiImp::CMapiImp() : m_cRef(1) { m_Lock = PR_NewLock(); } + +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, LPSTR aLogin, + LPSTR 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 %lu", aFlags)); + if (aFlags & MAPI_NEW_SESSION) bNewSession = true; + + // Check For Profile Name + if (aLogin != nullptr && aLogin[0] != '\0') { + if (!nsMapiHook::VerifyUserName(nsDependentCString(aLogin), id_key)) { + *aSessionId = MAPI_E_LOGIN_FAILURE; + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, + ("CMapiImp::Login failed for username %s", aLogin)); + NS_ASSERTION(false, "failed verifying user name"); + return hr; + } + } else { + // get default account + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, MAPI_E_LOGIN_FAILURE); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->GetDefaultAccount(getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, MAPI_E_LOGIN_FAILURE); + if (!account) return MAPI_E_LOGIN_FAILURE; + + nsCOMPtr<nsIMsgIdentity> identity; + 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, aLogin ? nsDependentCString(aLogin) : EmptyCString(), + aPassWord ? nsDependentCString(aPassWord) : EmptyCString(), + (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")); + break; + } + } + + return S_OK; +} + +STDMETHODIMP CMapiImp::SendMail(unsigned long aSession, + lpnsMapiMessage aMessage, unsigned long aFlags, + unsigned long aReserved) { + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, + ("CMapiImp::SendMail flags=%lx subject: %s sender: %s", aFlags, + (aMessage && aMessage->lpszSubject) ? aMessage->lpszSubject + : "(no subject)", + (aMessage && aMessage->lpOriginator && + aMessage->lpOriginator->lpszAddress) + ? aMessage->lpOriginator->lpszAddress + : "(no sender)")); + + /** create nsIMsgCompFields obj and populate it **/ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgCompFields> pCompFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + if (NS_FAILED(rv) || (!pCompFields)) return MAPI_E_INSUFFICIENT_MEMORY; + + if (aMessage) + 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::SendMailW(unsigned long aSession, + lpnsMapiMessageW aMessage, + unsigned long aFlags, + unsigned long aReserved) { + MOZ_LOG( + MAPI, mozilla::LogLevel::Debug, + ("CMapiImp::SendMailW flags=%lx subject: %s sender: %s", aFlags, + (aMessage && aMessage->lpszSubject) + ? NS_ConvertUTF16toUTF8(aMessage->lpszSubject).get() + : "(no subject)", + (aMessage && aMessage->lpOriginator && + aMessage->lpOriginator->lpszAddress) + ? NS_ConvertUTF16toUTF8(aMessage->lpOriginator->lpszAddress).get() + : "(no sender)")); + + // Create nsIMsgCompFields obj and populate it. + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgCompFields> pCompFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv); + if (NS_FAILED(rv) || !pCompFields) return MAPI_E_INSUFFICIENT_MEMORY; + + if (aMessage) rv = nsMapiHook::PopulateCompFieldsW(aMessage, pCompFields); + + if (NS_SUCCEEDED(rv)) { + // Check 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, LPSTR aDelimChar, + LPSTR aFilePaths, LPSTR aFileNames, + ULONG aFlags) { + nsresult rv = NS_OK; + + MOZ_LOG(MAPI, mozilla::LogLevel::Debug, + ("CMapiImp::SendDocument using flags %lu", aFlags)); + /** create nsIMsgCompFields obj and populate it **/ + nsCOMPtr<nsIMsgCompFields> pCompFields = + do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &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 = %x, paths = %s names = %s", rv, + aFilePaths, aFileNames)); + + return nsMAPIConfiguration::GetMAPIErrorFromNSError(rv); +} + +nsresult CMapiImp::GetDefaultInbox(nsIMsgFolder** inboxFolder) { + // get default account + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->GetDefaultAccount(getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + if (!account) return NS_ERROR_FAILURE; + + // 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<nsIMsgEnumerator> m_msgEnumerator; +}; + +LONG CMapiImp::InitContext(unsigned long session, + MsgMapiListContext** listContext) { + nsMAPIConfiguration* pMapiConfig = + nsMAPIConfiguration::GetMAPIConfiguration(); + if (!pMapiConfig) return MAPI_E_FAILURE; // get the singleton 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, + LPSTR lpszMessageType, LPSTR 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 singleton 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", (char*)lpszMessageID)); + return (SUCCESS_SUCCESS); +} + +STDMETHODIMP CMapiImp::ReadMail(unsigned long aSession, unsigned long ulUIParam, + LPSTR 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", (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, LPSTR 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, + LPSTR 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<nsIMsgDBHdr> msgHdr; + if (NS_SUCCEEDED(m_msgEnumerator->GetNext(getter_AddRefs(msgHdr))) && + msgHdr) { + 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; + + m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr)); + if (msgHdr) { + msgHdr->GetSubject(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=%lu subject %s date %s " + "sender %s", + 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<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 + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsILineInputStream> fileLineStream = do_QueryInterface(fileStream); + 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(fileStream); + seekableStream->Seek(PR_SEEK_SET, messageOffset); + bool hasMore = true; + nsAutoCString curLine; + + 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", + bytesCopied, msgSize + 1, (char*)body)); + body[bytesCopied] = '\0'; // rhp - fix last line garbage... + return body; +} + +//***************************************************************************** +// 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()) { + nsTArray<nsMsgKey> doomed({key}); + return NS_SUCCEEDED((m_db->DeleteMessages(doomed, 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/comm/mailnews/mapi/mapihook/src/msgMapiImp.h b/comm/mailnews/mapi/mapihook/src/msgMapiImp.h new file mode 100644 index 0000000000..b83f0566a0 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiImp.h @@ -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/. */ + +#ifndef MSG_MAPI_IMP_H +#define MSG_MAPI_IMP_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, LPSTR aLogin, LPSTR aPassWord, + unsigned long aFlags, unsigned long* aSessionId); + + STDMETHODIMP SendMail(unsigned long aSession, lpnsMapiMessage aMessage, + unsigned long aFlags, unsigned long aReserved); + + STDMETHODIMP SendDocuments(unsigned long aSession, LPSTR aDelimChar, + LPSTR aFilePaths, LPSTR aFileNames, ULONG aFlags); + + STDMETHODIMP FindNext(unsigned long aSession, unsigned long ulUIParam, + LPSTR lpszMessageType, LPSTR lpszSeedMessageID, + unsigned long flFlags, unsigned long ulReserved, + unsigned char lpszMessageID[64]); + + STDMETHODIMP ReadMail(unsigned long lhSession, unsigned long ulUIParam, + LPSTR lpszMessageID, unsigned long flFlags, + unsigned long ulReserved, lpnsMapiMessage* lppMessage); + STDMETHODIMP DeleteMail(unsigned long lhSession, unsigned long ulUIParam, + LPSTR lpszMessageID, unsigned long flFlags, + unsigned long ulReserved); + STDMETHODIMP SaveMail(unsigned long lhSession, unsigned long ulUIParam, + lpnsMapiMessage lppMessage, unsigned long flFlags, + unsigned long ulReserved, LPSTR lpszMessageID); + + STDMETHODIMP Initialize(); + STDMETHODIMP IsValid(); + STDMETHODIMP IsValidSession(unsigned long aSession); + + STDMETHODIMP SendMailW(unsigned long aSession, lpnsMapiMessageW aMessage, + unsigned long aFlags, unsigned long aReserved); + + STDMETHODIMP Logoff(unsigned long aSession); + STDMETHODIMP CleanUp(); + + CMapiImp(); + virtual ~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/comm/mailnews/mapi/mapihook/src/msgMapiMain.cpp b/comm/mailnews/mapi/mapihook/src/msgMapiMain.cpp new file mode 100644 index 0000000000..0e8d4cacac --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiMain.cpp @@ -0,0 +1,248 @@ +/* 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 "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 nsCString& aUserName, const nsCString& 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.IsEmpty()) n_SessionId = m_ProfileMap.Get(aUserName); + + // 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 + 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.InsertOrUpdate( + session_generator, + mozilla::MakeUnique<nsMAPISession>(aHwnd, aUserName, aPassword, + aForceDownLoad, aIdKey)); + if (!aUserName.IsEmpty()) + m_ProfileMap.InsertOrUpdate(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: + case NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY: + // Something went wrong with the sender. There's no error we can map to + // so we use a general error, see: + // https://msdn.microsoft.com/en-us/library/hh802867(v=vs.85).aspx + hr = MAPI_E_FAILURE; + 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_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_NOT_FOUND: + hr = MAPI_E_ATTACHMENT_NOT_FOUND; + 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 nsCString& aUserName, + const nsCString& aPassword, bool aForceDownLoad, + const char* aKey) + : m_nShared(1), m_pIdKey(aKey) { + m_listContext = NULL; + m_pProfileName = aUserName; + m_pPassword = 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/comm/mailnews/mapi/mapihook/src/msgMapiMain.h b/comm/mailnews/mapi/mapihook/src/msgMapiMain.h new file mode 100644 index 0000000000..fcca6ca541 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiMain.h @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MSG_MAPI_MAIN_H_ +#define MSG_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 "nsTHashMap.h" +#include "nsClassHashtable.h" +#include "nsString.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; + + nsTHashMap<nsCStringHashKey, uint32_t> m_ProfileMap; + nsClassHashtable<nsUint32HashKey, nsMAPISession> m_SessionMap; + nsMAPIConfiguration(); + ~nsMAPIConfiguration(); + + public: + static nsMAPIConfiguration* GetMAPIConfiguration(); + void OpenConfiguration(); + int16_t RegisterSession(uint32_t aHwnd, const nsCString& aUserName, + const nsCString& 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: + uint32_t m_nShared; + nsCString m_pIdKey; + nsCString m_pProfileName; + nsCString m_pPassword; + void* m_listContext; // used by findNext + + public: + nsMAPISession(uint32_t aHwnd, const nsCString& aUserName, + const nsCString& aPassword, bool aForceDownLoad, + const char* aKey); + uint32_t IncrementSession(); + uint32_t DecrementSession(); + uint32_t GetSessionCount(); + char16_t* 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/comm/mailnews/mapi/mapihook/src/msgMapiSupport.cpp b/comm/mailnews/mapi/mapihook/src/msgMapiSupport.cpp new file mode 100644 index 0000000000..9fd1655526 --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiSupport.cpp @@ -0,0 +1,101 @@ +/* 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 "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 propagation to pass back + ::RegisterServer(CLSID_CMapiImp, L"Mozilla MAPI", L"MozillaMapi", + L"MozillaMapi.1"); + return NS_OK; +} + +NS_IMETHODIMP +nsMapiSupport::UnRegisterServer() { + // TODO: Figure out what kind of error propagation to pass back + ::UnregisterServer(CLSID_CMapiImp, L"MozillaMapi", L"MozillaMapi.1"); + return NS_OK; +} diff --git a/comm/mailnews/mapi/mapihook/src/msgMapiSupport.h b/comm/mailnews/mapi/mapihook/src/msgMapiSupport.h new file mode 100644 index 0000000000..af17ea637c --- /dev/null +++ b/comm/mailnews/mapi/mapihook/src/msgMapiSupport.h @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef 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: + virtual ~nsMapiSupport(); + + DWORD m_dwRegister; + CMapiFactory* m_nsMapiFactory; +}; + +#endif // MSG_MAPI_SUPPORT_H_ |