summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mapi/mapihook/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/mapi/mapihook/src
parentInitial commit. (diff)
downloadthunderbird-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.cpp249
-rw-r--r--comm/mailnews/mapi/mapihook/src/Registry.h20
-rw-r--r--comm/mailnews/mapi/mapihook/src/components.conf18
-rw-r--r--comm/mailnews/mapi/mapihook/src/moz.build33
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiFactory.cpp64
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiFactory.h35
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiHook.cpp934
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiHook.h39
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiImp.cpp801
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiImp.h79
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiMain.cpp248
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiMain.h80
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiSupport.cpp101
-rw-r--r--comm/mailnews/mapi/mapihook/src/msgMapiSupport.h35
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_