/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsWindowsShellService.h" #include "nsIServiceManager.h" #include "nsICategoryManager.h" #include "nsNativeCharsetUtils.h" #include "nsIPrefService.h" #include "windows.h" #include "shellapi.h" #include "nsIFile.h" #include "nsDirectoryServiceDefs.h" #include "nsUnicharUtils.h" #include "nsComponentManagerUtils.h" #include "nsServiceManagerUtils.h" #include "nsIProperties.h" #include "nsString.h" #ifdef _WIN32_WINNT # undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0600 #define INITGUID #include #include #ifndef MAX_BUF # define MAX_BUF 4096 #endif #define REG_FAILED(val) (val != ERROR_SUCCESS) NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIShellService, nsIToolkitShellService) static nsresult OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName, HKEY* aKey) { const nsString& flatName = PromiseFlatString(aKeyName); DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey); switch (res) { case ERROR_SUCCESS: break; case ERROR_ACCESS_DENIED: return NS_ERROR_FILE_ACCESS_DENIED; case ERROR_FILE_NOT_FOUND: return NS_ERROR_NOT_AVAILABLE; } return NS_OK; } /////////////////////////////////////////////////////////////////////////////// // Default Mail Registry Settings /////////////////////////////////////////////////////////////////////////////// typedef enum { NO_SUBSTITUTION = 0x00, APP_PATH_SUBSTITUTION = 0x01 } SettingFlags; // APP_REG_NAME_MAIL and APP_REG_NAME_NEWS should be kept in synch with // AppRegNameMail and AppRegNameNews in the installer file: defines.nsi.in #define APP_REG_NAME_MAIL L"Thunderbird" #define APP_REG_NAME_NEWS L"Thunderbird (News)" #define APP_REG_NAME_CALENDAR L"Thunderbird (Calendar)" #define CLS_EML "ThunderbirdEML" #define CLS_MAILTOURL "Thunderbird.Url.mailto" #define CLS_MIDURL "Thunderbird.Url.mid" #define CLS_NEWSURL "Thunderbird.Url.news" #define CLS_FEEDURL "Thunderbird.Url.feed" #define CLS_WEBCALURL "Thunderbird.Url.webcal" #define CLS_ICS "ThunderbirdICS" #define SOP "\\shell\\open\\command" #define VAL_OPEN "\"%APPPATH%\" \"%1\"" #define VAL_MAIL_OPEN "\"%APPPATH%\" -osint -mail \"%1\"" #define VAL_COMPOSE_OPEN "\"%APPPATH%\" -osint -compose \"%1\"" #define MAKE_KEY_NAME1(PREFIX, MID) PREFIX MID static SETTING gMailSettings[] = { // File Extension Class {".eml", "", CLS_EML, NO_SUBSTITUTION}, // File Extension Class {MAKE_KEY_NAME1(CLS_EML, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, // Protocol Handler Class - for Vista and above {MAKE_KEY_NAME1(CLS_MAILTOURL, SOP), "", VAL_COMPOSE_OPEN, APP_PATH_SUBSTITUTION}, {MAKE_KEY_NAME1(CLS_MIDURL, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, // Protocol Handlers {MAKE_KEY_NAME1("mailto", SOP), "", VAL_COMPOSE_OPEN, APP_PATH_SUBSTITUTION}, {MAKE_KEY_NAME1("mid", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, }; static SETTING gNewsSettings[] = { // Protocol Handler Class - for Vista and above {MAKE_KEY_NAME1(CLS_NEWSURL, SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION}, // Protocol Handlers {MAKE_KEY_NAME1("news", SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION}, {MAKE_KEY_NAME1("nntp", SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION}, }; static SETTING gCalendarSettings[] = { // File Extension Class {".ics", "", CLS_ICS, NO_SUBSTITUTION}, // File Extension Class {MAKE_KEY_NAME1(CLS_ICS, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, // Protocol Handlers {MAKE_KEY_NAME1(CLS_WEBCALURL, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, {MAKE_KEY_NAME1("webcal", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, {MAKE_KEY_NAME1("webcals", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION}, }; nsresult GetHelperPath(nsAutoString& aPath) { nsresult rv; nsCOMPtr directoryService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr appHelper; rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(appHelper)); NS_ENSURE_SUCCESS(rv, rv); rv = appHelper->Append(u"uninstall"_ns); NS_ENSURE_SUCCESS(rv, rv); rv = appHelper->Append(u"helper.exe"_ns); NS_ENSURE_SUCCESS(rv, rv); return appHelper->GetPath(aPath); } nsresult LaunchHelper(nsAutoString& aPath, nsAutoString& aParams) { SHELLEXECUTEINFOW executeInfo = {0}; executeInfo.cbSize = sizeof(SHELLEXECUTEINFOW); executeInfo.hwnd = NULL; executeInfo.fMask = SEE_MASK_NOCLOSEPROCESS; executeInfo.lpDirectory = NULL; executeInfo.lpFile = aPath.get(); executeInfo.lpParameters = aParams.get(); executeInfo.nShow = SW_SHOWNORMAL; if (ShellExecuteExW(&executeInfo)) // Block until the program exits WaitForSingleObject(executeInfo.hProcess, INFINITE); else return NS_ERROR_ABORT; // We're going to ignore errors here since there's nothing we can do about // them, and helper.exe seems to return non-zero ret on success. return NS_OK; } nsresult nsWindowsShellService::Init() { WCHAR appPath[MAX_BUF]; if (!::GetModuleFileNameW(0, appPath, MAX_BUF)) return NS_ERROR_FAILURE; // Convert the path to a long path since GetModuleFileNameW returns the path // that was used to launch the app which is not necessarily a long path. if (!::GetLongPathNameW(appPath, appPath, MAX_BUF)) return NS_ERROR_FAILURE; mAppLongPath = appPath; return NS_OK; } nsWindowsShellService::nsWindowsShellService() : mCheckedThisSession(false) {} NS_IMETHODIMP nsWindowsShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps, bool* aIsDefaultClient) { // If this is the first mail window, maintain internal state that we've // checked this session (so that subsequent window opens don't show the // default client dialog). if (aStartupCheck) mCheckedThisSession = true; *aIsDefaultClient = true; // for each type, if (aApps & nsIShellService::MAIL) { *aIsDefaultClient &= TestForDefault(gMailSettings, sizeof(gMailSettings) / sizeof(SETTING)); // Only check if this app is default on Vista if the previous checks // indicate that this app is the default. if (*aIsDefaultClient) IsDefaultClientVista(nsIShellService::MAIL, aIsDefaultClient); } if (aApps & nsIShellService::NEWS) { *aIsDefaultClient &= TestForDefault(gNewsSettings, sizeof(gNewsSettings) / sizeof(SETTING)); // Only check if this app is default on Vista if the previous checks // indicate that this app is the default. if (*aIsDefaultClient) IsDefaultClientVista(nsIShellService::NEWS, aIsDefaultClient); } if (aApps & nsIShellService::CALENDAR) { *aIsDefaultClient &= TestForDefault( gCalendarSettings, sizeof(gCalendarSettings) / sizeof(SETTING)); // Only check if this app is default on Vista if the previous checks // indicate that this app is the default. if (*aIsDefaultClient) IsDefaultClientVista(nsIShellService::CALENDAR, aIsDefaultClient); } // RSS / feed protocol shell integration is not working so return true // until it is fixed (bug 445823). if (aApps & nsIShellService::RSS) *aIsDefaultClient &= true; return NS_OK; } NS_IMETHODIMP nsWindowsShellService::SetDefaultClient(bool aForAllUsers, uint16_t aApps) { nsAutoString appHelperPath; if (NS_FAILED(GetHelperPath(appHelperPath))) return NS_ERROR_FAILURE; nsAutoString params; if (aForAllUsers) { params.AppendLiteral(" /SetAsDefaultAppGlobal"); } else { params.AppendLiteral(" /SetAsDefaultAppUser"); if (aApps & nsIShellService::MAIL) params.AppendLiteral(" Mail"); if (aApps & nsIShellService::NEWS) params.AppendLiteral(" News"); if (aApps & nsIShellService::CALENDAR) params.AppendLiteral(" Calendar"); } return LaunchHelper(appHelperPath, params); } NS_IMETHODIMP nsWindowsShellService::GetShouldCheckDefaultClient(bool* aResult) { if (mCheckedThisSession) { *aResult = false; return NS_OK; } nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); return prefs->GetBoolPref("mail.shell.checkDefaultClient", aResult); } NS_IMETHODIMP nsWindowsShellService::SetShouldCheckDefaultClient(bool aShouldCheck) { nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); return prefs->SetBoolPref("mail.shell.checkDefaultClient", aShouldCheck); } /* helper routine. Iterate over the passed in settings object. */ bool nsWindowsShellService::TestForDefault(SETTING aSettings[], int32_t aSize) { bool isDefault = true; char16_t currValue[MAX_BUF]; SETTING* end = aSettings + aSize; for (SETTING* settings = aSettings; settings < end; ++settings) { NS_ConvertUTF8toUTF16 dataLongPath(settings->valueData); NS_ConvertUTF8toUTF16 key(settings->keyName); NS_ConvertUTF8toUTF16 value(settings->valueName); if (settings->flags & APP_PATH_SUBSTITUTION) { int32_t offset = dataLongPath.Find("%APPPATH%"); dataLongPath.Replace(offset, 9, mAppLongPath); } ::ZeroMemory(currValue, sizeof(currValue)); HKEY theKey; nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, key, &theKey); if (NS_FAILED(rv)) { // Key doesn't exist isDefault = false; break; } DWORD len = sizeof currValue; DWORD result = ::RegQueryValueExW(theKey, value.get(), NULL, NULL, (LPBYTE)currValue, &len); // Close the key we opened. ::RegCloseKey(theKey); if (REG_FAILED(result) || !dataLongPath.Equals(currValue, nsCaseInsensitiveStringComparator)) { // Key wasn't set, or was set to something else (something else became the // default client) isDefault = false; break; } } // for each registry key we want to look at return isDefault; } bool nsWindowsShellService::IsDefaultClientVista(uint16_t aApps, bool* aIsDefaultClient) { IApplicationAssociationRegistration* pAAR; HRESULT hr = CoCreateInstance( CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC, IID_IApplicationAssociationRegistration, (void**)&pAAR); if (SUCCEEDED(hr)) { BOOL isDefaultMail = true; BOOL isDefaultNews = true; BOOL isDefaultCalendar = true; if (aApps & nsIShellService::MAIL) pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_MAIL, &isDefaultMail); if (aApps & nsIShellService::NEWS) pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_NEWS, &isDefaultNews); if (aApps & nsIShellService::CALENDAR) pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_CALENDAR, &isDefaultCalendar); *aIsDefaultClient = isDefaultNews && isDefaultMail && isDefaultCalendar; pAAR->Release(); return true; } return false; }