gecko-dev/uriloader/exthandler/win/nsOSHelperAppService.cpp

607 строки
21 KiB
C++

/* -*- 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"
// 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);
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.
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", fileExtension.getW(),
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(), fileExtension.getW());
}
// If we have an extension, use it for lookup:
mi = GetByExtension(fileExtension, flatType.get());
LOG("Extension lookup on '%S' found: 0x%p\n", fileExtension.getW(), 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",
extensionFromMimeType.getW(), 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",
extensionFromMimeType.getW(), 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;
}