968 lines
28 KiB
C++
968 lines
28 KiB
C++
|
#include "shellprv.h"
|
||
|
#include <dpa.h>
|
||
|
#include <enumt.h>
|
||
|
|
||
|
typedef HRESULT (*PFNELEMCREATE)(const CLSID *pclsid, PCWSTR pszClass, IAssociationElement **ppae);
|
||
|
|
||
|
typedef struct _AEINFO
|
||
|
{
|
||
|
ASSOCELEM_MASK mask;
|
||
|
const CLSID *pclsid;
|
||
|
PCWSTR pszClass; // NULL indicates to use the _pszClass
|
||
|
PFNELEMCREATE pfnCreate;
|
||
|
IAssociationElement *pae;
|
||
|
} AEINFO;
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
GETELEM_RETRY = -2,
|
||
|
GETELEM_DONE = -1,
|
||
|
GETELEM_TRYNEXT = 0,
|
||
|
GETELEM_SUCCEEDED = 1,
|
||
|
}GETELEMRESULT;
|
||
|
|
||
|
#define TRYNEXT(gr) ((gr) >= GETELEM_TRYNEXT)
|
||
|
|
||
|
|
||
|
HRESULT _QueryString(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz, const IID *piid)
|
||
|
{
|
||
|
return pae->QueryString(query, pszCue, ppsz);
|
||
|
}
|
||
|
|
||
|
HRESULT _QueryDirect(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, FLAGGED_BYTE_BLOB **ppblob, const IID *piid)
|
||
|
{
|
||
|
return pae->QueryDirect(query, pszCue, ppblob);
|
||
|
}
|
||
|
|
||
|
HRESULT _QueryDword(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, DWORD *pdw, const IID *piid)
|
||
|
{
|
||
|
return pae->QueryDword(query, pszCue, pdw);
|
||
|
}
|
||
|
|
||
|
HRESULT _QueryExists(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, void *pv, const IID *piid)
|
||
|
{
|
||
|
return pae->QueryExists(query, pszCue);
|
||
|
}
|
||
|
|
||
|
HRESULT _QueryObject(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, void **ppv, const IID *piid)
|
||
|
{
|
||
|
return pae->QueryObject(query, pszCue, *piid, ppv);
|
||
|
}
|
||
|
|
||
|
|
||
|
class CAssocArray : public IAssociationArray,
|
||
|
public IAssociationArrayInitialize,
|
||
|
public IQueryAssociations
|
||
|
|
||
|
{
|
||
|
public:
|
||
|
CAssocArray() : _cRef(1), _hrInit(-1), _maskInclude(-1) {}
|
||
|
~CAssocArray() { _Reset(); }
|
||
|
// IUnknown methods
|
||
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
|
||
|
STDMETHODIMP_(ULONG) AddRef()
|
||
|
{
|
||
|
return ++_cRef;
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP_(ULONG) Release()
|
||
|
{
|
||
|
if (--_cRef > 0)
|
||
|
return _cRef;
|
||
|
|
||
|
delete this;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// IAssociationArrayInitialize
|
||
|
STDMETHODIMP InitClassElements(
|
||
|
ASSOCELEM_MASK maskBase,
|
||
|
PCWSTR pszClass);
|
||
|
|
||
|
STDMETHODIMP InsertElements(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
IEnumAssociationElements *peae);
|
||
|
|
||
|
STDMETHODIMP FilterElements(ASSOCELEM_MASK maskInclude)
|
||
|
{ _maskInclude = maskInclude; return S_OK; }
|
||
|
|
||
|
// IAssociationArray
|
||
|
STDMETHODIMP EnumElements(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
IEnumAssociationElements **ppeae);
|
||
|
|
||
|
STDMETHODIMP QueryString(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
ASSOCQUERY query,
|
||
|
PCWSTR pszCue,
|
||
|
PWSTR *ppsz)
|
||
|
{
|
||
|
return _QueryElementAny(_QueryString, mask, query, pszCue, ppsz, NULL);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP QueryDword(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
ASSOCQUERY query,
|
||
|
PCWSTR pszCue,
|
||
|
DWORD *pdw)
|
||
|
{
|
||
|
return _QueryElementAny(_QueryDword, mask, query, pszCue, pdw, NULL);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP QueryDirect(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
ASSOCQUERY query,
|
||
|
PCWSTR pszCue,
|
||
|
FLAGGED_BYTE_BLOB **ppblob)
|
||
|
{
|
||
|
return _QueryElementAny(_QueryDirect, mask, query, pszCue, ppblob, NULL);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP QueryExists(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
ASSOCQUERY query,
|
||
|
PCWSTR pszCue)
|
||
|
{
|
||
|
return _QueryElementAny(_QueryExists, mask, query, pszCue, (void*)NULL, NULL);
|
||
|
}
|
||
|
|
||
|
STDMETHODIMP QueryObject(
|
||
|
ASSOCELEM_MASK mask,
|
||
|
ASSOCQUERY query,
|
||
|
PCWSTR pszCue,
|
||
|
REFIID riid,
|
||
|
void **ppv)
|
||
|
{
|
||
|
return _QueryElementAny(_QueryObject, mask, query, pszCue, ppv, &riid);
|
||
|
}
|
||
|
|
||
|
// IQueryAssociations methods
|
||
|
STDMETHODIMP Init(ASSOCF flags, LPCTSTR pszAssoc, HKEY hkProgid, HWND hwnd);
|
||
|
STDMETHODIMP GetString(ASSOCF flags, ASSOCSTR str, LPCWSTR pszExtra, LPWSTR pszOut, DWORD *pcchOut);
|
||
|
STDMETHODIMP GetKey(ASSOCF flags, ASSOCKEY, LPCWSTR pszExtra, HKEY *phkeyOut);
|
||
|
STDMETHODIMP GetData(ASSOCF flags, ASSOCDATA data, LPCWSTR pszExtra, LPVOID pvOut, DWORD *pcbOut);
|
||
|
STDMETHODIMP GetEnum(ASSOCF flags, ASSOCENUM assocenum, LPCWSTR pszExtra, REFIID riid, LPVOID *ppvOut)
|
||
|
{ return E_NOTIMPL; }
|
||
|
|
||
|
|
||
|
GETELEMRESULT GetElement(int i, ASSOCELEM_MASK mask, IAssociationElement **ppae);
|
||
|
|
||
|
protected: // methods
|
||
|
void _Reset();
|
||
|
HRESULT _InsertSingleElement(IAssociationElement *pae);
|
||
|
HRESULT _GetCachedVerbElement(ASSOCELEM_MASK mask, PCWSTR pszVerb, IAssociationElement **ppaeVerb, IAssociationElement **ppaeElem);
|
||
|
void _SetCachedVerbElement(ASSOCELEM_MASK mask, PCWSTR pszVerb, IAssociationElement *paeVerb, IAssociationElement *paeVerbParent);
|
||
|
GETELEMRESULT _GetElement(int i, ASSOCELEM_MASK mask, IAssociationElement **ppae);
|
||
|
BOOL _FirstElement(ASSOCELEM_MASK mask, IAssociationElement **ppae);
|
||
|
HRESULT _GetVerbElement(ASSOCELEM_MASK mask, PCWSTR pszVerb, IAssociationElement **ppaeVerb, IAssociationElement **ppaeElem);
|
||
|
void _InitDelayedElements(int i, ASSOCELEM_MASK mask);
|
||
|
template<class T> HRESULT _QueryElementAny(HRESULT (CALLBACK *pfnAny)(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, T pData, const IID *piid), ASSOCELEM_MASK mask, ASSOCQUERY query, PCWSTR pszCue, T pData, const IID *piid)
|
||
|
{
|
||
|
mask &= _maskInclude;
|
||
|
IAssociationElement *pae;
|
||
|
HRESULT hr = E_FAIL;
|
||
|
if ((AQF_CUEIS_SHELLVERB & query) && _CacheVerb(query))
|
||
|
{
|
||
|
// delegate to the verb object if the cue is a verb
|
||
|
// except for AQVS_APPLICATION_FRIENDLYNAME which
|
||
|
// has some funky delegation issues.
|
||
|
IAssociationElement *paeParent;
|
||
|
hr = _GetVerbElement(mask, pszCue, &pae, &paeParent);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
if (query == AQVS_APPLICATION_FRIENDLYNAME)
|
||
|
hr = pfnAny(paeParent, query, pszCue, pData, piid);
|
||
|
else
|
||
|
hr = pfnAny(pae, query, NULL, pData, piid);
|
||
|
pae->Release();
|
||
|
paeParent->Release();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (int i = 0; FAILED(hr) && TRYNEXT(GetElement(i, mask, &pae)); i++)
|
||
|
{
|
||
|
if (pae)
|
||
|
{
|
||
|
hr = pfnAny(pae, query, pszCue, pData, piid);
|
||
|
pae->Release();
|
||
|
if (SUCCEEDED(hr))
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
BOOL _CacheVerb(ASSOCQUERY query)
|
||
|
{
|
||
|
// if we are init'd with an app element and
|
||
|
// querying for app specific values dont request the verb element
|
||
|
return !_fUsingAppElement || (query != AQVS_APPLICATION_PATH && query != AQVS_APPLICATION_FRIENDLYNAME && query != AQVO_APPLICATION_DELEGATE);
|
||
|
}
|
||
|
|
||
|
private: // members
|
||
|
LONG _cRef;
|
||
|
HRESULT _hrInit;
|
||
|
PWSTR _pszClass;
|
||
|
ASSOCELEM_MASK _maskInclude;
|
||
|
BOOL _fUsingAppElement;
|
||
|
CDSA<AEINFO> _dsaElems;
|
||
|
IEnumAssociationElements *_penumData;
|
||
|
ASSOCELEM_MASK _maskData;
|
||
|
IEnumAssociationElements *_penumExtra;
|
||
|
ASSOCELEM_MASK _maskExtra;
|
||
|
IAssociationElement *_paeVerb;
|
||
|
PWSTR _pszVerb;
|
||
|
ASSOCELEM_MASK _maskVerb;
|
||
|
IAssociationElement *_paeVerbParent;
|
||
|
};
|
||
|
|
||
|
int CALLBACK _AeinfoDelete(AEINFO *paei, void *pv)
|
||
|
{
|
||
|
if (paei->pae)
|
||
|
paei->pae->Release();
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void CAssocArray::_Reset()
|
||
|
{
|
||
|
if (_hrInit != -1)
|
||
|
{
|
||
|
if (_dsaElems)
|
||
|
_dsaElems.DestroyCallbackEx(_AeinfoDelete, (void*)NULL);
|
||
|
if (_pszClass)
|
||
|
{
|
||
|
CoTaskMemFree(_pszClass);
|
||
|
_pszClass = NULL;
|
||
|
}
|
||
|
|
||
|
ATOMICRELEASE(_penumData);
|
||
|
ATOMICRELEASE(_penumExtra);
|
||
|
ATOMICRELEASE(_paeVerb);
|
||
|
if (_pszVerb)
|
||
|
{
|
||
|
LocalFree(_pszVerb);
|
||
|
_pszVerb;
|
||
|
}
|
||
|
ATOMICRELEASE(_paeVerbParent);
|
||
|
_fUsingAppElement = FALSE;
|
||
|
_hrInit = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::QueryInterface(REFIID riid, void **ppv)
|
||
|
{
|
||
|
static const QITAB qit[] =
|
||
|
{
|
||
|
QITABENT(CAssocArray, IAssociationArray),
|
||
|
QITABENT(CAssocArray, IQueryAssociations),
|
||
|
QITABENT(CAssocArray, IAssociationArrayInitialize),
|
||
|
{ 0 },
|
||
|
};
|
||
|
|
||
|
return QISearch(this, qit, riid, ppv);
|
||
|
}
|
||
|
|
||
|
|
||
|
#define AEINFOPROGID(m, s) { m, &CLSID_AssocProgidElement, s, AssocElemCreateForClass, NULL}
|
||
|
#define MAKEAEINFO(m, c, s, p) { m, c, s, p, NULL}
|
||
|
HRESULT AssocElemCreateForUser(const CLSID *pclsid, PCWSTR pszClass, IAssociationElement **ppae)
|
||
|
{
|
||
|
WCHAR sz[64];
|
||
|
DWORD cb = sizeof(sz);
|
||
|
HRESULT hr = SKGetValue(SHELLKEY_HKCU_FILEEXTS, pszClass, L"Progid", NULL, sz, &cb);
|
||
|
if(SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = AssocElemCreateForClass(&CLSID_AssocProgidElement, sz, ppae);
|
||
|
}
|
||
|
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
cb = sizeof(sz);
|
||
|
hr = SKGetValue(SHELLKEY_HKCU_FILEEXTS, pszClass, L"Application", NULL, sz, &cb);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = AssocElemCreateForClass(&CLSID_AssocApplicationElement, sz, ppae);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
static const AEINFO s_rgaeinfoProgid[] =
|
||
|
{
|
||
|
AEINFOPROGID(ASSOCELEM_DEFAULT, NULL),
|
||
|
};
|
||
|
|
||
|
static const AEINFO s_rgaeinfoExtension[] =
|
||
|
{
|
||
|
MAKEAEINFO(ASSOCELEM_USER, NULL, NULL, AssocElemCreateForUser), // app or progid
|
||
|
AEINFOPROGID(ASSOCELEM_DEFAULT, NULL),
|
||
|
MAKEAEINFO(ASSOCELEM_SYSTEM_EXT, &CLSID_AssocSystemElement, NULL, AssocElemCreateForClass),
|
||
|
MAKEAEINFO(ASSOCELEM_SYSTEM_PERCEIVED, &CLSID_AssocPerceivedElement, NULL, AssocElemCreateForClass),
|
||
|
};
|
||
|
|
||
|
static const AEINFO s_rgaeinfoClsid[] =
|
||
|
{
|
||
|
// MAKEAEINFO(UserClsid), // clsid
|
||
|
MAKEAEINFO(ASSOCELEM_DEFAULT, &CLSID_AssocClsidElement, NULL, AssocElemCreateForClass),
|
||
|
// AEINFOPROGID(ASSOCELEM_PROGID, NULL), // progid
|
||
|
};
|
||
|
|
||
|
static const AEINFO s_aeinfoFolder = MAKEAEINFO(ASSOCELEM_BASEIS_FOLDER, &CLSID_AssocFolderElement, NULL, AssocElemCreateForClass);
|
||
|
static const AEINFO s_aeinfoStar = MAKEAEINFO(ASSOCELEM_BASEIS_STAR, &CLSID_AssocStarElement, NULL, AssocElemCreateForClass);
|
||
|
|
||
|
BOOL CAssocArray::_FirstElement(ASSOCELEM_MASK mask, IAssociationElement **ppae)
|
||
|
{
|
||
|
GETELEMRESULT res = GETELEM_TRYNEXT;
|
||
|
int cTrys = 0;
|
||
|
while (res == GETELEM_TRYNEXT)
|
||
|
{
|
||
|
// if it fails or succeeds we are done
|
||
|
// but if it calls TRYNEXT we loop
|
||
|
res = GetElement(cTrys++, mask, ppae);
|
||
|
}
|
||
|
return res == GETELEM_SUCCEEDED;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::InitClassElements(ASSOCELEM_MASK maskBase, PCWSTR pszClass)
|
||
|
{
|
||
|
_Reset();
|
||
|
// depending on what we think this is,
|
||
|
// we do things a little differently
|
||
|
ASSERT(*pszClass);
|
||
|
HRESULT hr = SHStrDup(pszClass, &_pszClass);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// 6 is the most that we will need
|
||
|
// in InitClassElements()
|
||
|
hr = _dsaElems.Create(6) ? S_OK : E_OUTOFMEMORY;
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
const AEINFO *rgAeinfo;
|
||
|
DWORD cAeinfo;
|
||
|
if (*_pszClass == L'.')
|
||
|
{
|
||
|
rgAeinfo = s_rgaeinfoExtension;
|
||
|
cAeinfo = ARRAYSIZE(s_rgaeinfoExtension);
|
||
|
}
|
||
|
else if (*_pszClass == L'{')
|
||
|
{
|
||
|
rgAeinfo = s_rgaeinfoClsid;
|
||
|
cAeinfo = ARRAYSIZE(s_rgaeinfoClsid);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rgAeinfo = s_rgaeinfoProgid;
|
||
|
cAeinfo = ARRAYSIZE(s_rgaeinfoProgid);
|
||
|
}
|
||
|
|
||
|
for (DWORD i = 0; i < cAeinfo; i++)
|
||
|
{
|
||
|
_dsaElems.AppendItem((AEINFO *)&rgAeinfo[i]);
|
||
|
}
|
||
|
|
||
|
if (ASSOCELEM_BASEIS_FOLDER & maskBase)
|
||
|
_dsaElems.AppendItem((AEINFO *)&s_aeinfoFolder);
|
||
|
|
||
|
if (ASSOCELEM_BASEIS_STAR & maskBase)
|
||
|
_dsaElems.AppendItem((AEINFO *)&s_aeinfoStar);
|
||
|
|
||
|
// we return S_FALSE if there is no default or user
|
||
|
// association. we treat this as an unknown type
|
||
|
IAssociationElement *pae;
|
||
|
if (_FirstElement(ASSOCELEM_USER | ASSOCELEM_DEFAULT, &pae))
|
||
|
{
|
||
|
pae->Release();
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hr = S_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_hrInit = hr;
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::InsertElements(ASSOCELEM_MASK mask, IEnumAssociationElements *peae)
|
||
|
{
|
||
|
HRESULT hr = E_UNEXPECTED;
|
||
|
if (!_penumData && (mask & ASSOCELEM_DATA))
|
||
|
{
|
||
|
_penumData = peae;
|
||
|
peae->AddRef();
|
||
|
_maskData = mask;
|
||
|
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
if (!_penumExtra && (mask & ASSOCELEM_EXTRA))
|
||
|
{
|
||
|
_penumExtra = peae;
|
||
|
peae->AddRef();
|
||
|
_maskExtra = mask;
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
class CEnumAssocElems : public CEnumAssociationElements
|
||
|
{
|
||
|
public:
|
||
|
CEnumAssocElems(CAssocArray *paa, ASSOCELEM_MASK mask) : _mask(mask), _paa(paa)
|
||
|
{ _paa->AddRef(); }
|
||
|
|
||
|
~CEnumAssocElems() { _paa->Release(); }
|
||
|
|
||
|
protected: // methods
|
||
|
virtual BOOL _Next(IAssociationElement **ppae);
|
||
|
|
||
|
protected:
|
||
|
ASSOCELEM_MASK _mask;
|
||
|
CAssocArray *_paa;
|
||
|
};
|
||
|
|
||
|
HRESULT CAssocArray::EnumElements(ASSOCELEM_MASK mask, IEnumAssociationElements **ppeae)
|
||
|
{
|
||
|
mask &= _maskInclude;
|
||
|
*ppeae = new CEnumAssocElems(this, mask);
|
||
|
return *ppeae ? S_OK : E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
void CAssocArray::_InitDelayedElements(int i, ASSOCELEM_MASK mask)
|
||
|
{
|
||
|
ULONG c;
|
||
|
IAssociationElement *pae;
|
||
|
if (i == 0 && _penumData && ((mask & _maskData) == _maskData))
|
||
|
{
|
||
|
// init the DSA with the data
|
||
|
int iInsert = 0;
|
||
|
while (S_OK == _penumData->Next(1, &pae, &c))
|
||
|
{
|
||
|
AEINFO ae = {ASSOCELEM_DATA, NULL, NULL, NULL, pae};
|
||
|
if (DSA_ERR != _dsaElems.InsertItem(iInsert++, &ae))
|
||
|
{
|
||
|
pae->AddRef();
|
||
|
}
|
||
|
pae->Release();
|
||
|
}
|
||
|
|
||
|
ATOMICRELEASE(_penumData);
|
||
|
}
|
||
|
|
||
|
if (_penumExtra && (mask & ASSOCELEM_EXTRA) && i == _dsaElems.GetItemCount())
|
||
|
{
|
||
|
// init the DSA with the data
|
||
|
while (S_OK == _penumExtra->Next(1, &pae, &c))
|
||
|
{
|
||
|
AEINFO ae = {ASSOCELEM_EXTRA, NULL, NULL, NULL, pae};
|
||
|
if (DSA_ERR != _dsaElems.AppendItem(&ae))
|
||
|
{
|
||
|
pae->AddRef();
|
||
|
}
|
||
|
pae->Release();
|
||
|
}
|
||
|
|
||
|
ATOMICRELEASE(_penumExtra);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GETELEMRESULT CAssocArray::_GetElement(int i, ASSOCELEM_MASK mask, IAssociationElement **ppae)
|
||
|
{
|
||
|
GETELEMRESULT res = GETELEM_DONE;
|
||
|
AEINFO *paei = _dsaElems.GetItemPtr(i);
|
||
|
if (paei)
|
||
|
{
|
||
|
// if this is one that we want to use
|
||
|
// please do
|
||
|
if (paei->mask & mask)
|
||
|
{
|
||
|
if (!paei->pae)
|
||
|
{
|
||
|
// try to create only once
|
||
|
PCWSTR pszClass = paei->pszClass ? paei->pszClass : _pszClass;
|
||
|
// make sure we dont query again
|
||
|
// if we dont need to
|
||
|
HRESULT hr = paei->pfnCreate(paei->pclsid, pszClass, &paei->pae);
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
_dsaElems.DeleteItem(i);
|
||
|
// retry the current index
|
||
|
res = GETELEM_RETRY;
|
||
|
}
|
||
|
else if (hr == S_FALSE)
|
||
|
{
|
||
|
// this is returned when the element
|
||
|
// is valid but points to an alternate location
|
||
|
// specifically the HKCR\Progid falls back to HKCR\.ext
|
||
|
// which is kind of weird. maybe we should move this
|
||
|
// to ASSOCELEM_EXTRA???
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
if (paei->pae)
|
||
|
{
|
||
|
*ppae = paei->pae;
|
||
|
paei->pae->AddRef();
|
||
|
res = GETELEM_SUCCEEDED;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
res = GETELEM_TRYNEXT;
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
GETELEMRESULT CAssocArray::GetElement(int i, ASSOCELEM_MASK mask, IAssociationElement **ppae)
|
||
|
{
|
||
|
GETELEMRESULT res = GETELEM_RETRY;
|
||
|
*ppae = 0;
|
||
|
if (_dsaElems)
|
||
|
{
|
||
|
_InitDelayedElements(i, mask);
|
||
|
while (GETELEM_RETRY == res && i < _dsaElems.GetItemCount())
|
||
|
{
|
||
|
res = _GetElement(i, mask, ppae);
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
BOOL CEnumAssocElems::_Next(IAssociationElement **ppae)
|
||
|
{
|
||
|
GETELEMRESULT res = GETELEM_TRYNEXT;
|
||
|
UINT cTrys = 0;
|
||
|
while (res == GETELEM_TRYNEXT)
|
||
|
{
|
||
|
// if it fails or succeeds we are done
|
||
|
// but if it calls TRYNEXT we loop
|
||
|
res = _paa->GetElement(_cNext + cTrys++, _mask, ppae);
|
||
|
}
|
||
|
// fix up _cNext when we skip
|
||
|
_cNext += cTrys - 1;
|
||
|
return res == GETELEM_SUCCEEDED;
|
||
|
}
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
ASSOCQUERY query;
|
||
|
PCWSTR pszCue;
|
||
|
} AQXLATE;
|
||
|
|
||
|
#define MAKEAQX(a, q, s) { q, s }
|
||
|
|
||
|
static const AQXLATE s_rgaqxStrings[] =
|
||
|
{
|
||
|
MAKEAQX(ASSOCSTR_COMMAND, AQVS_COMMAND, NULL),
|
||
|
MAKEAQX(ASSOCSTR_EXECUTABLE, AQVS_APPLICATION_PATH, NULL),
|
||
|
MAKEAQX(ASSOCSTR_FRIENDLYDOCNAME, AQS_FRIENDLYTYPENAME, , NULL), // friendly name of the document type
|
||
|
MAKEAQX(ASSOCSTR_FRIENDLYAPPNAME, AQVS_APPLICATION_FRIENDLYNAME, NULL),
|
||
|
MAKEAQX(ASSOCSTR_NOOPEN, AQNS_NAMED_MUI_STRING, L"NoOpen"),
|
||
|
MAKEAQX(ASSOCSTR_SHELLNEWVALUE, (ASSOCQUERY)0, NULL),
|
||
|
MAKEAQX(ASSOCSTR_DDECOMMAND, AQVS_DDECOMMAND, NULL),
|
||
|
MAKEAQX(ASSOCSTR_DDEIFEXEC, AQVS_DDEIFEXEC, NULL),
|
||
|
MAKEAQX(ASSOCSTR_DDEAPPLICATION, AQVS_DDEAPPLICATION, NULL),
|
||
|
MAKEAQX(ASSOCSTR_DDETOPIC, AQVS_DDETOPIC, NULL),
|
||
|
MAKEAQX(ASSOCSTR_INFOTIP, AQNS_NAMED_MUI_STRING, L"InfoTip"),
|
||
|
MAKEAQX(ASSOCSTR_QUICKTIP, AQNS_NAMED_MUI_STRING, L"QuickTip"),
|
||
|
MAKEAQX(ASSOCSTR_TILEINFO, AQNS_NAMED_MUI_STRING, L"TileInfo"),
|
||
|
MAKEAQX(ASSOCSTR_CONTENTTYPE, AQS_CONTENTTYPE, NULL),
|
||
|
MAKEAQX(ASSOCSTR_DEFAULTICON, AQS_DEFAULTICON, NULL),
|
||
|
MAKEAQX(ASSOCSTR_SHELLEXTENSION, AQNS_SHELLEX_HANDLER, NULL),
|
||
|
};
|
||
|
|
||
|
HRESULT _CopyOut(BOOL fNoTruncate, PCWSTR pszIn, PWSTR psz, DWORD *pcch)
|
||
|
{
|
||
|
// if caller doesnt want any return size,
|
||
|
// the incoming pointer is actually the size of the buffer
|
||
|
|
||
|
ASSERT(pcch);
|
||
|
ASSERT(psz || !IS_INTRESOURCE(pcch));
|
||
|
|
||
|
HRESULT hr;
|
||
|
DWORD cch = IS_INTRESOURCE(pcch) ? PtrToUlong(pcch) : *pcch;
|
||
|
DWORD cchStr = lstrlenW(pszIn);
|
||
|
|
||
|
if (psz)
|
||
|
{
|
||
|
if (!fNoTruncate || cch > cchStr)
|
||
|
{
|
||
|
StrCpyNW(psz, pszIn, cch);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hr = E_POINTER;
|
||
|
}
|
||
|
else
|
||
|
hr = S_FALSE;
|
||
|
|
||
|
// return the number of chars written/required
|
||
|
if (!IS_INTRESOURCE(pcch))
|
||
|
*pcch = (hr == S_OK) ? lstrlen(psz) + 1 : cchStr + 1;
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
ASSOCELEM_MASK _MaskFromFlags(ASSOCF flags)
|
||
|
{
|
||
|
ASSOCELEM_MASK mask = ASSOCELEM_MASK_QUERYNORMAL;
|
||
|
|
||
|
if (flags & ASSOCF_IGNOREBASECLASS)
|
||
|
mask &= (ASSOCELEM_USER | ASSOCELEM_DEFAULT);
|
||
|
|
||
|
if (flags & ASSOCF_NOUSERSETTINGS)
|
||
|
mask &= ~ASSOCELEM_USER;
|
||
|
|
||
|
return mask;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::GetString(ASSOCF flags, ASSOCSTR str, LPCTSTR pszCue, LPTSTR pszOut, DWORD *pcchOut)
|
||
|
{
|
||
|
HRESULT hr = E_UNEXPECTED;
|
||
|
if (str && str < ASSOCSTR_MAX && pcchOut && (pszOut || !IS_INTRESOURCE(pcchOut)))
|
||
|
{
|
||
|
// subtract the first one to make a zero based offset
|
||
|
int index = str - ASSOCSTR_COMMAND;
|
||
|
if (!pszCue)
|
||
|
pszCue = s_rgaqxStrings[index].pszCue;
|
||
|
|
||
|
if (s_rgaqxStrings[index].query)
|
||
|
{
|
||
|
PWSTR psz;
|
||
|
hr = QueryString(_MaskFromFlags(flags), s_rgaqxStrings[index].query, pszCue, &psz);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _CopyOut(flags & ASSOCF_NOTRUNCATE, psz, pszOut, pcchOut);
|
||
|
CoTaskMemFree(psz);
|
||
|
}
|
||
|
}
|
||
|
// else call win2k code for shellnew?
|
||
|
//
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
static const AQXLATE s_rgaqxDatas[] =
|
||
|
{
|
||
|
MAKEAQX(ASSOCDATA_MSIDESCRIPTOR, AQVD_MSIDESCRIPTOR, NULL),
|
||
|
MAKEAQX(ASSOCDATA_NOACTIVATEHANDLER, AQV_NOACTIVATEHANDLER, NULL),
|
||
|
MAKEAQX(ASSOCDATA_QUERYCLASSSTORE, AQN_NAMED_VALUE, L"QueryClassStore"),
|
||
|
MAKEAQX(ASSOCDATA_HASPERUSERASSOC, (ASSOCQUERY)0, NULL),
|
||
|
MAKEAQX(ASSOCDATA_EDITFLAGS, AQN_NAMED_VALUE, L"EditFlags"),
|
||
|
MAKEAQX(ASSOCDATA_VALUE, AQN_NAMED_VALUE, NULL),
|
||
|
};
|
||
|
|
||
|
HRESULT _CopyDataOut(BOOL fNoTruncate, FLAGGED_BYTE_BLOB *pblob, void *pv, DWORD *pcb)
|
||
|
{
|
||
|
// if caller doesnt want any return size,
|
||
|
// the incoming pointer is actually the size of the buffer
|
||
|
ASSERT(pcb);
|
||
|
ASSERT(pv || !IS_INTRESOURCE(pcb));
|
||
|
|
||
|
HRESULT hr;
|
||
|
DWORD cb = IS_INTRESOURCE(pcb) ? PtrToUlong(pcb) : *pcb;
|
||
|
if (pv)
|
||
|
{
|
||
|
if (!fNoTruncate || cb >= pblob->clSize)
|
||
|
{
|
||
|
// copy the smaller of the src or dst
|
||
|
cb = min(cb, pblob->clSize);
|
||
|
memcpy(pv, pblob->abData, cb);
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hr = E_POINTER;
|
||
|
}
|
||
|
else
|
||
|
hr = S_FALSE;
|
||
|
|
||
|
// return the number of chars written/required
|
||
|
if (!IS_INTRESOURCE(pcb))
|
||
|
*pcb = pblob->clSize;
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::GetData(ASSOCF flags, ASSOCDATA data, PCWSTR pszCue, LPVOID pvOut, DWORD *pcbOut)
|
||
|
{
|
||
|
HRESULT hr = E_INVALIDARG;
|
||
|
if (data && data < ASSOCDATA_MAX)
|
||
|
{
|
||
|
// subtract the first one to make a zero based offset
|
||
|
int index = data - ASSOCDATA_MSIDESCRIPTOR;
|
||
|
if (!pszCue)
|
||
|
pszCue = s_rgaqxDatas[index].pszCue;
|
||
|
|
||
|
if (s_rgaqxDatas[index].query)
|
||
|
{
|
||
|
if (pcbOut)
|
||
|
{
|
||
|
FLAGGED_BYTE_BLOB *pblob;
|
||
|
hr = QueryDirect(_MaskFromFlags(flags), s_rgaqxDatas[index].query, pszCue, &pblob);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _CopyDataOut(flags & ASSOCF_NOTRUNCATE, pblob, pvOut, pcbOut);
|
||
|
CoTaskMemFree(pblob);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hr = QueryExists(_MaskFromFlags(flags), s_rgaqxDatas[index].query, pszCue);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (data == ASSOCDATA_HASPERUSERASSOC)
|
||
|
{
|
||
|
IAssociationElement *pae;
|
||
|
if (_FirstElement(ASSOCELEM_USER, &pae))
|
||
|
{
|
||
|
pae->Release();
|
||
|
hr = S_OK;
|
||
|
}
|
||
|
else
|
||
|
hr = S_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
BOOL _IsSameVerb(PCWSTR pszV1, PCWSTR pszV2)
|
||
|
{
|
||
|
if (!pszV1 && !pszV2)
|
||
|
return TRUE;
|
||
|
else if (pszV1 && pszV2 && 0 == StrCmpIW(pszV1, pszV2))
|
||
|
return TRUE;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::_GetCachedVerbElement(ASSOCELEM_MASK mask, PCWSTR pszVerb, IAssociationElement **ppaeVerb, IAssociationElement **ppaeElem)
|
||
|
{
|
||
|
if (_paeVerb
|
||
|
&& mask == _maskVerb
|
||
|
&& _IsSameVerb(pszVerb, _pszVerb))
|
||
|
{
|
||
|
*ppaeVerb = _paeVerb;
|
||
|
_paeVerb->AddRef();
|
||
|
|
||
|
if (ppaeElem)
|
||
|
{
|
||
|
*ppaeElem = _paeVerbParent;
|
||
|
_paeVerbParent->AddRef();
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*ppaeVerb = NULL;
|
||
|
|
||
|
if (ppaeElem)
|
||
|
*ppaeElem = NULL;
|
||
|
return E_FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAssocArray::_SetCachedVerbElement(ASSOCELEM_MASK mask, PCWSTR pszVerb, IAssociationElement *paeVerb, IAssociationElement *paeVerbParent)
|
||
|
{
|
||
|
if (_paeVerb)
|
||
|
{
|
||
|
ATOMICRELEASE(_paeVerb);
|
||
|
ATOMICRELEASE(_paeVerbParent);
|
||
|
if (_pszVerb)
|
||
|
LocalFree(_pszVerb);
|
||
|
}
|
||
|
|
||
|
if (pszVerb)
|
||
|
_pszVerb = StrDupW(pszVerb);
|
||
|
else
|
||
|
_pszVerb = NULL;
|
||
|
|
||
|
if (_pszVerb || !pszVerb)
|
||
|
{
|
||
|
_paeVerb = paeVerb;
|
||
|
_paeVerb->AddRef();
|
||
|
_paeVerbParent = paeVerbParent;
|
||
|
_paeVerbParent->AddRef();
|
||
|
_maskVerb = mask;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::_GetVerbElement(ASSOCELEM_MASK mask, PCWSTR pszVerb, IAssociationElement **ppaeVerb, IAssociationElement **ppaeElem)
|
||
|
{
|
||
|
HRESULT hr = _GetCachedVerbElement(mask, pszVerb, ppaeVerb, ppaeElem);
|
||
|
IAssociationElement *pae;
|
||
|
for (int i = 0; FAILED(hr) && TRYNEXT(GetElement(i, mask, &pae)); i++)
|
||
|
{
|
||
|
if (pae)
|
||
|
{
|
||
|
hr = pae->QueryObject(AQVO_SHELLVERB_DELEGATE, pszVerb, IID_PPV_ARG(IAssociationElement, ppaeVerb));
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
_SetCachedVerbElement(mask, pszVerb, *ppaeVerb, pae);
|
||
|
if (ppaeElem)
|
||
|
{
|
||
|
pae->AddRef();
|
||
|
*ppaeElem = pae;
|
||
|
}
|
||
|
}
|
||
|
pae->Release();
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::GetKey(ASSOCF flags, ASSOCKEY key, LPCTSTR pszCue, HKEY *phkey)
|
||
|
{
|
||
|
HRESULT hr = E_INVALIDARG;
|
||
|
*phkey = NULL;
|
||
|
|
||
|
if (key && key < ASSOCKEY_MAX)
|
||
|
{
|
||
|
IAssociationElement *pae;
|
||
|
switch (key)
|
||
|
{
|
||
|
case ASSOCKEY_SHELLEXECCLASS:
|
||
|
{
|
||
|
IAssociationElement *paeVerb;
|
||
|
hr = _GetVerbElement(_MaskFromFlags(flags) & _maskInclude, pszCue, &paeVerb, &pae);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
// we dont use the verb element
|
||
|
paeVerb->Release();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ASSOCKEY_APP:
|
||
|
// get app element
|
||
|
hr = QueryObject(ASSOCELEM_MASK_QUERYNORMAL, AQVO_APPLICATION_DELEGATE, pszCue, IID_PPV_ARG(IAssociationElement, &pae));
|
||
|
break;
|
||
|
|
||
|
case ASSOCKEY_CLASS:
|
||
|
{
|
||
|
hr = _FirstElement(_MaskFromFlags(flags) & _maskInclude, &pae) == GETELEM_SUCCEEDED ? S_OK : E_FAIL;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ASSOCKEY_BASECLASS:
|
||
|
hr = _FirstElement(ASSOCELEM_BASE & _maskInclude, &pae) == GETELEM_SUCCEEDED ? S_OK : E_FAIL;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = AssocKeyFromElement(pae, phkey);
|
||
|
pae->Release();
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::_InsertSingleElement(IAssociationElement *pae)
|
||
|
{
|
||
|
AEINFO ae = {ASSOCELEM_DEFAULT, NULL, NULL, NULL, pae};
|
||
|
ASSERT(!_dsaElems);
|
||
|
if (_dsaElems.Create(1) && (DSA_ERR != _dsaElems.AppendItem(&ae)))
|
||
|
{
|
||
|
pae->AddRef();
|
||
|
return S_OK;
|
||
|
}
|
||
|
return E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
HRESULT CAssocArray::Init(ASSOCF flags, LPCTSTR pszAssoc, HKEY hkProgid, HWND hwnd)
|
||
|
{
|
||
|
IAssociationElement *pae;
|
||
|
HRESULT hr = (pszAssoc || hkProgid) ? S_OK : E_INVALIDARG;
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
_Reset();
|
||
|
_fUsingAppElement = flags & ASSOCF_INIT_BYEXENAME;
|
||
|
|
||
|
if (hkProgid)
|
||
|
{
|
||
|
const CLSID *pclsid;
|
||
|
if (_fUsingAppElement)
|
||
|
pclsid = &CLSID_AssocApplicationElement;
|
||
|
else if (flags & ASSOCF_INIT_NOREMAPCLSID)
|
||
|
pclsid = &CLSID_AssocClsidElement;
|
||
|
else
|
||
|
pclsid = &CLSID_AssocProgidElement;
|
||
|
|
||
|
hr = AssocElemCreateForKey(pclsid, hkProgid, &pae);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _InsertSingleElement(pae);
|
||
|
pae->Release();
|
||
|
}
|
||
|
}
|
||
|
else if (_fUsingAppElement)
|
||
|
{
|
||
|
ASSERT(pszAssoc);
|
||
|
hr = AssocElemCreateForClass(&CLSID_AssocApplicationElement, pszAssoc, &pae);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
hr = _InsertSingleElement(pae);
|
||
|
pae->Release();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ASSERT(pszAssoc);
|
||
|
ASSOCELEM_MASK maskBase = 0;
|
||
|
if (flags & ASSOCF_INIT_DEFAULTTOFOLDER)
|
||
|
maskBase |= ASSOCELEM_BASEIS_FOLDER;
|
||
|
if (flags & ASSOCF_INIT_DEFAULTTOSTAR)
|
||
|
maskBase |= ASSOCELEM_BASEIS_STAR;
|
||
|
|
||
|
if (StrChr(pszAssoc, TEXT('\\')))
|
||
|
pszAssoc = PathFindExtension(pszAssoc);
|
||
|
|
||
|
if (*pszAssoc)
|
||
|
hr = InitClassElements(maskBase, pszAssoc);
|
||
|
else
|
||
|
hr = E_INVALIDARG;
|
||
|
}
|
||
|
}
|
||
|
_hrInit = hr;
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
STDAPI CQueryAssociations_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
|
||
|
{
|
||
|
|
||
|
HRESULT hr = E_INVALIDARG;
|
||
|
|
||
|
if (ppv)
|
||
|
{
|
||
|
*ppv = NULL;
|
||
|
|
||
|
if (punkOuter)
|
||
|
return CLASS_E_NOAGGREGATION;
|
||
|
|
||
|
CAssocArray *passoc = new CAssocArray();
|
||
|
|
||
|
if (passoc)
|
||
|
{
|
||
|
hr = passoc->QueryInterface(riid, ppv);
|
||
|
passoc->Release();
|
||
|
}
|
||
|
else
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|