diff options
Diffstat (limited to '')
-rw-r--r-- | uriloader/exthandler/win/nsMIMEInfoWin.cpp | 913 | ||||
-rw-r--r-- | uriloader/exthandler/win/nsMIMEInfoWin.h | 79 | ||||
-rw-r--r-- | uriloader/exthandler/win/nsOSHelperAppService.cpp | 612 | ||||
-rw-r--r-- | uriloader/exthandler/win/nsOSHelperAppService.h | 81 |
4 files changed, 1685 insertions, 0 deletions
diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.cpp b/uriloader/exthandler/win/nsMIMEInfoWin.cpp new file mode 100644 index 0000000000..758b2018a7 --- /dev/null +++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp @@ -0,0 +1,913 @@ +/* -*- Mode: C++; tab-width: 3; 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 "nsArrayEnumerator.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMArray.h" +#include "nsLocalFile.h" +#include "nsMIMEInfoWin.h" +#include "nsIMIMEService.h" +#include "nsNetUtil.h" +#include <windows.h> +#include <shellapi.h> +#include "nsIMutableArray.h" +#include "nsTArray.h" +#include <shlobj.h> +#include "nsIWindowsRegKey.h" +#include "nsUnicharUtils.h" +#include "nsTextToSubURI.h" +#include "nsVariant.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/ShellHeaderOnlyUtils.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/UrlmonHeaderOnlyUtils.h" +#include "mozilla/UniquePtrExtensions.h" + +#define RUNDLL32_EXE L"\\rundll32.exe" + +NS_IMPL_ISUPPORTS_INHERITED(nsMIMEInfoWin, nsMIMEInfoBase, nsIPropertyBag) + +nsMIMEInfoWin::~nsMIMEInfoWin() {} + +nsresult nsMIMEInfoWin::LaunchDefaultWithFile(nsIFile* aFile) { + // Launch the file, unless it is an executable. + bool executable = true; + aFile->IsExecutable(&executable); + if (executable) return NS_ERROR_FAILURE; + + return aFile->Launch(); +} + +nsresult nsMIMEInfoWin::ShellExecuteWithIFile(nsIFile* aExecutable, int aArgc, + const wchar_t** aArgv) { + nsresult rv; + + NS_ASSERTION(aArgc >= 1, "aArgc must be at least 1"); + + nsAutoString execPath; + rv = aExecutable->GetTarget(execPath); + if (NS_FAILED(rv) || execPath.IsEmpty()) { + rv = aExecutable->GetPath(execPath); + } + if (NS_FAILED(rv)) { + return rv; + } + + auto assembledArgs = mozilla::MakeCommandLine(aArgc, aArgv); + if (!assembledArgs) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + _bstr_t execPathBStr(execPath.get()); + // Pass VT_ERROR/DISP_E_PARAMNOTFOUND to omit an optional RPC parameter + // to execute a file with the default verb. + _variant_t verbDefault(DISP_E_PARAMNOTFOUND, VT_ERROR); + _variant_t workingDir; + _variant_t showCmd(SW_SHOWNORMAL); + + // Ask Explorer to ShellExecute on our behalf, as some applications such as + // Skype for Business do not start correctly when inheriting our process's + // migitation policies. + // It does not work in a special environment such as Citrix. In such a case + // we fall back to launching an application as a child process. We need to + // find a way to handle the combination of these interop issues. + mozilla::LauncherVoidResult shellExecuteOk = mozilla::ShellExecuteByExplorer( + execPathBStr, assembledArgs.get(), verbDefault, workingDir, showCmd); + if (shellExecuteOk.isErr()) { + // No need to pass assembledArgs to LaunchWithIProcess. aArgv will be + // processed in nsProcess::RunProcess. + return LaunchWithIProcess(aExecutable, aArgc, + reinterpret_cast<const char16_t**>(aArgv)); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInfoWin::LaunchWithFile(nsIFile* aFile) { + nsresult rv; + + // it doesn't make any sense to call this on protocol handlers + NS_ASSERTION(mClass == eMIMEInfo, + "nsMIMEInfoBase should have mClass == eMIMEInfo"); + + if (AutomationOnlyCheckIfLaunchStubbed(aFile)) { + return NS_OK; + } + + if (mPreferredAction == useSystemDefault) { + nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication(); + if (defaultApp && + mozilla::StaticPrefs::browser_pdf_launchDefaultEdgeAsApp()) { + // Since Edgium is the default handler for PDF and other kinds of files, + // if we're using the OS default and it's Edgium prefer its app mode so it + // operates as a viewer (without browser toolbars). Bug 1632277. + nsAutoCString defaultAppExecutable; + rv = defaultApp->GetNativeLeafName(defaultAppExecutable); + if (NS_SUCCEEDED(rv) && + defaultAppExecutable.LowerCaseEqualsLiteral("msedge.exe")) { + nsAutoString path; + rv = aFile->GetPath(path); + if (NS_SUCCEEDED(rv)) { + // If the --app flag doesn't work we'll want to fallback to a + // regular path. Send two args so we call `msedge.exe --app={path} + // {path}`. + nsAutoString appArg; + appArg.AppendLiteral("--app="); + appArg.Append(path); + const wchar_t* argv[] = {appArg.get(), path.get()}; + + return ShellExecuteWithIFile(defaultApp, mozilla::ArrayLength(argv), + argv); + } + } + } + return LaunchDefaultWithFile(aFile); + } + + if (mPreferredAction == useHelperApp) { + if (!mPreferredApplication) return NS_ERROR_FILE_NOT_FOUND; + + // at the moment, we only know how to hand files off to local handlers + nsCOMPtr<nsILocalHandlerApp> localHandler = + do_QueryInterface(mPreferredApplication, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> executable; + rv = localHandler->GetExecutable(getter_AddRefs(executable)); + NS_ENSURE_SUCCESS(rv, rv); + + // Deal with local dll based handlers + nsCString filename; + executable->GetNativeLeafName(filename); + if (filename.Length() > 4) { + nsCString extension(Substring(filename, filename.Length() - 4, 4)); + + if (extension.LowerCaseEqualsLiteral(".dll")) { + nsAutoString args; + + // executable is rundll32, everything else is a list of parameters, + // including the dll handler. + if (!GetDllLaunchInfo(executable, aFile, args, false)) + return NS_ERROR_INVALID_ARG; + + WCHAR rundll32Path[MAX_PATH + sizeof(RUNDLL32_EXE) / sizeof(WCHAR) + + 1] = {L'\0'}; + if (!GetSystemDirectoryW(rundll32Path, MAX_PATH)) { + return NS_ERROR_FILE_NOT_FOUND; + } + lstrcatW(rundll32Path, RUNDLL32_EXE); + + SHELLEXECUTEINFOW seinfo; + memset(&seinfo, 0, sizeof(seinfo)); + seinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + seinfo.fMask = 0; + seinfo.hwnd = nullptr; + seinfo.lpVerb = nullptr; + seinfo.lpFile = rundll32Path; + seinfo.lpParameters = args.get(); + seinfo.lpDirectory = nullptr; + seinfo.nShow = SW_SHOWNORMAL; + if (ShellExecuteExW(&seinfo)) return NS_OK; + + switch ((LONG_PTR)seinfo.hInstApp) { + case 0: + case SE_ERR_OOM: + return NS_ERROR_OUT_OF_MEMORY; + case SE_ERR_ACCESSDENIED: + return NS_ERROR_FILE_ACCESS_DENIED; + case SE_ERR_ASSOCINCOMPLETE: + case SE_ERR_NOASSOC: + return NS_ERROR_UNEXPECTED; + case SE_ERR_DDEBUSY: + case SE_ERR_DDEFAIL: + case SE_ERR_DDETIMEOUT: + return NS_ERROR_NOT_AVAILABLE; + case SE_ERR_DLLNOTFOUND: + return NS_ERROR_FAILURE; + case SE_ERR_SHARE: + return NS_ERROR_FILE_IS_LOCKED; + default: + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + return NS_ERROR_FILE_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + case ERROR_BAD_FORMAT: + return NS_ERROR_FILE_CORRUPTED; + } + } + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } + nsAutoString path; + aFile->GetPath(path); + const wchar_t* argv[] = {path.get()}; + return ShellExecuteWithIFile(executable, mozilla::ArrayLength(argv), argv); + } + + return NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP +nsMIMEInfoWin::GetHasDefaultHandler(bool* _retval) { + // We have a default application if we have a description + // We can ShellExecute anything; however, callers are probably interested if + // there is really an application associated with this type of file + *_retval = !mDefaultAppDescription.IsEmpty(); + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInfoWin::GetEnumerator(nsISimpleEnumerator** _retval) { + nsCOMArray<nsIVariant> properties; + + nsCOMPtr<nsIVariant> variant; + GetProperty(u"defaultApplicationIconURL"_ns, getter_AddRefs(variant)); + if (variant) properties.AppendObject(variant); + + GetProperty(u"customApplicationIconURL"_ns, getter_AddRefs(variant)); + if (variant) properties.AppendObject(variant); + + return NS_NewArrayEnumerator(_retval, properties, NS_GET_IID(nsIVariant)); +} + +static nsresult GetIconURLVariant(nsIFile* aApplication, nsIVariant** _retval) { + nsAutoCString fileURLSpec; + NS_GetURLSpecFromFile(aApplication, fileURLSpec); + nsAutoCString iconURLSpec; + iconURLSpec.AssignLiteral("moz-icon://"); + iconURLSpec += fileURLSpec; + RefPtr<nsVariant> writable(new nsVariant()); + writable->SetAsAUTF8String(iconURLSpec); + writable.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInfoWin::GetProperty(const nsAString& aName, nsIVariant** _retval) { + nsresult rv; + nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication(); + if (defaultApp && aName.EqualsLiteral(PROPERTY_DEFAULT_APP_ICON_URL)) { + rv = GetIconURLVariant(defaultApp, _retval); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mPreferredApplication && + aName.EqualsLiteral(PROPERTY_CUSTOM_APP_ICON_URL)) { + nsCOMPtr<nsILocalHandlerApp> localHandler = + do_QueryInterface(mPreferredApplication, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> executable; + rv = localHandler->GetExecutable(getter_AddRefs(executable)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetIconURLVariant(executable, _retval); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// this implementation was pretty much copied verbatime from +// Tony Robinson's code in nsExternalProtocolWin.cpp +nsresult nsMIMEInfoWin::LoadUriInternal(nsIURI* aURL) { + nsresult rv = NS_OK; + + // 1. Find the default app for this protocol + // 2. Set up the command line + // 3. Launch the app. + + // For now, we'll just cheat essentially, check for the command line + // then just call ShellExecute()! + + if (aURL) { + // extract the url spec from the url + nsAutoCString urlSpec; + aURL->GetAsciiSpec(urlSpec); + + // Unescape non-ASCII characters in the URL + nsAutoString utf16Spec; + if (NS_FAILED(nsTextToSubURI::UnEscapeNonAsciiURI("UTF-8"_ns, urlSpec, + utf16Spec))) { + CopyASCIItoUTF16(urlSpec, utf16Spec); + } + + // Ask the shell/urlmon to parse |utf16Spec| to avoid malformed URLs. + // Failure is indicative of a potential security issue so we should + // bail out if so. + mozilla::LauncherResult<_bstr_t> validatedUri = + mozilla::UrlmonValidateUri(utf16Spec.get()); + if (validatedUri.isErr()) { + return NS_ERROR_FAILURE; + } + + _variant_t args; + _variant_t verb(L"open"); + _variant_t workingDir; + _variant_t showCmd(SW_SHOWNORMAL); + + // To open a uri, we first try ShellExecuteByExplorer, which starts a new + // process as a child process of explorer.exe, because applications may not + // support the mitigation policies inherited from our process. If it fails, + // we fall back to ShellExecuteExW. + // + // For Thunderbird, however, there is a known issue that + // ShellExecuteByExplorer succeeds but explorer.exe shows an error popup + // if a uri to open includes credentials. This does not happen in Firefox + // because Firefox does not have to launch a process to open a uri. + // + // Since Thunderbird does not use mitigation policies which could cause + // compatibility issues, we get no benefit from using + // ShellExecuteByExplorer. Thus we skip it and go straight to + // ShellExecuteExW for Thunderbird. +#ifndef MOZ_THUNDERBIRD + mozilla::LauncherVoidResult shellExecuteOk = + mozilla::ShellExecuteByExplorer(validatedUri.inspect(), args, verb, + workingDir, showCmd); + if (shellExecuteOk.isOk()) { + return NS_OK; + } +#endif // MOZ_THUNDERBIRD + + SHELLEXECUTEINFOW sinfo = {sizeof(sinfo)}; + sinfo.fMask = SEE_MASK_NOASYNC; + sinfo.lpVerb = V_BSTR(&verb); + sinfo.nShow = showCmd; + sinfo.lpFile = validatedUri.inspect(); + + BOOL result = ShellExecuteExW(&sinfo); + if (!result || reinterpret_cast<LONG_PTR>(sinfo.hInstApp) < 32) { + rv = NS_ERROR_FAILURE; + } + } + + return rv; +} + +void nsMIMEInfoWin::UpdateDefaultInfoIfStale() { + if (!mIsDefaultAppInfoFresh) { + nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1"); + if (mime) { + mime->UpdateDefaultAppInfo(static_cast<nsIMIMEInfo*>(this)); + } + mIsDefaultAppInfoFresh = true; + } +} + +// Given a path to a local file, return its nsILocalHandlerApp instance. +bool nsMIMEInfoWin::GetLocalHandlerApp(const nsAString& aCommandHandler, + nsCOMPtr<nsILocalHandlerApp>& aApp) { + nsCOMPtr<nsIFile> locfile; + nsresult rv = NS_NewLocalFile(aCommandHandler, true, getter_AddRefs(locfile)); + if (NS_FAILED(rv)) return false; + + aApp = do_CreateInstance("@mozilla.org/uriloader/local-handler-app;1"); + if (!aApp) return false; + + aApp->SetExecutable(locfile); + return true; +} + +// Return the cleaned up file path associated with a command verb +// located in root/Applications. +bool nsMIMEInfoWin::GetAppsVerbCommandHandler(const nsAString& appExeName, + nsAString& applicationPath, + bool edit) { + nsCOMPtr<nsIWindowsRegKey> appKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!appKey) return false; + + // HKEY_CLASSES_ROOT\Applications\iexplore.exe + nsAutoString applicationsPath; + applicationsPath.AppendLiteral("Applications\\"); + applicationsPath.Append(appExeName); + + nsresult rv = + appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, applicationsPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return false; + + // Check for the NoOpenWith flag, if it exists + uint32_t value; + if (NS_SUCCEEDED(appKey->ReadIntValue(u"NoOpenWith"_ns, &value)) && + value == 1) + return false; + + nsAutoString dummy; + if (NS_SUCCEEDED(appKey->ReadStringValue(u"NoOpenWith"_ns, dummy))) + return false; + + appKey->Close(); + + // HKEY_CLASSES_ROOT\Applications\iexplore.exe\shell\open\command + applicationsPath.AssignLiteral("Applications\\"); + applicationsPath.Append(appExeName); + if (!edit) + applicationsPath.AppendLiteral("\\shell\\open\\command"); + else + applicationsPath.AppendLiteral("\\shell\\edit\\command"); + + rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, applicationsPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return false; + + nsAutoString appFilesystemCommand; + if (NS_SUCCEEDED(appKey->ReadStringValue(u""_ns, appFilesystemCommand))) { + // Expand environment vars, clean up any misc. + if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand)) return false; + + applicationPath = appFilesystemCommand; + return true; + } + return false; +} + +// Return a fully populated command string based on +// passing information. Used in launchWithFile to trace +// back to the full handler path based on the dll. +// (dll, targetfile, return args, open/edit) +bool nsMIMEInfoWin::GetDllLaunchInfo(nsIFile* aDll, nsIFile* aFile, + nsAString& args, bool edit) { + if (!aDll || !aFile) return false; + + nsString appExeName; + aDll->GetLeafName(appExeName); + + nsCOMPtr<nsIWindowsRegKey> appKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!appKey) return false; + + // HKEY_CLASSES_ROOT\Applications\iexplore.exe + nsAutoString applicationsPath; + applicationsPath.AppendLiteral("Applications\\"); + applicationsPath.Append(appExeName); + + nsresult rv = + appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, applicationsPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return false; + + // Check for the NoOpenWith flag, if it exists + uint32_t value; + rv = appKey->ReadIntValue(u"NoOpenWith"_ns, &value); + if (NS_SUCCEEDED(rv) && value == 1) return false; + + nsAutoString dummy; + if (NS_SUCCEEDED(appKey->ReadStringValue(u"NoOpenWith"_ns, dummy))) + return false; + + appKey->Close(); + + // HKEY_CLASSES_ROOT\Applications\iexplore.exe\shell\open\command + applicationsPath.AssignLiteral("Applications\\"); + applicationsPath.Append(appExeName); + if (!edit) + applicationsPath.AppendLiteral("\\shell\\open\\command"); + else + applicationsPath.AppendLiteral("\\shell\\edit\\command"); + + rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, applicationsPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return false; + + nsAutoString appFilesystemCommand; + if (NS_SUCCEEDED(appKey->ReadStringValue(u""_ns, appFilesystemCommand))) { + // Replace embedded environment variables. + uint32_t bufLength = + ::ExpandEnvironmentStringsW(appFilesystemCommand.get(), nullptr, 0); + if (bufLength == 0) // Error + return false; + + auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength); + if (!destination) return false; + if (!::ExpandEnvironmentStringsW(appFilesystemCommand.get(), + destination.get(), bufLength)) + return false; + + appFilesystemCommand.Assign(destination.get()); + + // C:\Windows\System32\rundll32.exe "C:\Program Files\Windows + // Photo Gallery\PhotoViewer.dll", ImageView_Fullscreen %1 + nsAutoString params; + constexpr auto rundllSegment = u"rundll32.exe "_ns; + int32_t index = appFilesystemCommand.Find(rundllSegment); + if (index > kNotFound) { + params.Append( + Substring(appFilesystemCommand, index + rundllSegment.Length())); + } else { + params.Append(appFilesystemCommand); + } + + // check to make sure we have a %1 and fill it + constexpr auto percentOneParam = u"%1"_ns; + index = params.Find(percentOneParam); + if (index == kNotFound) // no parameter + return false; + + nsString target; + aFile->GetTarget(target); + params.Replace(index, 2, target); + + args = params; + + return true; + } + return false; +} + +// Return the cleaned up file path associated with a progid command +// verb located in root. +bool nsMIMEInfoWin::GetProgIDVerbCommandHandler(const nsAString& appProgIDName, + nsAString& applicationPath, + bool edit) { + nsCOMPtr<nsIWindowsRegKey> appKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!appKey) return false; + + nsAutoString appProgId(appProgIDName); + + // HKEY_CLASSES_ROOT\Windows.XPSReachViewer\shell\open\command + if (!edit) + appProgId.AppendLiteral("\\shell\\open\\command"); + else + appProgId.AppendLiteral("\\shell\\edit\\command"); + + nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, appProgId, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return false; + + nsAutoString appFilesystemCommand; + if (NS_SUCCEEDED(appKey->ReadStringValue(u""_ns, appFilesystemCommand))) { + // Expand environment vars, clean up any misc. + if (!nsLocalFile::CleanupCmdHandlerPath(appFilesystemCommand)) return false; + + applicationPath = appFilesystemCommand; + return true; + } + return false; +} + +// Helper routine used in tracking app lists. Converts path +// entries to lower case and stores them in the trackList array. +void nsMIMEInfoWin::ProcessPath(nsCOMPtr<nsIMutableArray>& appList, + nsTArray<nsString>& trackList, + const nsAString& appFilesystemCommand) { + nsAutoString lower(appFilesystemCommand); + ToLowerCase(lower); + + // Don't include firefox.exe in the list + WCHAR exe[MAX_PATH + 1]; + uint32_t len = GetModuleFileNameW(nullptr, exe, MAX_PATH); + if (len < MAX_PATH && len != 0) { + int32_t index = lower.Find(reinterpret_cast<const char16_t*>(exe)); + if (index != -1) return; + } + + nsCOMPtr<nsILocalHandlerApp> aApp; + if (!GetLocalHandlerApp(appFilesystemCommand, aApp)) return; + + // Save in our main tracking arrays + appList->AppendElement(aApp); + trackList.AppendElement(lower); +} + +// Helper routine that handles a compare between a path +// and an array of paths. +static bool IsPathInList(nsAString& appPath, nsTArray<nsString>& trackList) { + // trackList data is always lowercase, see ProcessPath + // above. + nsAutoString tmp(appPath); + ToLowerCase(tmp); + + for (uint32_t i = 0; i < trackList.Length(); i++) { + if (tmp.Equals(trackList[i])) return true; + } + return false; +} + +/** + * Returns a list of nsILocalHandlerApp objects containing local + * handlers associated with this mimeinfo. Implemented per + * platform using information in this object to generate the + * best list. Typically used for an "open with" style user + * option. + * + * @return nsIArray of nsILocalHandlerApp + */ +NS_IMETHODIMP +nsMIMEInfoWin::GetPossibleLocalHandlers(nsIArray** _retval) { + nsresult rv; + + *_retval = nullptr; + + nsCOMPtr<nsIMutableArray> appList = do_CreateInstance("@mozilla.org/array;1"); + + if (!appList) return NS_ERROR_FAILURE; + + nsTArray<nsString> trackList; + + nsAutoCString fileExt; + GetPrimaryExtension(fileExt); + + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return NS_ERROR_FAILURE; + nsCOMPtr<nsIWindowsRegKey> appKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!appKey) return NS_ERROR_FAILURE; + + nsAutoString workingRegistryPath; + + bool extKnown = false; + if (fileExt.IsEmpty()) { + extKnown = true; + // Mime type discovery is possible in some cases, through + // HKEY_CLASSES_ROOT\MIME\Database\Content Type, however, a number + // of file extensions related to mime type are simply not defined, + // (application/rss+xml & application/atom+xml are good examples) + // in which case we can only provide a generic list. + nsAutoCString mimeType; + GetMIMEType(mimeType); + if (!mimeType.IsEmpty()) { + workingRegistryPath.AppendLiteral("MIME\\Database\\Content Type\\"); + workingRegistryPath.Append(NS_ConvertASCIItoUTF16(mimeType)); + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + workingRegistryPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + nsAutoString mimeFileExt; + if (NS_SUCCEEDED(regKey->ReadStringValue(u""_ns, mimeFileExt))) { + CopyUTF16toUTF8(mimeFileExt, fileExt); + extKnown = false; + } + } + } + } + + nsAutoString fileExtToUse; + if (!fileExt.IsEmpty() && fileExt.First() != '.') { + fileExtToUse = char16_t('.'); + } + fileExtToUse.Append(NS_ConvertUTF8toUTF16(fileExt)); + + // Note, the order in which these occur has an effect on the + // validity of the resulting display list. + + if (!extKnown) { + // 1) Get the default handler if it exists + workingRegistryPath = fileExtToUse; + + rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + workingRegistryPath, nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + nsAutoString appProgId; + if (NS_SUCCEEDED(regKey->ReadStringValue(u""_ns, appProgId))) { + // Bug 358297 - ignore the embedded internet explorer handler + if (appProgId != u"XPSViewer.Document"_ns) { + nsAutoString appFilesystemCommand; + if (GetProgIDVerbCommandHandler(appProgId, appFilesystemCommand, + false) && + !IsPathInList(appFilesystemCommand, trackList)) { + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + } + regKey->Close(); + } + + // 2) list HKEY_CLASSES_ROOT\.ext\OpenWithList + + workingRegistryPath = fileExtToUse; + workingRegistryPath.AppendLiteral("\\OpenWithList"); + + rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + workingRegistryPath, nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + nsAutoString appName; + if (NS_FAILED(regKey->GetValueName(index, appName))) continue; + + // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params" + nsAutoString appFilesystemCommand; + if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand, + false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + regKey->Close(); + } + + // 3) List HKEY_CLASSES_ROOT\.ext\OpenWithProgids, with the + // different step of resolving the progids for the command handler. + + workingRegistryPath = fileExtToUse; + workingRegistryPath.AppendLiteral("\\OpenWithProgids"); + + rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + workingRegistryPath, nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + // HKEY_CLASSES_ROOT\.ext\OpenWithProgids\Windows.XPSReachViewer + nsAutoString appProgId; + if (NS_FAILED(regKey->GetValueName(index, appProgId))) continue; + + nsAutoString appFilesystemCommand; + if (!GetProgIDVerbCommandHandler(appProgId, appFilesystemCommand, + false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + regKey->Close(); + } + + // 4) Add any non configured applications located in the MRU list + + // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion + // \Explorer\FileExts\.ext\OpenWithList + workingRegistryPath = nsLiteralString( + u"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\"); + workingRegistryPath += fileExtToUse; + workingRegistryPath.AppendLiteral("\\OpenWithList"); + + rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + workingRegistryPath, nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + nsAutoString appName, appValue; + if (NS_FAILED(regKey->GetValueName(index, appName))) continue; + if (appName.EqualsLiteral("MRUList")) continue; + if (NS_FAILED(regKey->ReadStringValue(appName, appValue))) continue; + + // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params" + nsAutoString appFilesystemCommand; + if (!GetAppsVerbCommandHandler(appValue, appFilesystemCommand, + false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + } + + // 5) Add any non configured progids in the MRU list, with the + // different step of resolving the progids for the command handler. + + // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion + // \Explorer\FileExts\.ext\OpenWithProgids + workingRegistryPath = nsLiteralString( + u"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\"); + workingRegistryPath += fileExtToUse; + workingRegistryPath.AppendLiteral("\\OpenWithProgids"); + + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, workingRegistryPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + nsAutoString appIndex, appProgId; + if (NS_FAILED(regKey->GetValueName(index, appProgId))) continue; + + nsAutoString appFilesystemCommand; + if (!GetProgIDVerbCommandHandler(appProgId, appFilesystemCommand, + false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + regKey->Close(); + } + + // 6) Check the perceived type value, and use this to lookup the + // perceivedtype open with list. + // http://msdn2.microsoft.com/en-us/library/aa969373.aspx + + workingRegistryPath = fileExtToUse; + + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, workingRegistryPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + nsAutoString perceivedType; + rv = regKey->ReadStringValue(u"PerceivedType"_ns, perceivedType); + if (NS_SUCCEEDED(rv)) { + nsAutoString openWithListPath(u"SystemFileAssociations\\"_ns); + openWithListPath.Append(perceivedType); // no period + openWithListPath.AppendLiteral("\\OpenWithList"); + + nsresult rv = appKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + openWithListPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + nsAutoString appName; + if (NS_FAILED(regKey->GetValueName(index, appName))) continue; + + // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params" + nsAutoString appFilesystemCommand; + if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand, + false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + } + } + } + } // extKnown == false + + // 7) list global HKEY_CLASSES_ROOT\*\OpenWithList + // Listing general purpose handlers, not specific to a mime type or file + // extension + + workingRegistryPath = u"*\\OpenWithList"_ns; + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + workingRegistryPath, nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetValueCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + nsAutoString appName; + if (NS_FAILED(regKey->GetValueName(index, appName))) continue; + + // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params" + nsAutoString appFilesystemCommand; + if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand, false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + regKey->Close(); + } + + // 8) General application's list - not file extension specific on windows + workingRegistryPath = u"Applications"_ns; + + rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, workingRegistryPath, + nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS | + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + uint32_t count = 0; + if (NS_SUCCEEDED(regKey->GetChildCount(&count)) && count > 0) { + for (uint32_t index = 0; index < count; index++) { + nsAutoString appName; + if (NS_FAILED(regKey->GetChildName(index, appName))) continue; + + // HKEY_CLASSES_ROOT\Applications\firefox.exe = "path params" + nsAutoString appFilesystemCommand; + if (!GetAppsVerbCommandHandler(appName, appFilesystemCommand, false) || + IsPathInList(appFilesystemCommand, trackList)) + continue; + ProcessPath(appList, trackList, appFilesystemCommand); + } + } + } + + // Return to the caller + *_retval = appList; + NS_ADDREF(*_retval); + + return NS_OK; +} + +NS_IMETHODIMP +nsMIMEInfoWin::IsCurrentAppOSDefault(bool* _retval) { + *_retval = false; + nsCOMPtr<nsIFile> defaultApp = GetDefaultApplication(); + if (defaultApp) { + // Determine if the default executable is our executable. + nsCOMPtr<nsIFile> ourBinary; + XRE_GetBinaryPath(getter_AddRefs(ourBinary)); + bool isSame = false; + nsresult rv = defaultApp->Equals(ourBinary, &isSame); + if (NS_FAILED(rv)) { + return rv; + } + *_retval = isSame; + } + return NS_OK; +} diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.h b/uriloader/exthandler/win/nsMIMEInfoWin.h new file mode 100644 index 0000000000..fa972b23a9 --- /dev/null +++ b/uriloader/exthandler/win/nsMIMEInfoWin.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 nsMIMEInfoWin_h_ +#define nsMIMEInfoWin_h_ + +#include "nsMIMEInfoImpl.h" +#include "nsIPropertyBag.h" +#include "nsIMutableArray.h" +#include "nsTArray.h" + +class nsMIMEInfoWin : public nsMIMEInfoBase, public nsIPropertyBag { + virtual ~nsMIMEInfoWin(); + + public: + explicit nsMIMEInfoWin(const char* aType = "") : nsMIMEInfoBase(aType) {} + explicit nsMIMEInfoWin(const nsACString& aMIMEType) + : nsMIMEInfoBase(aMIMEType) {} + nsMIMEInfoWin(const nsACString& aType, HandlerClass aClass) + : nsMIMEInfoBase(aType, aClass) {} + + NS_IMETHOD LaunchWithFile(nsIFile* aFile) override; + NS_IMETHOD GetHasDefaultHandler(bool* _retval) override; + NS_IMETHOD GetPossibleLocalHandlers(nsIArray** _retval) override; + NS_IMETHOD IsCurrentAppOSDefault(bool* _retval) override; + + void UpdateDefaultInfoIfStale(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPROPERTYBAG + + void SetDefaultApplicationHandler(nsIFile* aDefaultApplication) { + mDefaultApplication = aDefaultApplication; + } + + protected: + nsIFile* GetDefaultApplication() { + UpdateDefaultInfoIfStale(); + return mDefaultApplication; + } + + virtual nsresult LoadUriInternal(nsIURI* aURI); + virtual nsresult LaunchDefaultWithFile(nsIFile* aFile); + + private: + nsCOMPtr<nsIFile> mDefaultApplication; + + // Given a path to a local handler, return its + // nsILocalHandlerApp instance. + bool GetLocalHandlerApp(const nsAString& aCommandHandler, + nsCOMPtr<nsILocalHandlerApp>& aApp); + + // Return the cleaned up file path associated + // with a command verb located in root/Applications. + bool GetAppsVerbCommandHandler(const nsAString& appExeName, + nsAString& applicationPath, bool bEdit); + + // Return the cleaned up file path associated + // with a progid command verb located in root. + bool GetProgIDVerbCommandHandler(const nsAString& appProgIDName, + nsAString& applicationPath, bool bEdit); + + // Lookup a rundll command handler and return + // a populated command template for use with rundll32.exe. + bool GetDllLaunchInfo(nsIFile* aDll, nsIFile* aFile, nsAString& args, + bool bEdit); + + // Helper routine used in tracking app lists + void ProcessPath(nsCOMPtr<nsIMutableArray>& appList, + nsTArray<nsString>& trackList, + const nsAString& appFilesystemCommand); + + // Helper routine to call mozilla::ShellExecuteByExplorer + nsresult ShellExecuteWithIFile(nsIFile* aExecutable, int aArgc, + const wchar_t** aArgv); +}; + +#endif diff --git a/uriloader/exthandler/win/nsOSHelperAppService.cpp b/uriloader/exthandler/win/nsOSHelperAppService.cpp new file mode 100644 index 0000000000..8d23dd758d --- /dev/null +++ b/uriloader/exthandler/win/nsOSHelperAppService.cpp @@ -0,0 +1,612 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim:set ts=2 sts=2 sw=2 et cin: + * + * 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 "nsComponentManagerUtils.h" +#include "nsOSHelperAppService.h" +#include "nsISupports.h" +#include "nsString.h" +#include "nsIMIMEInfo.h" +#include "nsMIMEInfoWin.h" +#include "nsMimeTypes.h" +#include "nsNativeCharsetUtils.h" +#include "nsLocalFile.h" +#include "nsIWindowsRegKey.h" +#include "nsXULAppAPI.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/WindowsVersion.h" + +// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN +#include <shellapi.h> +#include <shlwapi.h> + +#define LOG(...) MOZ_LOG(sLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +// helper methods: forward declarations... +static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType, + nsString& aFileExtension); + +nsOSHelperAppService::nsOSHelperAppService() + : nsExternalHelperAppService(), mAppAssoc(nullptr) { + CoInitialize(nullptr); + CoCreateInstance(CLSID_ApplicationAssociationRegistration, nullptr, + CLSCTX_INPROC, IID_IApplicationAssociationRegistration, + (void**)&mAppAssoc); +} + +nsOSHelperAppService::~nsOSHelperAppService() { + if (mAppAssoc) mAppAssoc->Release(); + mAppAssoc = nullptr; + CoUninitialize(); +} + +// The windows registry provides a mime database key which lists a set of mime +// types and corresponding "Extension" values. we can use this to look up our +// mime type to see if there is a preferred extension for the mime type. +static nsresult GetExtensionFromWindowsMimeDatabase(const nsACString& aMimeType, + nsString& aFileExtension) { + nsAutoString mimeDatabaseKey; + mimeDatabaseKey.AssignLiteral("MIME\\Database\\Content Type\\"); + + AppendASCIItoUTF16(aMimeType, mimeDatabaseKey); + + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, mimeDatabaseKey, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + + if (NS_SUCCEEDED(rv)) + regKey->ReadStringValue(u"Extension"_ns, aFileExtension); + + return NS_OK; +} + +nsresult nsOSHelperAppService::OSProtocolHandlerExists( + const char* aProtocolScheme, bool* aHandlerExists) { + // look up the protocol scheme in the windows registry....if we find a match + // then we have a handler for it... + *aHandlerExists = false; + if (aProtocolScheme && *aProtocolScheme) { + NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE); + wchar_t* pResult = nullptr; + NS_ConvertASCIItoUTF16 scheme(aProtocolScheme); + // We are responsible for freeing returned strings. + HRESULT hr = mAppAssoc->QueryCurrentDefault(scheme.get(), AT_URLPROTOCOL, + AL_EFFECTIVE, &pResult); + if (SUCCEEDED(hr)) { + CoTaskMemFree(pResult); + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + nsDependentString(scheme.get()), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) { + // Open will fail if the registry key path doesn't exist. + return NS_OK; + } + + bool hasValue; + rv = regKey->HasValue(u"URL Protocol"_ns, &hasValue); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + if (!hasValue) { + return NS_OK; + } + + *aHandlerExists = true; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription( + const nsACString& aScheme, nsAString& _retval) { + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return NS_ERROR_NOT_AVAILABLE; + + NS_ConvertASCIItoUTF16 buf(aScheme); + + if (mozilla::IsWin8OrLater()) { + wchar_t result[1024]; + DWORD resultSize = 1024; + HRESULT hr = AssocQueryString(0x1000 /* ASSOCF_IS_PROTOCOL */, + ASSOCSTR_FRIENDLYAPPNAME, buf.get(), NULL, + result, &resultSize); + if (SUCCEEDED(hr)) { + _retval = result; + return NS_OK; + } + } + + NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE); + wchar_t* pResult = nullptr; + // We are responsible for freeing returned strings. + HRESULT hr = mAppAssoc->QueryCurrentDefault(buf.get(), AT_URLPROTOCOL, + AL_EFFECTIVE, &pResult); + if (SUCCEEDED(hr)) { + nsCOMPtr<nsIFile> app; + nsAutoString appInfo(pResult); + CoTaskMemFree(pResult); + if (NS_SUCCEEDED(GetDefaultAppInfo(appInfo, _retval, getter_AddRefs(app)))) + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsOSHelperAppService::IsCurrentAppOSDefaultForProtocol( + const nsACString& aScheme, bool* _retval) { + *_retval = false; + + NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE); + + NS_ConvertASCIItoUTF16 buf(aScheme); + + // Find the progID + wchar_t* pResult = nullptr; + HRESULT hr = mAppAssoc->QueryCurrentDefault(buf.get(), AT_URLPROTOCOL, + AL_EFFECTIVE, &pResult); + if (FAILED(hr)) { + return NS_ERROR_FAILURE; + } + nsAutoString progID(pResult); + // We are responsible for freeing returned strings. + CoTaskMemFree(pResult); + + // Find the default executable. + nsAutoString description; + nsCOMPtr<nsIFile> appFile; + nsresult rv = GetDefaultAppInfo(progID, description, getter_AddRefs(appFile)); + if (NS_FAILED(rv)) { + return rv; + } + // Determine if the default executable is our executable. + nsCOMPtr<nsIFile> ourBinary; + XRE_GetBinaryPath(getter_AddRefs(ourBinary)); + bool isSame = false; + rv = appFile->Equals(ourBinary, &isSame); + if (NS_FAILED(rv)) { + return rv; + } + *_retval = isSame; + return NS_OK; +} + +// GetMIMEInfoFromRegistry: This function obtains the values of some of the +// nsIMIMEInfo attributes for the mimeType/extension associated with the input +// registry key. The default entry for that key is the name of a registry key +// under HKEY_CLASSES_ROOT. The default value for *that* key is the descriptive +// name of the type. The EditFlags value is a binary value; the low order bit +// of the third byte of which indicates that the user does not need to be +// prompted. +// +// This function sets only the Description attribute of the input nsIMIMEInfo. +/* static */ +nsresult nsOSHelperAppService::GetMIMEInfoFromRegistry(const nsString& fileType, + nsIMIMEInfo* pInfo) { + nsresult rv = NS_OK; + + NS_ENSURE_ARG(pInfo); + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return NS_ERROR_NOT_AVAILABLE; + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, fileType, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + // OK, the default value here is the description of the type. + nsAutoString description; + rv = regKey->ReadStringValue(u""_ns, description); + if (NS_SUCCEEDED(rv)) pInfo->SetDescription(description); + + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// method overrides used to gather information from the windows registry for +// various mime types. +//////////////////////////////////////////////////////////////////////////////////////////////// + +/// Looks up the type for the extension aExt and compares it to aType +/* static */ +bool nsOSHelperAppService::typeFromExtEquals(const char16_t* aExt, + const char* aType) { + if (!aType) return false; + nsAutoString fileExtToUse; + if (aExt[0] != char16_t('.')) fileExtToUse = char16_t('.'); + + fileExtToUse.Append(aExt); + + bool eq = false; + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return eq; + + nsresult rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, fileExtToUse, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return eq; + + nsAutoString type; + rv = regKey->ReadStringValue(u"Content Type"_ns, type); + if (NS_SUCCEEDED(rv)) eq = type.LowerCaseEqualsASCII(aType); + + return eq; +} + +// The "real" name of a given helper app (as specified by the path to the +// executable file held in various registry keys) is stored n the VERSIONINFO +// block in the file's resources. We need to find the path to the executable +// and then retrieve the "FileDescription" field value from the file. +nsresult nsOSHelperAppService::GetDefaultAppInfo( + const nsAString& aAppInfo, nsAString& aDefaultDescription, + nsIFile** aDefaultApplication) { + nsAutoString handlerCommand; + + // If all else fails, use the file type key name, which will be + // something like "pngfile" for .pngs, "WMVFile" for .wmvs, etc. + aDefaultDescription = aAppInfo; + *aDefaultApplication = nullptr; + + if (aAppInfo.IsEmpty()) return NS_ERROR_FAILURE; + + // aAppInfo may be a file, file path, program id, or + // Applications reference - + // c:\dir\app.exe + // Applications\appfile.exe/dll (shell\open...) + // ProgID.progid (shell\open...) + + nsAutoString handlerKeyName(aAppInfo); + + nsCOMPtr<nsIWindowsRegKey> chkKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!chkKey) return NS_ERROR_FAILURE; + + nsresult rv = + chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, handlerKeyName, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) { + // It's a file system path to a handler + handlerCommand.Assign(aAppInfo); + } else { + handlerKeyName.AppendLiteral("\\shell\\open\\command"); + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return NS_ERROR_FAILURE; + + nsresult rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, handlerKeyName, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + // OK, the default value here is the description of the type. + rv = regKey->ReadStringValue(u""_ns, handlerCommand); + if (NS_FAILED(rv)) { + // Check if there is a DelegateExecute string + nsAutoString delegateExecute; + rv = regKey->ReadStringValue(u"DelegateExecute"_ns, delegateExecute); + NS_ENSURE_SUCCESS(rv, rv); + + // Look for InProcServer32 + nsAutoString delegateExecuteRegPath; + delegateExecuteRegPath.AssignLiteral("CLSID\\"); + delegateExecuteRegPath.Append(delegateExecute); + delegateExecuteRegPath.AppendLiteral("\\InProcServer32"); + rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + delegateExecuteRegPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + rv = chkKey->ReadStringValue(u""_ns, handlerCommand); + } + + if (NS_FAILED(rv)) { + // Look for LocalServer32 + delegateExecuteRegPath.AssignLiteral("CLSID\\"); + delegateExecuteRegPath.Append(delegateExecute); + delegateExecuteRegPath.AppendLiteral("\\LocalServer32"); + rv = chkKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, + delegateExecuteRegPath, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + NS_ENSURE_SUCCESS(rv, rv); + rv = chkKey->ReadStringValue(u""_ns, handlerCommand); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + // XXX FIXME: If this fails, the UI will display the full command + // string. + // There are some rare cases this can happen - ["url.dll" -foo] + // for example won't resolve correctly to the system dir. The + // subsequent launch of the helper app will work though. + nsCOMPtr<nsILocalFileWin> lf = new nsLocalFile(); + rv = lf->InitWithCommandLine(handlerCommand); + NS_ENSURE_SUCCESS(rv, rv); + lf.forget(aDefaultApplication); + + wchar_t friendlyName[1024]; + DWORD friendlyNameSize = 1024; + HRESULT hr = AssocQueryString(ASSOCF_NONE, ASSOCSTR_FRIENDLYAPPNAME, + PromiseFlatString(aAppInfo).get(), NULL, + friendlyName, &friendlyNameSize); + if (SUCCEEDED(hr) && friendlyNameSize > 1) { + aDefaultDescription.Assign(friendlyName, friendlyNameSize - 1); + } + + return NS_OK; +} + +already_AddRefed<nsMIMEInfoWin> nsOSHelperAppService::GetByExtension( + const nsString& aFileExt, const char* aTypeHint) { + if (aFileExt.IsEmpty()) return nullptr; + + // Determine the mime type. + nsAutoCString typeToUse; + if (aTypeHint && *aTypeHint) { + typeToUse.Assign(aTypeHint); + } else if (!GetMIMETypeFromOSForExtension(NS_ConvertUTF16toUTF8(aFileExt), + typeToUse)) { + return nullptr; + } + + RefPtr<nsMIMEInfoWin> mimeInfo = new nsMIMEInfoWin(typeToUse); + + // Our extension APIs expect extensions without the '.', so normalize: + uint32_t dotlessIndex = aFileExt.First() != char16_t('.') ? 0 : 1; + nsAutoCString lowerFileExt = + NS_ConvertUTF16toUTF8(Substring(aFileExt, dotlessIndex)); + ToLowerCase(lowerFileExt); + mimeInfo->AppendExtension(lowerFileExt); + mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk); + + if (NS_FAILED(InternalSetDefaultsOnMIME(mimeInfo))) { + return nullptr; + } + + return mimeInfo.forget(); +} + +nsresult nsOSHelperAppService::InternalSetDefaultsOnMIME( + nsMIMEInfoWin* aMIMEInfo) { + NS_ENSURE_ARG(aMIMEInfo); + + nsAutoCString primaryExt; + aMIMEInfo->GetPrimaryExtension(primaryExt); + + if (primaryExt.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + // windows registry assumes your file extension is going to include the '.', + // but our APIs don't have it, so add it: + nsAutoString assocType = NS_ConvertUTF8toUTF16(primaryExt); + if (assocType.First() != char16_t('.')) { + assocType.Insert(char16_t('.'), 0); + } + + nsAutoString appInfo; + bool found; + + // Retrieve the default application for this extension + NS_ENSURE_TRUE(mAppAssoc, NS_ERROR_NOT_AVAILABLE); + wchar_t* pResult = nullptr; + HRESULT hr = mAppAssoc->QueryCurrentDefault(assocType.get(), AT_FILEEXTENSION, + AL_EFFECTIVE, &pResult); + if (SUCCEEDED(hr)) { + found = true; + appInfo.Assign(pResult); + CoTaskMemFree(pResult); + } else { + found = false; + } + + // Bug 358297 - ignore the default handler, force the user to choose app + if (appInfo.EqualsLiteral("XPSViewer.Document")) found = false; + + if (!found) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Get other nsIMIMEInfo fields from registry, if possible. + nsAutoString defaultDescription; + nsCOMPtr<nsIFile> defaultApplication; + + if (NS_FAILED(GetDefaultAppInfo(appInfo, defaultDescription, + getter_AddRefs(defaultApplication)))) { + return NS_ERROR_NOT_AVAILABLE; + } + + aMIMEInfo->SetDefaultDescription(defaultDescription); + aMIMEInfo->SetDefaultApplicationHandler(defaultApplication); + + // Grab the general description + GetMIMEInfoFromRegistry(appInfo, aMIMEInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType, + const nsACString& aFileExt, + bool* aFound, nsIMIMEInfo** aMIMEInfo) { + *aFound = false; + + const nsCString& flatType = PromiseFlatCString(aMIMEType); + nsAutoString fileExtension; + CopyUTF8toUTF16(aFileExt, fileExtension); + + /* XXX The octet-stream check is a gross hack to wallpaper over the most + * common Win32 extension issues caused by the fix for bug 116938. See bug + * 120327, comment 271 for why this is needed. Not even sure we + * want to remove this once we have fixed all this stuff to work + * right; any info we get from the OS on this type is pretty much + * useless.... + */ + bool haveMeaningfulMimeType = + !aMIMEType.IsEmpty() && + !aMIMEType.LowerCaseEqualsLiteral(APPLICATION_OCTET_STREAM); + LOG("Extension lookup on '%S' with mimetype '%s'%s\n", + static_cast<const wchar_t*>(fileExtension.get()), flatType.get(), + haveMeaningfulMimeType ? " (treated as meaningful)" : ""); + + RefPtr<nsMIMEInfoWin> mi; + + // We should have *something* to go on here. + nsAutoString extensionFromMimeType; + if (haveMeaningfulMimeType) { + GetExtensionFromWindowsMimeDatabase(aMIMEType, extensionFromMimeType); + } + if (fileExtension.IsEmpty() && extensionFromMimeType.IsEmpty()) { + // Without an extension from the mimetype or the file, we can't + // do anything here. + mi = new nsMIMEInfoWin(flatType.get()); + mi.forget(aMIMEInfo); + return NS_OK; + } + + // Either fileExtension or extensionFromMimeType must now be non-empty. + + *aFound = true; + + // On Windows, we prefer the file extension for lookups over the mimetype, + // because that's how windows does things. + // If we have no file extension or it doesn't match the mimetype, use the + // mime type's default file extension instead. + bool usedMimeTypeExtensionForLookup = false; + if (fileExtension.IsEmpty() || + (!extensionFromMimeType.IsEmpty() && + !typeFromExtEquals(fileExtension.get(), flatType.get()))) { + usedMimeTypeExtensionForLookup = true; + fileExtension = extensionFromMimeType; + LOG("Now using '%s' mimetype's default file extension '%S' for lookup\n", + flatType.get(), static_cast<const wchar_t*>(fileExtension.get())); + } + + // If we have an extension, use it for lookup: + mi = GetByExtension(fileExtension, flatType.get()); + LOG("Extension lookup on '%S' found: 0x%p\n", + static_cast<const wchar_t*>(fileExtension.get()), mi.get()); + + if (mi) { + bool hasDefault = false; + mi->GetHasDefaultHandler(&hasDefault); + // If we don't find a default handler description, see if we can find one + // using the mimetype. + if (!hasDefault && !usedMimeTypeExtensionForLookup) { + RefPtr<nsMIMEInfoWin> miFromMimeType = + GetByExtension(extensionFromMimeType, flatType.get()); + LOG("Mime-based ext. lookup for '%S' found 0x%p\n", + static_cast<const wchar_t*>(extensionFromMimeType.get()), + miFromMimeType.get()); + if (miFromMimeType) { + nsAutoString desc; + miFromMimeType->GetDefaultDescription(desc); + mi->SetDefaultDescription(desc); + } + } + mi.forget(aMIMEInfo); + return NS_OK; + } + + // The extension didn't work. Try the extension from the mimetype if + // different: + if (!extensionFromMimeType.IsEmpty() && !usedMimeTypeExtensionForLookup) { + mi = GetByExtension(extensionFromMimeType, flatType.get()); + LOG("Mime-based ext. lookup for '%S' found 0x%p\n", + static_cast<const wchar_t*>(extensionFromMimeType.get()), mi.get()); + } + if (mi) { + mi.forget(aMIMEInfo); + return NS_OK; + } + // This didn't work either, so just return an empty dummy mimeinfo. + *aFound = false; + mi = new nsMIMEInfoWin(flatType.get()); + // If we didn't resort to the mime type's extension, we must have had a + // valid extension, so stick its lowercase version on the mime info. + if (!usedMimeTypeExtensionForLookup) { + nsAutoCString lowerFileExt; + ToLowerCase(aFileExt, lowerFileExt); + mi->AppendExtension(lowerFileExt); + } + mi.forget(aMIMEInfo); + return NS_OK; +} + +NS_IMETHODIMP +nsOSHelperAppService::UpdateDefaultAppInfo(nsIMIMEInfo* aMIMEInfo) { + InternalSetDefaultsOnMIME(static_cast<nsMIMEInfoWin*>(aMIMEInfo)); + return NS_OK; +} + +NS_IMETHODIMP +nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString& aScheme, + bool* found, + nsIHandlerInfo** _retval) { + NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!"); + + nsresult rv = + OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(), found); + if (NS_FAILED(rv)) return rv; + + nsMIMEInfoWin* handlerInfo = + new nsMIMEInfoWin(aScheme, nsMIMEInfoBase::eProtocolInfo); + NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY); + NS_ADDREF(*_retval = handlerInfo); + + if (!*found) { + // Code that calls this requires an object regardless if the OS has + // something for us, so we return the empty object. + return NS_OK; + } + + nsAutoString desc; + GetApplicationDescription(aScheme, desc); + handlerInfo->SetDefaultDescription(desc); + + return NS_OK; +} + +bool nsOSHelperAppService::GetMIMETypeFromOSForExtension( + const nsACString& aExtension, nsACString& aMIMEType) { + if (aExtension.IsEmpty()) return false; + + // windows registry assumes your file extension is going to include the '.'. + // so make sure it's there... + nsAutoString fileExtToUse; + if (aExtension.First() != '.') fileExtToUse = char16_t('.'); + + AppendUTF8toUTF16(aExtension, fileExtToUse); + + // Try to get an entry from the windows registry. + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1"); + if (!regKey) return false; + + nsresult rv = + regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, fileExtToUse, + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) return false; + + nsAutoString mimeType; + if (NS_FAILED(regKey->ReadStringValue(u"Content Type"_ns, mimeType)) || + mimeType.IsEmpty()) { + return false; + } + // Content-Type is always in ASCII + aMIMEType.Truncate(); + LossyAppendUTF16toASCII(mimeType, aMIMEType); + return true; +} diff --git a/uriloader/exthandler/win/nsOSHelperAppService.h b/uriloader/exthandler/win/nsOSHelperAppService.h new file mode 100644 index 0000000000..e2bce86d5b --- /dev/null +++ b/uriloader/exthandler/win/nsOSHelperAppService.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsOSHelperAppService_h__ +#define nsOSHelperAppService_h__ + +// The OS helper app service is a subclass of nsExternalHelperAppService and is +// implemented on each platform. It contains platform specific code for finding +// helper applications for a given mime type in addition to launching those +// applications. + +#include "nsExternalHelperAppService.h" +#include "nsCExternalHandlerService.h" +#include "nsCOMPtr.h" +#include <windows.h> + +#ifdef _WIN32_WINNT +# undef _WIN32_WINNT +#endif +#define _WIN32_WINNT 0x0600 +#include <shlobj.h> + +class nsMIMEInfoWin; +class nsIMIMEInfo; + +class nsOSHelperAppService : public nsExternalHelperAppService { + public: + nsOSHelperAppService(); + virtual ~nsOSHelperAppService(); + + // override nsIExternalProtocolService methods + NS_IMETHOD OSProtocolHandlerExists(const char* aProtocolScheme, + bool* aHandlerExists) override; + nsresult LoadUriInternal(nsIURI* aURL); + NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, + nsAString& _retval) override; + + NS_IMETHOD IsCurrentAppOSDefaultForProtocol(const nsACString& aScheme, + bool* _retval) override; + + // method overrides for windows registry look up steps.... + NS_IMETHOD GetMIMEInfoFromOS(const nsACString& aMIMEType, + const nsACString& aFileExt, bool* aFound, + nsIMIMEInfo** aMIMEInfo) override; + NS_IMETHOD UpdateDefaultAppInfo(nsIMIMEInfo* aMIMEInfo) override; + NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString& aScheme, + bool* found, + nsIHandlerInfo** _retval); + virtual bool GetMIMETypeFromOSForExtension(const nsACString& aExtension, + nsACString& aMIMEType) override; + + /** Get the string value of a registry value and store it in result. + * @return true on success, false on failure + */ + static bool GetValueString(HKEY hKey, const char16_t* pValueName, + nsAString& result); + + protected: + nsresult GetDefaultAppInfo(const nsAString& aTypeName, + nsAString& aDefaultDescription, + nsIFile** aDefaultApplication); + // Lookup a mime info by extension, using an optional type hint + already_AddRefed<nsMIMEInfoWin> GetByExtension( + const nsString& aFileExt, const char* aTypeHint = nullptr); + nsresult InternalSetDefaultsOnMIME(nsMIMEInfoWin* aMIMEInfo); + nsresult FindOSMimeInfoForType(const char* aMimeContentType, nsIURI* aURI, + char** aFileExtension, + nsIMIMEInfo** aMIMEInfo); + + static nsresult GetMIMEInfoFromRegistry(const nsString& fileType, + nsIMIMEInfo* pInfo); + /// Looks up the type for the extension aExt and compares it to aType + static bool typeFromExtEquals(const char16_t* aExt, const char* aType); + + private: + IApplicationAssociationRegistration* mAppAssoc; +}; + +#endif // nsOSHelperAppService_h__ |