windows-nt/Source/XPSP1/NT/shell/shlwapi/assocelem.cpp
2020-09-26 16:20:57 +08:00

1435 lines
42 KiB
C++

#include "priv.h"
#include "ids.h"
#include "assoc.h"
#include <memt.h>
BOOL _GetAppPath(PCWSTR pszApp, PWSTR pszExe, DWORD cchExe)
{
WCHAR sz[MAX_PATH];
_MakeAppPathKey(pszApp, sz, SIZECHARS(sz));
DWORD cb = CbFromCchW(cchExe);
return ERROR_SUCCESS == SHGetValueW(HKEY_LOCAL_MACHINE, sz, NULL, NULL, pszExe, &cb);
}
inline HRESULT _QuerySourceCreateFromKey(HKEY hk, PCWSTR pszSub, BOOL fCreate, IQuerySource **ppqs)
{
return QuerySourceCreateFromKey(hk, pszSub, fCreate, IID_PPV_ARG(IQuerySource, ppqs));
}
typedef struct QUERYKEYVAL
{
ASSOCQUERY query;
PCWSTR pszKey;
PCWSTR pszVal;
} QUERYKEYVAL;
#define MAKEQKV(q, k, v) { q, k, v}
static const QUERYKEYVAL s_rgqkvVerb[] =
{
MAKEQKV(AQVS_COMMAND, L"command", NULL),
MAKEQKV(AQVS_DDECOMMAND, L"ddeexec", NULL),
MAKEQKV(AQVS_DDEIFEXEC, L"ddeexec\\ifexec", NULL),
MAKEQKV(AQVS_DDEAPPLICATION, L"ddeexec\\application", NULL),
MAKEQKV(AQVS_DDETOPIC, L"ddeexec\\topic", NULL),
MAKEQKV(AQV_NOACTIVATEHANDLER, L"ddeexec", L"NoActivateHandler"),
MAKEQKV(AQVD_MSIDESCRIPTOR, L"command", L"command"),
MAKEQKV(AQVS_APPLICATION_FRIENDLYNAME, NULL, L"FriendlyAppName"),
};
static const QUERYKEYVAL s_rgqkvShell[] =
{
MAKEQKV(AQS_FRIENDLYTYPENAME, NULL, L"FriendlyTypeName"),
MAKEQKV(AQS_DEFAULTICON, L"DefaultIcon", NULL),
MAKEQKV(AQS_CLSID, L"Clsid", NULL),
MAKEQKV(AQS_PROGID, L"Progid", NULL),
MAKEQKV(AQNS_SHELLEX_HANDLER, L"ShellEx\\%s", NULL),
};
static const QUERYKEYVAL s_rgqkvExt[] =
{
MAKEQKV(AQNS_SHELLEX_HANDLER, L"ShellEx\\%s", NULL),
MAKEQKV(AQS_CONTENTTYPE, NULL, L"Content Type"),
};
static const QUERYKEYVAL s_rgqkvApp[] =
{
MAKEQKV(AQVS_APPLICATION_FRIENDLYNAME, NULL, L"FriendlyAppName"),
};
const QUERYKEYVAL *_FindKeyVal(ASSOCQUERY query, const QUERYKEYVAL *rgQkv, UINT cQkv)
{
for (UINT i = 0; i < cQkv; i++)
{
if (rgQkv[i].query == query)
{
return &rgQkv[i];
}
}
return NULL;
}
HRESULT _SHAllocMUI(LPWSTR *ppsz)
{
WCHAR sz[INFOTIPSIZE];
HRESULT hr = SHLoadIndirectString(*ppsz, sz, ARRAYSIZE(sz), NULL);
CoTaskMemFree(*ppsz);
if (SUCCEEDED(hr))
hr = SHStrDupW(sz, ppsz);
else
*ppsz = 0;
return hr;
}
HRESULT CALLBACK _QuerySourceString(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, PWSTR *ppsz)
{
HRESULT hr = pqs->QueryValueString(pszKey, pszValue, ppsz);
if (SUCCEEDED(hr) && (query & AQF_MUISTRING))
{
// NOTE - this sucks for stack usage.
// since there is currently no way to get
// the size of the target.
hr = _SHAllocMUI(ppsz);
}
return hr;
}
HRESULT CALLBACK _QuerySourceDirect(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, FLAGGED_BYTE_BLOB **ppblob)
{
return pqs->QueryValueDirect(pszKey, pszValue, ppblob);
}
HRESULT CALLBACK _QuerySourceExists(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, void *pv)
{
return pqs->QueryValueExists(pszKey, pszValue);
}
HRESULT CALLBACK _QuerySourceDword(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, DWORD *pdw)
{
return pqs->QueryValueDword(pszKey, pszValue, pdw);
}
class CAssocElement : public IObjectWithQuerySource,
public IAssociationElement
{
public:
CAssocElement() : _cRef(1), _pqs(0) {}
virtual ~CAssocElement() { ATOMICRELEASE(_pqs); }
// IUnknown refcounting
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void)
{
return ++_cRef;
}
STDMETHODIMP_(ULONG) Release(void)
{
if (--_cRef > 0)
return _cRef;
delete this;
return 0;
}
// IObjectWithQuerySource
STDMETHODIMP SetSource(IQuerySource *pqs)
{
if (!_pqs)
{
_pqs = pqs;
_pqs->AddRef();
return S_OK;
}
return E_UNEXPECTED;
}
STDMETHODIMP GetSource(REFIID riid, void **ppv)
{
if (_pqs)
{
return _pqs->QueryInterface(riid, ppv);
}
*ppv = NULL;
return E_NOINTERFACE;
}
// IAssociationElement
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz)
{
*ppsz = 0;
return _QuerySourceAny(_QuerySourceString, _pqs, (ASSOCQUERY)(AQF_DIRECT | AQF_STRING), query, pszCue, ppsz);
}
STDMETHODIMP QueryDword(
ASSOCQUERY query,
PCWSTR pszCue,
DWORD *pdw)
{
return _QuerySourceAny(_QuerySourceDword, _pqs, (ASSOCQUERY)(AQF_DIRECT | AQF_DWORD), query, pszCue, pdw);
}
STDMETHODIMP QueryExists(
ASSOCQUERY query,
PCWSTR pszCue)
{
return _QuerySourceAny(_QuerySourceExists, _pqs, (ASSOCQUERY)(AQF_DIRECT | AQF_EXISTS), query, pszCue, (void*)NULL);
}
STDMETHODIMP QueryDirect(
ASSOCQUERY query,
PCWSTR pszCue,
FLAGGED_BYTE_BLOB **ppblob)
{
*ppblob = 0;
return _QuerySourceAny(_QuerySourceDirect, _pqs, AQF_DIRECT, query, pszCue, ppblob);
}
STDMETHODIMP QueryObject(
ASSOCQUERY query,
PCWSTR pszCue,
REFIID riid,
void **ppv)
{
*ppv = 0;
return E_NOTIMPL;
}
protected:
template<class T> HRESULT _QueryKeyValAny(HRESULT (CALLBACK *pfnAny)(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, T *pData), const QUERYKEYVAL *rgQkv, UINT cQkv, IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszCue, T *pData)
{
HRESULT hr = E_INVALIDARG;
const QUERYKEYVAL *pqkv = _FindKeyVal(query, rgQkv, cQkv);
if (pqkv)
{
WCHAR szKey[128];
PCWSTR pszKey = pqkv->pszKey;
if (query & AQF_CUEIS_NAME)
{
if (pqkv->pszKey)
{
wnsprintfW(szKey, ARRAYSIZE(szKey), pqkv->pszKey, pszCue);
pszKey = szKey;
}
// wnsprintf(szVal, ARRAYSIZE(szVal), pqkv->pszVal, pszCue);
}
hr = pfnAny(pqs, query, pszKey, pqkv->pszVal, pData);
}
return hr;
}
template<class T> HRESULT _QuerySourceAny(HRESULT (CALLBACK *pfnAny)(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, T *pData), IQuerySource *pqs, ASSOCQUERY mask, ASSOCQUERY query, PCWSTR pszCue, T *pData)
{
HRESULT hr = E_INVALIDARG;
if (pqs)
{
if (query == AQN_NAMED_VALUE || query == AQNS_NAMED_MUI_STRING)
{
hr = pfnAny(pqs, query, NULL, pszCue, pData);
}
else if ((query & (mask)) == (mask))
{
const QUERYKEYVAL *rgQkv;
UINT cQkv = _GetQueryKeyVal(&rgQkv);
if (cQkv)
{
hr = _QueryKeyValAny(pfnAny, rgQkv, cQkv, pqs, query, pszCue, pData);
}
}
}
return hr;
}
virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv) { *prgQkv = 0; return 0; }
protected:
LONG _cRef;
IQuerySource *_pqs;
};
HRESULT CAssocElement::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CAssocElement, IAssociationElement),
QITABENT(CAssocElement, IObjectWithQuerySource),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
HRESULT _QueryString(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
return pae->QueryString(query, pszCue, ppsz);
}
HRESULT _QueryDirect(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, FLAGGED_BYTE_BLOB **ppblob)
{
return pae->QueryDirect(query, pszCue, ppblob);
}
HRESULT _QueryDword(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, DWORD *pdw)
{
return pae->QueryDword(query, pszCue, pdw);
}
HRESULT _QueryExists(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, void *pv)
{
return pae->QueryExists(query, pszCue);
}
class CAssocShellElement : public CAssocElement, public IPersistString2
{
public:
virtual ~CAssocShellElement() { if (_pszInit && _pszInit != _szInit) LocalFree(_pszInit);}
// IUnknown refcounting
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP_(ULONG) AddRef(void)
{
return ++_cRef;
}
STDMETHODIMP_(ULONG) Release(void)
{
if (--_cRef > 0)
return _cRef;
delete this;
return 0;
}
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocShellElement; return S_OK;}
// IPersistString2
STDMETHODIMP SetString(PCWSTR psz)
{
if (!_pszInit)
{
DWORD cch = lstrlenW(psz);
if (cch < ARRAYSIZE(_szInit))
_pszInit = _szInit;
else
SHLocalAlloc(CbFromCchW(cch + 1), &_pszInit);
if (_pszInit)
{
StrCpyW(_pszInit, psz);
return _InitSource();
}
}
return E_UNEXPECTED;
}
STDMETHODIMP GetString(PWSTR *ppsz)
{ return SHStrDupW(_pszInit, ppsz); }
// IAssociationElement
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz)
{
if (AQF_CUEIS_SHELLVERB & query)
return _QueryVerbAny(_QueryString, query, pszCue, ppsz);
else
return CAssocElement::QueryString(query, pszCue, ppsz);
}
STDMETHODIMP QueryDword(
ASSOCQUERY query,
PCWSTR pszCue,
DWORD *pdw)
{
if (AQF_CUEIS_SHELLVERB & query)
return _QueryVerbAny(_QueryDword, query, pszCue, pdw);
else
return CAssocElement::QueryDword(query, pszCue, pdw);
}
STDMETHODIMP QueryExists(
ASSOCQUERY query,
PCWSTR pszCue)
{
if (AQF_CUEIS_SHELLVERB & query)
return _QueryVerbAny(_QueryExists, query, pszCue, (void*)NULL);
else
return CAssocElement::QueryExists(query, pszCue);
}
STDMETHODIMP QueryDirect(
ASSOCQUERY query,
PCWSTR pszCue,
FLAGGED_BYTE_BLOB **ppblob)
{
if (AQF_CUEIS_SHELLVERB & query)
return _QueryVerbAny(_QueryDirect, query, pszCue, ppblob);
else
return CAssocElement::QueryDirect(query, pszCue, ppblob);
}
STDMETHODIMP QueryObject(
ASSOCQUERY query,
PCWSTR pszCue,
REFIID riid,
void **ppv);
protected:
template<class T> HRESULT _QueryVerbAny(HRESULT (CALLBACK *pfnAny)(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, T pData), ASSOCQUERY query, PCWSTR pszCue, T pData)
{
IAssociationElement *pae;
HRESULT hr = _GetVerbDelegate(pszCue, &pae);
if (SUCCEEDED(hr))
{
hr = pfnAny(pae, query, NULL, pData);
pae->Release();
}
return hr;
}
// from CAssocElement
virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv)
{ *prgQkv = s_rgqkvShell; return ARRAYSIZE(s_rgqkvShell); }
// defaults for our subclasses
virtual BOOL _UseEnumForDefaultVerb()
{ return FALSE;}
virtual HRESULT _InitSource()
{ return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, _pszInit, FALSE, &_pqs); }
virtual BOOL _IsAppSource()
{ return FALSE; }
HRESULT _GetVerbDelegate(PCWSTR pszVerb, IAssociationElement **ppae);
HRESULT _DefaultVerbSource(IQuerySource **ppqsVerb);
HRESULT _QueryShellExtension(PCWSTR pszShellEx, PWSTR *ppsz);
protected:
PWSTR _pszInit;
WCHAR _szInit[64];
};
HRESULT CAssocShellElement::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] =
{
QITABENT(CAssocShellElement, IAssociationElement),
QITABENT(CAssocShellElement, IObjectWithQuerySource),
QITABENT(CAssocShellElement, IPersistString2),
QITABENTMULTI(CAssocShellElement, IPersist, IPersistString2),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
class CAssocProgidElement : public CAssocShellElement
{
public:
virtual ~CAssocProgidElement() { ATOMICRELEASE(_pqsExt); }
// then we handle fallback for IAssociationElement
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz);
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocProgidElement; return S_OK;}
protected: // methods
HRESULT _InitSource();
HRESULT _DefaultVerbSource(IQuerySource **ppqsVerb);
BOOL _UseEnumForDefaultVerb()
{ return TRUE; }
protected: // members
IQuerySource *_pqsExt;
};
HRESULT _QuerySourceCreateFromKey2(HKEY hk, PCWSTR pszSub1, PCWSTR pszSub2, IQuerySource **ppqs)
{
WCHAR szKey[MAX_PATH];
_PathAppend(pszSub1, pszSub2, szKey, SIZECHARS(szKey));
return _QuerySourceCreateFromKey(hk, szKey, FALSE, ppqs);
}
class CAssocClsidElement : public CAssocShellElement
{
public:
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocClsidElement; return S_OK;}
protected:
virtual HRESULT _InitSource()
{ return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"CLSID", _pszInit, &_pqs);}
};
class CAssocSystemExtElement : public CAssocShellElement
{
public:
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocSystemElement; return S_OK;}
protected:
virtual HRESULT _InitSource()
{ return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"SystemFileAssociations", _pszInit, &_pqs);}
};
class CAssocPerceivedElement : public CAssocShellElement
{
public:
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocPerceivedElement; return S_OK;}
protected:
virtual HRESULT _InitSource();
// maybe _GetVerbDelegate() to support Accepts filters
};
class CAssocApplicationElement : public CAssocShellElement
{
public:
// need to fallback to the pszInit for FriendlyAppName
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz);
STDMETHODIMP QueryObject(
ASSOCQUERY query,
PCWSTR pszCue,
REFIID riid,
void **ppv);
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocApplicationElement; return S_OK;}
protected:
virtual HRESULT _InitSource();
virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv)
{ *prgQkv = s_rgqkvApp; return ARRAYSIZE(s_rgqkvApp); }
virtual BOOL _IsAppSource()
{ return TRUE; }
BOOL _UseEnumForDefaultVerb()
{ return TRUE; }
HRESULT _GetAppDisplayName(PWSTR *ppsz);
protected:
BOOL _fIsPath;
};
HRESULT CAssocApplicationElement::_GetAppDisplayName(PWSTR *ppsz)
{
HRESULT hr;
PWSTR pszPath;
if (_fIsPath)
{
hr = S_OK;
pszPath = _pszInit;
ASSERT(pszPath);
}
else
hr = QueryString(AQVS_APPLICATION_PATH, NULL, &pszPath);
if (SUCCEEDED(hr))
{
WCHAR sz[MAX_PATH];
DWORD cb = sizeof(sz);
hr = SKGetValueW(SHELLKEY_HKCULM_MUICACHE, NULL, pszPath, NULL, sz, &cb);
if (FAILED(hr))
{
UINT cch = ARRAYSIZE(sz);
if (SHGetFileDescriptionW(pszPath, NULL, NULL, sz, &cch))
{
hr = S_OK;
SKSetValueW(SHELLKEY_HKCULM_MUICACHE, NULL, pszPath, REG_SZ, sz, CbFromCchW(lstrlenW(sz) + 1));
}
}
if (SUCCEEDED(hr))
hr = SHStrDupW(sz, ppsz);
if (pszPath != _pszInit)
CoTaskMemFree(pszPath);
}
return hr;
}
HRESULT CAssocApplicationElement::_InitSource()
{
WCHAR sz[MAX_PATH];
PCWSTR pszName = PathFindFileNameW(_pszInit);
_MakeApplicationsKey(pszName, sz, ARRAYSIZE(sz));
HRESULT hr = _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, sz, FALSE, &_pqs);
_fIsPath = pszName != _pszInit;
if (FAILED(hr))
{
if (_fIsPath && PathFileExistsW(_pszInit))
hr = S_FALSE;
}
return hr;
}
HRESULT CAssocApplicationElement::QueryObject(ASSOCQUERY query, PCWSTR pszCue, REFIID riid, void **ppv)
{
if (query == AQVO_APPLICATION_DELEGATE)
{
return QueryInterface(riid, ppv);
}
return CAssocShellElement::QueryObject(query, pszCue, riid, ppv);
}
HRESULT CAssocApplicationElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
HRESULT hr = CAssocShellElement::QueryString(query, pszCue, ppsz);
if (FAILED(hr))
{
switch (query)
{
case AQVS_APPLICATION_FRIENDLYNAME:
hr = _GetAppDisplayName(ppsz);
break;
}
}
return hr;
}
class CAssocShellVerbElement : public CAssocElement
{
public:
CAssocShellVerbElement(BOOL fIsApp) : _fIsApp(fIsApp) {}
// overload QS to return default DDEExec strings
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz);
STDMETHODIMP QueryObject(
ASSOCQUERY query,
PCWSTR pszCue,
REFIID riid,
void **ppv);
protected:
virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv)
{ *prgQkv = s_rgqkvVerb; return ARRAYSIZE(s_rgqkvVerb); }
HRESULT _GetAppDelegate(REFIID riid, void **ppv);
protected:
BOOL _fIsApp;
};
class CAssocFolderElement : public CAssocShellElement
{
public:
// overload QS to return default MUI strings
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz);
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocFolderElement; return S_OK;}
protected:
virtual HRESULT _InitSource()
{ return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"Folder", FALSE, &_pqs); }
};
class CAssocStarElement : public CAssocShellElement
{
public:
// overload QS to return default MUI strings
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz);
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocStarElement; return S_OK;}
protected:
virtual HRESULT _InitSource()
{ return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"*", FALSE, &_pqs); }
};
HRESULT CAssocShellElement::_DefaultVerbSource(IQuerySource **ppqsVerb)
{
IQuerySource *pqsShell;
HRESULT hr = _pqs->OpenSource(L"shell", FALSE, &pqsShell);
if (SUCCEEDED(hr))
{
PWSTR pszFree = NULL;
PCWSTR pszVerb;
// see if something is specified...
if (SUCCEEDED(pqsShell->QueryValueString(NULL, NULL, &pszFree)))
{
pszVerb = pszFree;
}
else
{
// default to "open"
pszVerb = L"open";
}
hr = pqsShell->OpenSource(pszVerb, FALSE, ppqsVerb);
if (FAILED(hr))
{
if (pszFree)
{
// try to find one of the ordered verbs
int c = StrCSpnW(pszFree, L" ,");
if (c != lstrlenW(pszFree))
{
pszFree[c] = 0;
hr = pqsShell->OpenSource(pszFree, FALSE, ppqsVerb);
}
}
else if (_UseEnumForDefaultVerb())
{
// APPCOMPAT - regitems need to have the open verb - ZekeL - 30-JAN-2001
// so that the IQA and ICM will behave the same,
// and regitem folders will always default to
// folder\shell\open unless they implement open
// or specify default verbs.
//
// everything else, just use the first key we find....
IEnumString *penum;
if (SUCCEEDED(pqsShell->EnumSources(&penum)))
{
ULONG c;
CSmartCoTaskMem<OLECHAR> spszEnum;
if (S_OK == penum->Next(1, &spszEnum, &c))
{
hr = pqsShell->OpenSource(spszEnum, FALSE, ppqsVerb);
}
penum->Release();
}
}
}
if (pszFree)
CoTaskMemFree(pszFree);
pqsShell->Release();
}
return hr;
}
HRESULT QSOpen2(IQuerySource *pqs, PCWSTR pszSub1, PCWSTR pszSub2, BOOL fCreate, IQuerySource **ppqs)
{
WCHAR szKey[MAX_PATH];
_PathAppend(pszSub1, pszSub2, szKey, SIZECHARS(szKey));
return pqs->OpenSource(szKey, fCreate, ppqs);
}
HRESULT CAssocShellElement::_GetVerbDelegate(PCWSTR pszVerb, IAssociationElement **ppae)
{
HRESULT hr = _pqs ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
// we will recalc each time.
// the array will cache appropriately
IQuerySource *pqs;
if (pszVerb)
{
hr = QSOpen2(_pqs, L"shell", pszVerb, FALSE, &pqs);
}
else
{
hr = _DefaultVerbSource(&pqs);
}
if (SUCCEEDED(hr))
{
CAssocShellVerbElement *pave = new CAssocShellVerbElement(_IsAppSource());
if (pave)
{
hr = pave->SetSource(pqs);
// this cant fail...
ASSERT(SUCCEEDED(hr));
*ppae = pave;
}
else
hr = E_OUTOFMEMORY;
pqs->Release();
}
}
return hr;
}
HRESULT CAssocShellElement::QueryObject(ASSOCQUERY query, PCWSTR pszCue, REFIID riid, void **ppv)
{
HRESULT hr = E_INVALIDARG;
if (AQF_CUEIS_SHELLVERB & query)
{
IAssociationElement *pae;
hr = _GetVerbDelegate(pszCue, &pae);
if (SUCCEEDED(hr))
{
if (AQVO_SHELLVERB_DELEGATE == query)
hr = pae->QueryInterface(riid, ppv);
else
hr = pae->QueryObject(query, NULL, riid, ppv);
pae->Release();
}
}
return hr;
}
HKEY _OpenProgidKey(PCWSTR pszProgid)
{
HKEY hkOut;
if (SUCCEEDED(_AssocOpenRegKey(HKEY_CLASSES_ROOT, pszProgid, &hkOut)))
{
// Check for a newer version of the ProgID
WCHAR sz[64];
DWORD cb = sizeof(sz);
//
// APPCOMPAT LEGACY - Quattro Pro 2000 and Excel 2000 dont get along - ZekeL - 7-MAR-2000
// mill bug #129525. the problem is if Quattro is installed
// first, then excel picks up quattro's CurVer key for some
// reason. then we end up using Quattro.Worksheet as the current
// version of the Excel.Sheet. this is bug in both of their code.
// since quattro cant even open the file when we give it to them,
// they never should take the assoc in the first place, and when excel
// takes over it shouldnt have preserved the CurVer key from the
// previous association. we could add some code to insure that the
// CurVer key follows the OLE progid naming conventions and that it must
// be derived from the same app name as the progid in order to take
// precedence but for now we will block CurVer from working whenever
// the progid is excel.sheet.8 (excel 2000)
//
if (StrCmpIW(L"Excel.Sheet.8", pszProgid)
&& ERROR_SUCCESS == SHGetValueW(hkOut, L"CurVer", NULL, NULL, sz, &cb)
&& (cb > sizeof(WCHAR)))
{
// cache this bubby
HKEY hkTemp = hkOut;
if (SUCCEEDED(_AssocOpenRegKey(HKEY_CLASSES_ROOT, sz, &hkOut)))
{
//
// APPCOMPAT LEGACY - order of preference - ZekeL - 22-JUL-99
// this is to support associations that installed empty curver
// keys, like microsoft project.
//
// 1. curver with shell subkey
// 2. progid with shell subkey
// 3. curver without shell subkey
// 4. progid without shell subkey
//
HKEY hkShell;
if (SUCCEEDED(_AssocOpenRegKey(hkOut, L"shell", &hkShell)))
{
RegCloseKey(hkShell);
RegCloseKey(hkTemp); // close old ProgID key
}
else if (SUCCEEDED(_AssocOpenRegKey(hkTemp, L"shell", &hkShell)))
{
RegCloseKey(hkShell);
RegCloseKey(hkOut);
hkOut = hkTemp;
}
else
RegCloseKey(hkTemp);
}
else // reset!
hkOut = hkTemp;
}
}
return hkOut;
}
HRESULT CAssocProgidElement::_InitSource()
{
HRESULT hr = S_OK;
// we need to init from an extension or Progid.
// we also support redirection
LPWSTR pszProgid;
if (_pszInit[0] == L'.')
{
hr = _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, _pszInit, FALSE, &_pqsExt);
if (SUCCEEDED(hr))
hr = _pqsExt->QueryValueString(NULL, NULL, &pszProgid);
}
else
pszProgid = _pszInit;
if (SUCCEEDED(hr))
{
HKEY hk = _OpenProgidKey(pszProgid);
if (hk)
{
hr = _QuerySourceCreateFromKey(hk, NULL, FALSE, &_pqs);
RegCloseKey(hk);
}
else
hr = E_UNEXPECTED;
if (pszProgid != _pszInit)
CoTaskMemFree(pszProgid);
}
// for legacy compat reasons, we support
// falling back to "HKEY_CLASSES_ROOT\.ext"
if (FAILED(hr) && _pqsExt)
{
_pqs = _pqsExt;
_pqsExt = NULL;
hr = S_FALSE;
}
return hr;
}
HRESULT CAssocProgidElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
HRESULT hr = CAssocShellElement::QueryString(query, pszCue, ppsz);
if (FAILED(hr))
{
if ((AQF_QUERY_INITCLASS & query) && _pqsExt)
hr = _QueryKeyValAny(_QuerySourceString, s_rgqkvExt, ARRAYSIZE(s_rgqkvExt), _pqsExt, query, pszCue, ppsz);
else if (_pqs)
{
switch (query)
{
case AQS_FRIENDLYTYPENAME:
// we like to query the default value
hr = _pqs->QueryValueString(NULL, NULL, ppsz);
break;
}
}
}
return hr;
}
STDAPI _SHAllocLoadString(HINSTANCE hinst, int ids, PWSTR *ppsz)
{
WCHAR sz[MAX_PATH];
LoadStringW(hinst, ids, sz, ARRAYSIZE(sz));
return SHStrDupW(sz, ppsz);
}
HRESULT CAssocFolderElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
if (query == AQS_FRIENDLYTYPENAME)
return _SHAllocLoadString(HINST_THISDLL, IDS_FOLDERTYPENAME, ppsz);
else
return CAssocShellElement::QueryString(query, pszCue, ppsz);
}
HRESULT _GetFileTypeName(PWSTR pszExt, PWSTR *ppsz)
{
if (pszExt && pszExt[0] == L'.' && pszExt[1])
{
WCHAR sz[MAX_PATH];
WCHAR szTemplate[128]; // "%s File"
CharUpperW(pszExt);
LoadStringW(HINST_THISDLL, IDS_EXTTYPETEMPLATE, szTemplate, ARRAYSIZE(szTemplate));
wnsprintfW(sz, ARRAYSIZE(sz), szTemplate, pszExt + 1);
return SHStrDupW(sz, ppsz);
}
else
{
// load the file description "File"
return _SHAllocLoadString(HINST_THISDLL, IDS_FILETYPENAME, ppsz);
}
}
HRESULT CAssocStarElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
if (query == AQS_FRIENDLYTYPENAME)
return _GetFileTypeName(_pszInit, ppsz);
else
return CAssocShellElement::QueryString(query, pszCue, ppsz);
}
HRESULT _ExeFromCmd(PWSTR pszCommand, PWSTR *ppsz)
{
// we just need to find where the params begin, and the exe ends...
HRESULT hr = S_OK;
PWSTR pch = PathGetArgsW(pszCommand);
WCHAR szExe[MAX_PATH];
if (*pch)
*(--pch) = 0;
else
pch = NULL;
StrCpyNW(szExe, pszCommand, ARRAYSIZE(szExe));
StrTrimW(szExe, L" \t");
PathUnquoteSpacesW(szExe);
//
// WARNING: Expensive disk hits all over!
//
// We check for %1 since it is what appears under (for example) HKEY_CLASSES_ROOT\exefile\shell\open\command
// This will save us a chain of 35 calls to _PathIsFile("%1") when launching or getting a
// context menu on a shortcut to an .exe or .bat file.
if (0 == StrCmpW(szExe, L"%1"))
hr = S_FALSE;
else if (!_PathIsFile(szExe))
{
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
if (PathIsFileSpecW(szExe))
{
if (_GetAppPath(szExe, szExe, ARRAYSIZE(szExe)))
{
if (_PathIsFile(szExe))
hr = S_OK;
}
else if (PathFindOnPathExW(szExe, NULL, PFOPEX_DEFAULT | PFOPEX_OPTIONAL))
{
// the find does a disk check for us...
hr = S_OK;
}
}
else
{
//
// sometimes the path is not properly quoted.
// these keys will still work because of the
// way CreateProcess works, but we need to do
// some fiddling to figure that out.
//
// if we found args, put them back...
// and try some different args
while (pch)
{
*pch++ = L' ';
if (pch = StrChrW(pch, L' '))
*pch = 0;
StrCpyNW(szExe, pszCommand, ARRAYSIZE(szExe));
StrTrimW(szExe, L" \t");
if (_PathIsFile(szExe))
{
hr = S_OK;
// this means that we found something
// but the command line was kinda screwed
break;
}
}// while (pch)
}
}
if (S_OK == hr && pch)
{
// currently right before the args, on a NULL terminator
ASSERT(!*pch);
pch++;
if (0 == StrCmpNIW(PathFindFileNameW(szExe), L"rundll", ARRAYSIZE(L"rundll") -1))
{
PWSTR pchComma = StrChrW(pch, L',');
// make the comma the beginning of the args
if (pchComma)
*pchComma = 0;
if (!*(PathFindExtensionW(pch))
&& lstrlenW(++pchComma) > SIZECHARS(L".dll"))
{
StrCatW(pch, L".dll");
}
// can we instead just do PFOPX()
// cuz i think that rundll just checks for
// the comma
StrCpyNW(szExe, pch, ARRAYSIZE(szExe));
StrTrimW(szExe, L" \t");
if (_PathIsFile(szExe)
|| PathFindOnPathExW(szExe, NULL, 0))
{
hr = S_OK;
}
}
}
if (SUCCEEDED(hr))
hr = SHStrDupW(szExe, ppsz);
return hr;
}
HRESULT CAssocShellVerbElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
HRESULT hr = CAssocElement::QueryString(query, pszCue, ppsz);
if (FAILED(hr))
{
// we havent scored yet
switch (query)
{
case AQVS_DDEAPPLICATION:
// we make one up
hr = QueryString(AQVS_APPLICATION_PATH, NULL, ppsz);
if (SUCCEEDED(hr))
{
PathRemoveExtensionW(*ppsz);
PathStripPathW(*ppsz);
ASSERT(**ppsz);
}
break;
case AQVS_DDETOPIC:
hr = SHStrDupW(L"System", ppsz);
break;
case AQVS_APPLICATION_FRIENDLYNAME:
// need to delegate to the application element
if (!_fIsApp)
{
IAssociationElement *pae;
hr = _GetAppDelegate(IID_PPV_ARG(IAssociationElement, &pae));
if (SUCCEEDED(hr))
{
hr = pae->QueryString(AQVS_APPLICATION_FRIENDLYNAME, NULL, ppsz);
pae->Release();
}
}
break;
case AQVS_APPLICATION_PATH:
{
CSmartCoTaskMem<OLECHAR> spszCmd;
hr = CAssocElement::QueryString(AQVS_COMMAND, NULL, &spszCmd);
if (SUCCEEDED(hr))
{
hr = _ExeFromCmd(spszCmd, ppsz);
}
}
}
}
return hr;
}
HRESULT CAssocShellVerbElement::QueryObject(ASSOCQUERY query, PCWSTR pszCue, REFIID riid, void **ppv)
{
HRESULT hr = E_INVALIDARG;
if (query == AQVO_APPLICATION_DELEGATE)
{
hr = _GetAppDelegate(riid, ppv);
}
return hr;
}
HRESULT CAssocShellVerbElement::_GetAppDelegate(REFIID riid, void **ppv)
{
CSmartCoTaskMem<OLECHAR> spszApp;
HRESULT hr = QueryString(AQVS_APPLICATION_PATH, NULL, &spszApp);
if (SUCCEEDED(hr))
{
IPersistString2 *pips;
hr = AssocCreateElement(CLSID_AssocApplicationElement, IID_PPV_ARG(IPersistString2, &pips));
if (SUCCEEDED(hr))
{
hr = pips->SetString(spszApp);
if (SUCCEEDED(hr))
hr = pips->QueryInterface(riid, ppv);
pips->Release();
}
}
return hr;
}
HRESULT CAssocPerceivedElement::_InitSource()
{
// maybe support Content Type?
WCHAR sz[64];
DWORD cb = sizeof(sz);
if (ERROR_SUCCESS == SHGetValueW(HKEY_CLASSES_ROOT, _pszInit, L"PerceivedType", NULL, sz, &cb))
{
return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"SystemFileAssociations", sz, &_pqs);
}
return E_FAIL;
}
class CAssocClientElement : public CAssocShellElement
{
public:
// overload QS to return default MUI strings
STDMETHODIMP QueryString(
ASSOCQUERY query,
PCWSTR pszCue,
PWSTR *ppsz);
// IPersist
STDMETHODIMP GetClassID(CLSID *pclsid)
{ *pclsid = CLSID_AssocClientElement; return S_OK;}
protected:
virtual HRESULT _InitSource();
private:
HRESULT _InitSourceFromKey(HKEY hkRoot, LPCWSTR pszKey);
HRESULT _FixNetscapeRegistration();
BOOL _CreateRepairedNetscapeRegistration(HKEY hkNSCopy);
};
HRESULT CAssocClientElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
{
HRESULT hr;
switch (query)
{
case AQS_FRIENDLYTYPENAME:
// First try LocalizedString; if that fails, then use the default value
// for backwards compatibility.
hr = CAssocShellElement::QueryString(AQNS_NAMED_MUI_STRING, L"LocalizedString", ppsz);
if (FAILED(hr))
{
hr = CAssocShellElement::QueryString(AQN_NAMED_VALUE, NULL, ppsz);
}
break;
case AQS_DEFAULTICON:
// First try DefaultIcon; if that fails then use the first icon of the EXE
// associated with the "open" verb.
hr = CAssocShellElement::QueryString(AQS_DEFAULTICON, pszCue, ppsz);
if (FAILED(hr))
{
hr = CAssocShellElement::QueryString(AQVS_APPLICATION_PATH, L"open", ppsz);
}
break;
default:
hr = CAssocShellElement::QueryString(query, pszCue, ppsz);
break;
}
return hr;
}
HRESULT CAssocClientElement::_InitSourceFromKey(HKEY hkRoot, LPCWSTR pszKey)
{
DWORD dwType, cbSize;
WCHAR szClient[80];
cbSize = sizeof(szClient);
LONG lRc = SHGetValueW(hkRoot, pszKey, NULL, &dwType, szClient, &cbSize);
if (lRc == ERROR_SUCCESS && dwType == REG_SZ && szClient[0])
{
// Client info is kept in HKLM
HRESULT hr = _QuerySourceCreateFromKey2(HKEY_LOCAL_MACHINE, pszKey, szClient, &_pqs);
//
// If this is the Mail client and the client is Netscape Messenger,
// then we need to do extra work to detect the broken Netscape
// Navigator 4.75 mail client and fix its registration because
// Netscape registered incorrectly. They always registered
// incorrectly, but since the only access point before Windows XP
// was an obscure menu option under IE/Tools/Mail and News, they
// never noticed that it was wrong.
//
if (SUCCEEDED(hr) &&
StrCmpICW(_pszInit, L"mail") == 0 &&
StrCmpICW(szClient, L"Netscape Messenger") == 0 &&
FAILED(QueryExists(AQVS_COMMAND, L"open")))
{
hr = _FixNetscapeRegistration();
}
return hr;
}
else
{
return E_FAIL; // no registered client
}
}
// Create a volatile copy of the Netscape registration and repair it.
// We don't touch the original registration because...
//
// 1. Its existence may break the Netscape uninstaller, and
// 2. We may be running as non-administrator so don't have write access
// anyway.
HRESULT CAssocClientElement::_FixNetscapeRegistration()
{
HKEY hkMail;
HRESULT hr = E_FAIL;
if (ERROR_SUCCESS == RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Clients\\Mail",
0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
&hkMail, NULL))
{
HKEY hkNSCopy;
DWORD dwDisposition;
if (ERROR_SUCCESS == RegCreateKeyExW(hkMail, L"Netscape Messenger",
0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL,
&hkNSCopy, &dwDisposition))
{
if (dwDisposition == REG_OPENED_EXISTING_KEY ||
_CreateRepairedNetscapeRegistration(hkNSCopy))
{
// Now swap in the good registration for the bad one
_pqs->Release();
hr = _QuerySourceCreateFromKey(hkNSCopy, NULL, FALSE, &_pqs);
}
RegCloseKey(hkNSCopy);
}
if (FAILED(hr))
{
SHDeleteKeyW(hkMail, L"Netscape Messenger");
}
RegCloseKey(hkMail);
}
return hr;
}
LONG _RegQueryString(HKEY hk, PCWSTR pszSub, LPWSTR pszBuf, LONG cbBuf)
{
return RegQueryValueW(hk, pszSub, pszBuf, &cbBuf);
}
LONG _RegSetVolatileString(HKEY hk, PCWSTR pszSub, LPCWSTR pszBuf)
{
HKEY hkSub;
LONG lRc;
if (!pszSub || pszSub[0] == L'\0')
{
lRc = RegOpenKeyEx(hk, NULL, 0, KEY_WRITE, &hkSub);
}
else
{
lRc = RegCreateKeyExW(hk, pszSub,
0, NULL, REG_OPTION_VOLATILE, KEY_WRITE, NULL,
&hkSub, NULL);
}
if (lRc == ERROR_SUCCESS)
{
lRc = RegSetValueW(hkSub, NULL, REG_SZ, pszBuf, (lstrlenW(pszBuf) + 1) * sizeof(pszBuf[0]));
RegCloseKey(hkSub);
}
return lRc;
}
BOOL CAssocClientElement::_CreateRepairedNetscapeRegistration(HKEY hkNSCopy)
{
BOOL fSuccess = FALSE;
HKEY hkSrc;
// Sadly, we cannot use SHCopyKey because SHCopyKey does not work
// on volatile keys. So we just copy the keys we care about.
WCHAR szBuf[MAX_PATH];
if (ERROR_SUCCESS == RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"Software\\Clients\\Mail\\Netscape Messenger",
0, KEY_READ, &hkSrc))
{
// Copy default icon but don't panic if it's not there.
if (ERROR_SUCCESS == _RegQueryString(hkSrc, L"Protocols\\mailto\\DefaultIcon", szBuf, ARRAYSIZE(szBuf)))
{
// Great, Netscape also registers the wrong icon so we have to fix that too.
PathParseIconLocationW(szBuf);
StrCatBuffW(szBuf, L",-1349", ARRAYSIZE(szBuf));
_RegSetVolatileString(hkNSCopy, L"DefaultIcon", szBuf);
}
// Copy friendly name
if (ERROR_SUCCESS == _RegQueryString(hkSrc, NULL, szBuf, ARRAYSIZE(szBuf)) &&
ERROR_SUCCESS == _RegSetVolatileString(hkNSCopy, NULL, szBuf))
{
PWSTR pszExe;
// Copy command line, but with a new command line parameter
if (ERROR_SUCCESS == _RegQueryString(hkSrc, L"Protocols\\mailto\\shell\\open\\command", szBuf, ARRAYSIZE(szBuf)) &&
SUCCEEDED(_ExeFromCmd(szBuf, &pszExe)))
{
lstrcpynW(szBuf, pszExe, ARRAYSIZE(szBuf));
SHFree(pszExe);
PathQuoteSpacesW(szBuf);
StrCatBuffW(szBuf, L" -mail", ARRAYSIZE(szBuf));
if (ERROR_SUCCESS == _RegSetVolatileString(hkNSCopy, L"shell\\open\\command", szBuf))
{
fSuccess = TRUE;
}
}
}
RegCloseKey(hkSrc);
}
return fSuccess;
}
HRESULT CAssocClientElement::_InitSource()
{
// First try HKCU; if that doesn't work (no value set in HKCU or
// the value in HKCU is bogus), then try again with HKLM.
WCHAR szKey[MAX_PATH];
wnsprintfW(szKey, ARRAYSIZE(szKey), L"Software\\Clients\\%s", _pszInit);
HRESULT hr = _InitSourceFromKey(HKEY_CURRENT_USER, szKey);
if (FAILED(hr))
{
hr = _InitSourceFromKey(HKEY_LOCAL_MACHINE, szKey);
}
return hr;
}
HRESULT AssocCreateElement(REFCLSID clsid, REFIID riid, void **ppv)
{
IAssociationElement *pae = NULL;
if (clsid == CLSID_AssocShellElement)
pae = new CAssocShellElement();
else if (clsid == CLSID_AssocProgidElement)
pae = new CAssocProgidElement();
else if (clsid == CLSID_AssocClsidElement)
pae = new CAssocClsidElement();
else if (clsid == CLSID_AssocSystemElement)
pae = new CAssocSystemExtElement();
else if (clsid == CLSID_AssocPerceivedElement)
pae = new CAssocPerceivedElement();
else if (clsid == CLSID_AssocApplicationElement)
pae = new CAssocApplicationElement();
else if (clsid == CLSID_AssocFolderElement)
pae = new CAssocFolderElement();
else if (clsid == CLSID_AssocStarElement)
pae = new CAssocStarElement();
else if (clsid == CLSID_AssocClientElement)
pae = new CAssocClientElement();
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
if (pae)
{
hr = pae->QueryInterface(riid, ppv);
pae->Release();
}
else
*ppv = 0;
return hr;
}