Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1703 lines
52 KiB

  1. #include "priv.h"
  2. #include "ids.h"
  3. #include "assoc.h"
  4. #include <memt.h>
  5. BOOL _PathIsFile(PCWSTR pszPath)
  6. {
  7. DWORD attrs = GetFileAttributesW(pszPath);
  8. return ((DWORD)-1 != attrs && !(attrs & FILE_ATTRIBUTE_DIRECTORY));
  9. }
  10. BOOL _GetAppPath(PCWSTR pszApp, PWSTR pszExe, DWORD cchExe)
  11. {
  12. WCHAR sz[MAX_PATH];
  13. _MakeAppPathKey(pszApp, sz, SIZECHARS(sz));
  14. DWORD cb = CbFromCchW(cchExe);
  15. return ERROR_SUCCESS == SHGetValueW(HKEY_LOCAL_MACHINE, sz, NULL, NULL, pszExe, &cb);
  16. }
  17. inline HRESULT _QuerySourceCreateFromKey(HKEY hk, PCWSTR pszSub, BOOL fCreate, IQuerySource **ppqs)
  18. {
  19. return QuerySourceCreateFromKey(hk, pszSub, fCreate, IID_PPV_ARG(IQuerySource, ppqs));
  20. }
  21. typedef struct QUERYKEYVAL
  22. {
  23. ASSOCQUERY query;
  24. PCWSTR pszKey;
  25. PCWSTR pszVal;
  26. } QUERYKEYVAL;
  27. #define MAKEQKV(q, k, v) { q, k, v}
  28. static const QUERYKEYVAL s_rgqkvVerb[] =
  29. {
  30. MAKEQKV(AQVS_COMMAND, L"command", NULL),
  31. MAKEQKV(AQVS_DDECOMMAND, L"ddeexec", NULL),
  32. MAKEQKV(AQVS_DDEIFEXEC, L"ddeexec\\ifexec", NULL),
  33. MAKEQKV(AQVS_DDEAPPLICATION, L"ddeexec\\application", NULL),
  34. MAKEQKV(AQVS_DDETOPIC, L"ddeexec\\topic", NULL),
  35. MAKEQKV(AQV_NOACTIVATEHANDLER, L"ddeexec", L"NoActivateHandler"),
  36. MAKEQKV(AQVD_MSIDESCRIPTOR, L"command", L"command"),
  37. MAKEQKV(AQVS_APPLICATION_FRIENDLYNAME, NULL, L"FriendlyAppName"),
  38. };
  39. static const QUERYKEYVAL s_rgqkvShell[] =
  40. {
  41. MAKEQKV(AQS_FRIENDLYTYPENAME, NULL, L"FriendlyTypeName"),
  42. MAKEQKV(AQS_DEFAULTICON, L"DefaultIcon", NULL),
  43. MAKEQKV(AQS_CLSID, L"Clsid", NULL),
  44. MAKEQKV(AQS_PROGID, L"Progid", NULL),
  45. MAKEQKV(AQNS_SHELLEX_HANDLER, L"ShellEx\\%s", NULL),
  46. };
  47. static const QUERYKEYVAL s_rgqkvExt[] =
  48. {
  49. MAKEQKV(AQNS_SHELLEX_HANDLER, L"ShellEx\\%s", NULL),
  50. MAKEQKV(AQS_CONTENTTYPE, NULL, L"Content Type"),
  51. };
  52. static const QUERYKEYVAL s_rgqkvApp[] =
  53. {
  54. MAKEQKV(AQVS_APPLICATION_FRIENDLYNAME, NULL, L"FriendlyAppName"),
  55. };
  56. const QUERYKEYVAL *_FindKeyVal(ASSOCQUERY query, const QUERYKEYVAL *rgQkv, UINT cQkv)
  57. {
  58. for (UINT i = 0; i < cQkv; i++)
  59. {
  60. if (rgQkv[i].query == query)
  61. {
  62. return &rgQkv[i];
  63. }
  64. }
  65. return NULL;
  66. }
  67. HRESULT _SHAllocMUI(LPWSTR *ppsz)
  68. {
  69. WCHAR sz[INFOTIPSIZE];
  70. HRESULT hr = SHLoadIndirectString(*ppsz, sz, ARRAYSIZE(sz), NULL);
  71. CoTaskMemFree(*ppsz);
  72. if (SUCCEEDED(hr))
  73. hr = SHStrDupW(sz, ppsz);
  74. else
  75. *ppsz = 0;
  76. return hr;
  77. }
  78. HRESULT CALLBACK _QuerySourceString(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, PWSTR *ppsz)
  79. {
  80. HRESULT hr = pqs->QueryValueString(pszKey, pszValue, ppsz);
  81. if (SUCCEEDED(hr) && (query & AQF_MUISTRING))
  82. {
  83. // NOTE - this sucks for stack usage.
  84. // since there is currently no way to get
  85. // the size of the target.
  86. hr = _SHAllocMUI(ppsz);
  87. }
  88. return hr;
  89. }
  90. HRESULT CALLBACK _QuerySourceDirect(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, FLAGGED_BYTE_BLOB **ppblob)
  91. {
  92. return pqs->QueryValueDirect(pszKey, pszValue, ppblob);
  93. }
  94. HRESULT CALLBACK _QuerySourceExists(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, void *pv)
  95. {
  96. return pqs->QueryValueExists(pszKey, pszValue);
  97. }
  98. HRESULT CALLBACK _QuerySourceDword(IQuerySource *pqs, ASSOCQUERY query, PCWSTR pszKey, PCWSTR pszValue, DWORD *pdw)
  99. {
  100. return pqs->QueryValueDword(pszKey, pszValue, pdw);
  101. }
  102. class CAssocElement : public IObjectWithQuerySource,
  103. public IAssociationElement
  104. {
  105. public:
  106. CAssocElement() : _cRef(1), _pqs(0) {}
  107. virtual ~CAssocElement() { ATOMICRELEASE(_pqs); }
  108. // IUnknown refcounting
  109. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  110. STDMETHODIMP_(ULONG) AddRef(void)
  111. {
  112. return InterlockedIncrement(&_cRef);
  113. }
  114. STDMETHODIMP_(ULONG) Release(void)
  115. {
  116. LONG cNewRef = InterlockedDecrement(&_cRef);
  117. if (cNewRef == 0)
  118. {
  119. delete this;
  120. }
  121. return cNewRef;
  122. }
  123. // IObjectWithQuerySource
  124. STDMETHODIMP SetSource(IQuerySource *pqs)
  125. {
  126. if (!_pqs)
  127. {
  128. _pqs = pqs;
  129. _pqs->AddRef();
  130. return S_OK;
  131. }
  132. return E_UNEXPECTED;
  133. }
  134. STDMETHODIMP GetSource(REFIID riid, void **ppv)
  135. {
  136. if (_pqs)
  137. {
  138. return _pqs->QueryInterface(riid, ppv);
  139. }
  140. *ppv = NULL;
  141. return E_NOINTERFACE;
  142. }
  143. // IAssociationElement
  144. STDMETHODIMP QueryString(
  145. ASSOCQUERY query,
  146. PCWSTR pszCue,
  147. PWSTR *ppsz)
  148. {
  149. *ppsz = 0;
  150. return _QuerySourceAny(_QuerySourceString, _pqs, (ASSOCQUERY)(AQF_DIRECT | AQF_STRING), query, pszCue, ppsz);
  151. }
  152. STDMETHODIMP QueryDword(
  153. ASSOCQUERY query,
  154. PCWSTR pszCue,
  155. DWORD *pdw)
  156. {
  157. return _QuerySourceAny(_QuerySourceDword, _pqs, (ASSOCQUERY)(AQF_DIRECT | AQF_DWORD), query, pszCue, pdw);
  158. }
  159. STDMETHODIMP QueryExists(
  160. ASSOCQUERY query,
  161. PCWSTR pszCue)
  162. {
  163. return _QuerySourceAny(_QuerySourceExists, _pqs, (ASSOCQUERY)(AQF_DIRECT | AQF_EXISTS), query, pszCue, (void*)NULL);
  164. }
  165. STDMETHODIMP QueryDirect(
  166. ASSOCQUERY query,
  167. PCWSTR pszCue,
  168. FLAGGED_BYTE_BLOB **ppblob)
  169. {
  170. *ppblob = 0;
  171. return _QuerySourceAny(_QuerySourceDirect, _pqs, AQF_DIRECT, query, pszCue, ppblob);
  172. }
  173. STDMETHODIMP QueryObject(
  174. ASSOCQUERY query,
  175. PCWSTR pszCue,
  176. REFIID riid,
  177. void **ppv)
  178. {
  179. *ppv = 0;
  180. return E_NOTIMPL;
  181. }
  182. protected:
  183. 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)
  184. {
  185. HRESULT hr = E_INVALIDARG;
  186. const QUERYKEYVAL *pqkv = _FindKeyVal(query, rgQkv, cQkv);
  187. if (pqkv)
  188. {
  189. WCHAR szKey[128];
  190. PCWSTR pszKey = pqkv->pszKey;
  191. if (query & AQF_CUEIS_NAME)
  192. {
  193. if (pqkv->pszKey)
  194. {
  195. wnsprintfW(szKey, ARRAYSIZE(szKey), pqkv->pszKey, pszCue);
  196. pszKey = szKey;
  197. }
  198. // wnsprintf(szVal, ARRAYSIZE(szVal), pqkv->pszVal, pszCue);
  199. }
  200. hr = pfnAny(pqs, query, pszKey, pqkv->pszVal, pData);
  201. }
  202. return hr;
  203. }
  204. 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)
  205. {
  206. HRESULT hr = E_INVALIDARG;
  207. if (pqs)
  208. {
  209. if (query == AQN_NAMED_VALUE || query == AQNS_NAMED_MUI_STRING)
  210. {
  211. hr = pfnAny(pqs, query, NULL, pszCue, pData);
  212. }
  213. else if ((query & (mask)) == (mask))
  214. {
  215. const QUERYKEYVAL *rgQkv;
  216. UINT cQkv = _GetQueryKeyVal(&rgQkv);
  217. if (cQkv)
  218. {
  219. hr = _QueryKeyValAny(pfnAny, rgQkv, cQkv, pqs, query, pszCue, pData);
  220. }
  221. }
  222. }
  223. return hr;
  224. }
  225. virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv) { *prgQkv = 0; return 0; }
  226. protected:
  227. LONG _cRef;
  228. IQuerySource *_pqs;
  229. };
  230. HRESULT CAssocElement::QueryInterface(REFIID riid, void **ppv)
  231. {
  232. static const QITAB qit[] =
  233. {
  234. QITABENT(CAssocElement, IAssociationElement),
  235. QITABENT(CAssocElement, IObjectWithQuerySource),
  236. { 0 },
  237. };
  238. return QISearch(this, qit, riid, ppv);
  239. }
  240. HRESULT _QueryString(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  241. {
  242. return pae->QueryString(query, pszCue, ppsz);
  243. }
  244. HRESULT _QueryDirect(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, FLAGGED_BYTE_BLOB **ppblob)
  245. {
  246. return pae->QueryDirect(query, pszCue, ppblob);
  247. }
  248. HRESULT _QueryDword(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, DWORD *pdw)
  249. {
  250. return pae->QueryDword(query, pszCue, pdw);
  251. }
  252. HRESULT _QueryExists(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, void *pv)
  253. {
  254. return pae->QueryExists(query, pszCue);
  255. }
  256. class CAssocShellElement : public CAssocElement, public IPersistString2
  257. {
  258. public:
  259. virtual ~CAssocShellElement() { if (_pszInit && _pszInit != _szInit) LocalFree(_pszInit);}
  260. // IUnknown refcounting
  261. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  262. STDMETHODIMP_(ULONG) AddRef(void)
  263. {
  264. return ++_cRef;
  265. }
  266. STDMETHODIMP_(ULONG) Release(void)
  267. {
  268. if (--_cRef > 0)
  269. return _cRef;
  270. delete this;
  271. return 0;
  272. }
  273. // IPersist
  274. STDMETHODIMP GetClassID(CLSID *pclsid)
  275. { *pclsid = CLSID_AssocShellElement; return S_OK;}
  276. // IPersistString2
  277. STDMETHODIMP SetString(PCWSTR psz)
  278. {
  279. if (!_pszInit)
  280. {
  281. int cchInit;
  282. DWORD cch = lstrlenW(psz);
  283. if (cch < ARRAYSIZE(_szInit))
  284. {
  285. _pszInit = _szInit;
  286. cchInit = ARRAYSIZE(_szInit);
  287. }
  288. else
  289. {
  290. SHLocalAlloc(CbFromCchW(cch + 1), &_pszInit);
  291. cchInit = cch + 1;
  292. }
  293. if (_pszInit)
  294. {
  295. StringCchCopyW(_pszInit, cchInit, psz);
  296. return _InitSource();
  297. }
  298. }
  299. return E_UNEXPECTED;
  300. }
  301. STDMETHODIMP GetString(PWSTR *ppsz)
  302. { return SHStrDupW(_pszInit, ppsz); }
  303. // IAssociationElement
  304. STDMETHODIMP QueryString(
  305. ASSOCQUERY query,
  306. PCWSTR pszCue,
  307. PWSTR *ppsz)
  308. {
  309. if (AQF_CUEIS_SHELLVERB & query)
  310. return _QueryVerbAny(_QueryString, query, pszCue, ppsz);
  311. else
  312. return CAssocElement::QueryString(query, pszCue, ppsz);
  313. }
  314. STDMETHODIMP QueryDword(
  315. ASSOCQUERY query,
  316. PCWSTR pszCue,
  317. DWORD *pdw)
  318. {
  319. if (AQF_CUEIS_SHELLVERB & query)
  320. return _QueryVerbAny(_QueryDword, query, pszCue, pdw);
  321. else
  322. return CAssocElement::QueryDword(query, pszCue, pdw);
  323. }
  324. STDMETHODIMP QueryExists(
  325. ASSOCQUERY query,
  326. PCWSTR pszCue)
  327. {
  328. if (AQF_CUEIS_SHELLVERB & query)
  329. return _QueryVerbAny(_QueryExists, query, pszCue, (void*)NULL);
  330. else
  331. return CAssocElement::QueryExists(query, pszCue);
  332. }
  333. STDMETHODIMP QueryDirect(
  334. ASSOCQUERY query,
  335. PCWSTR pszCue,
  336. FLAGGED_BYTE_BLOB **ppblob)
  337. {
  338. if (AQF_CUEIS_SHELLVERB & query)
  339. return _QueryVerbAny(_QueryDirect, query, pszCue, ppblob);
  340. else
  341. return CAssocElement::QueryDirect(query, pszCue, ppblob);
  342. }
  343. STDMETHODIMP QueryObject(
  344. ASSOCQUERY query,
  345. PCWSTR pszCue,
  346. REFIID riid,
  347. void **ppv);
  348. protected:
  349. template<class T> HRESULT _QueryVerbAny(HRESULT (CALLBACK *pfnAny)(IAssociationElement *pae, ASSOCQUERY query, PCWSTR pszCue, T pData), ASSOCQUERY query, PCWSTR pszCue, T pData)
  350. {
  351. IAssociationElement *pae;
  352. HRESULT hr = _GetVerbDelegate(pszCue, &pae);
  353. if (SUCCEEDED(hr))
  354. {
  355. hr = pfnAny(pae, query, NULL, pData);
  356. pae->Release();
  357. }
  358. return hr;
  359. }
  360. // from CAssocElement
  361. virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv)
  362. { *prgQkv = s_rgqkvShell; return ARRAYSIZE(s_rgqkvShell); }
  363. // defaults for our subclasses
  364. virtual BOOL _UseEnumForDefaultVerb()
  365. { return FALSE;}
  366. virtual HRESULT _InitSource()
  367. { return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, _pszInit, FALSE, &_pqs); }
  368. virtual BOOL _IsAppSource()
  369. { return FALSE; }
  370. HRESULT _GetVerbDelegate(PCWSTR pszVerb, IAssociationElement **ppae);
  371. HRESULT _DefaultVerbSource(IQuerySource **ppqsVerb);
  372. HRESULT _QueryShellExtension(PCWSTR pszShellEx, PWSTR *ppsz);
  373. protected:
  374. PWSTR _pszInit;
  375. WCHAR _szInit[64];
  376. };
  377. HRESULT CAssocShellElement::QueryInterface(REFIID riid, void **ppv)
  378. {
  379. static const QITAB qit[] =
  380. {
  381. QITABENT(CAssocShellElement, IAssociationElement),
  382. QITABENT(CAssocShellElement, IObjectWithQuerySource),
  383. QITABENT(CAssocShellElement, IPersistString2),
  384. QITABENTMULTI(CAssocShellElement, IPersist, IPersistString2),
  385. { 0 },
  386. };
  387. return QISearch(this, qit, riid, ppv);
  388. }
  389. class CAssocProgidElement : public CAssocShellElement
  390. {
  391. public:
  392. virtual ~CAssocProgidElement() { ATOMICRELEASE(_pqsExt); }
  393. // then we handle fallback for IAssociationElement
  394. STDMETHODIMP QueryString(
  395. ASSOCQUERY query,
  396. PCWSTR pszCue,
  397. PWSTR *ppsz);
  398. // IPersist
  399. STDMETHODIMP GetClassID(CLSID *pclsid)
  400. { *pclsid = CLSID_AssocProgidElement; return S_OK;}
  401. protected: // methods
  402. HRESULT _InitSource();
  403. HRESULT _DefaultVerbSource(IQuerySource **ppqsVerb);
  404. BOOL _UseEnumForDefaultVerb()
  405. { return TRUE; }
  406. protected: // members
  407. IQuerySource *_pqsExt;
  408. };
  409. HRESULT _QuerySourceCreateFromKey2(HKEY hk, PCWSTR pszSub1, PCWSTR pszSub2, IQuerySource **ppqs)
  410. {
  411. WCHAR szKey[MAX_PATH];
  412. _PathAppend(pszSub1, pszSub2, szKey, SIZECHARS(szKey));
  413. return _QuerySourceCreateFromKey(hk, szKey, FALSE, ppqs);
  414. }
  415. class CAssocClsidElement : public CAssocShellElement
  416. {
  417. public:
  418. // IPersist
  419. STDMETHODIMP GetClassID(CLSID *pclsid)
  420. { *pclsid = CLSID_AssocClsidElement; return S_OK;}
  421. protected:
  422. virtual HRESULT _InitSource()
  423. { return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"CLSID", _pszInit, &_pqs);}
  424. };
  425. class CAssocSystemExtElement : public CAssocShellElement
  426. {
  427. public:
  428. // IPersist
  429. STDMETHODIMP GetClassID(CLSID *pclsid)
  430. { *pclsid = CLSID_AssocSystemElement; return S_OK;}
  431. protected:
  432. virtual HRESULT _InitSource()
  433. { return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"SystemFileAssociations", _pszInit, &_pqs);}
  434. };
  435. class CAssocPerceivedElement : public CAssocShellElement
  436. {
  437. public:
  438. // IPersist
  439. STDMETHODIMP GetClassID(CLSID *pclsid)
  440. { *pclsid = CLSID_AssocPerceivedElement; return S_OK;}
  441. protected:
  442. virtual HRESULT _InitSource();
  443. // maybe _GetVerbDelegate() to support Accepts filters
  444. };
  445. class CAssocApplicationElement : public CAssocShellElement
  446. {
  447. public:
  448. // need to fallback to the pszInit for FriendlyAppName
  449. STDMETHODIMP QueryString(
  450. ASSOCQUERY query,
  451. PCWSTR pszCue,
  452. PWSTR *ppsz);
  453. STDMETHODIMP QueryObject(
  454. ASSOCQUERY query,
  455. PCWSTR pszCue,
  456. REFIID riid,
  457. void **ppv);
  458. // IPersist
  459. STDMETHODIMP GetClassID(CLSID *pclsid)
  460. { *pclsid = CLSID_AssocApplicationElement; return S_OK;}
  461. protected:
  462. virtual HRESULT _InitSource();
  463. virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv)
  464. { *prgQkv = s_rgqkvApp; return ARRAYSIZE(s_rgqkvApp); }
  465. virtual BOOL _IsAppSource()
  466. { return TRUE; }
  467. BOOL _UseEnumForDefaultVerb()
  468. { return TRUE; }
  469. HRESULT _GetAppDisplayName(PWSTR *ppsz);
  470. protected:
  471. BOOL _fIsPath;
  472. };
  473. HRESULT CAssocApplicationElement::_GetAppDisplayName(PWSTR *ppsz)
  474. {
  475. HRESULT hr;
  476. PWSTR pszPath;
  477. if (_fIsPath)
  478. {
  479. hr = S_OK;
  480. pszPath = _pszInit;
  481. ASSERT(pszPath);
  482. }
  483. else
  484. hr = QueryString(AQVS_APPLICATION_PATH, NULL, &pszPath);
  485. if (SUCCEEDED(hr))
  486. {
  487. WCHAR sz[MAX_PATH];
  488. DWORD cb = sizeof(sz);
  489. hr = SKGetValueW(SHELLKEY_HKCULM_MUICACHE, NULL, pszPath, NULL, sz, &cb);
  490. if (FAILED(hr))
  491. {
  492. UINT cch = ARRAYSIZE(sz);
  493. if (SHGetFileDescriptionW(pszPath, NULL, NULL, sz, &cch))
  494. {
  495. hr = S_OK;
  496. SKSetValueW(SHELLKEY_HKCULM_MUICACHE, NULL, pszPath, REG_SZ, sz, CbFromCchW(lstrlenW(sz) + 1));
  497. }
  498. }
  499. if (SUCCEEDED(hr))
  500. hr = SHStrDupW(sz, ppsz);
  501. if (pszPath != _pszInit)
  502. CoTaskMemFree(pszPath);
  503. }
  504. return hr;
  505. }
  506. HRESULT CAssocApplicationElement::_InitSource()
  507. {
  508. WCHAR sz[MAX_PATH];
  509. PCWSTR pszName = PathFindFileNameW(_pszInit);
  510. _MakeApplicationsKey(pszName, sz, ARRAYSIZE(sz));
  511. HRESULT hr = _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, sz, FALSE, &_pqs);
  512. _fIsPath = pszName != _pszInit;
  513. if (FAILED(hr))
  514. {
  515. if (_fIsPath && PathFileExistsW(_pszInit))
  516. hr = S_FALSE;
  517. }
  518. return hr;
  519. }
  520. HRESULT CAssocApplicationElement::QueryObject(ASSOCQUERY query, PCWSTR pszCue, REFIID riid, void **ppv)
  521. {
  522. if (query == AQVO_APPLICATION_DELEGATE)
  523. {
  524. return QueryInterface(riid, ppv);
  525. }
  526. return CAssocShellElement::QueryObject(query, pszCue, riid, ppv);
  527. }
  528. HRESULT CAssocApplicationElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  529. {
  530. HRESULT hr = CAssocShellElement::QueryString(query, pszCue, ppsz);
  531. if (FAILED(hr))
  532. {
  533. switch (query)
  534. {
  535. case AQVS_APPLICATION_FRIENDLYNAME:
  536. hr = _GetAppDisplayName(ppsz);
  537. break;
  538. }
  539. }
  540. return hr;
  541. }
  542. class CAssocShellVerbElement : public CAssocElement
  543. {
  544. public:
  545. CAssocShellVerbElement(BOOL fIsApp) : _fIsApp(fIsApp) {}
  546. // overload QS to return default DDEExec strings
  547. STDMETHODIMP QueryString(
  548. ASSOCQUERY query,
  549. PCWSTR pszCue,
  550. PWSTR *ppsz);
  551. STDMETHODIMP QueryObject(
  552. ASSOCQUERY query,
  553. PCWSTR pszCue,
  554. REFIID riid,
  555. void **ppv);
  556. protected:
  557. virtual UINT _GetQueryKeyVal(const QUERYKEYVAL **prgQkv)
  558. { *prgQkv = s_rgqkvVerb; return ARRAYSIZE(s_rgqkvVerb); }
  559. HRESULT _GetAppDelegate(REFIID riid, void **ppv);
  560. protected:
  561. BOOL _fIsApp;
  562. };
  563. class CAssocFolderElement : public CAssocShellElement
  564. {
  565. public:
  566. // overload QS to return default MUI strings
  567. STDMETHODIMP QueryString(
  568. ASSOCQUERY query,
  569. PCWSTR pszCue,
  570. PWSTR *ppsz);
  571. // IPersist
  572. STDMETHODIMP GetClassID(CLSID *pclsid)
  573. { *pclsid = CLSID_AssocFolderElement; return S_OK;}
  574. protected:
  575. virtual HRESULT _InitSource()
  576. { return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"Folder", FALSE, &_pqs); }
  577. };
  578. class CAssocStarElement : public CAssocShellElement
  579. {
  580. public:
  581. // overload QS to return default MUI strings
  582. STDMETHODIMP QueryString(
  583. ASSOCQUERY query,
  584. PCWSTR pszCue,
  585. PWSTR *ppsz);
  586. // IPersist
  587. STDMETHODIMP GetClassID(CLSID *pclsid)
  588. { *pclsid = CLSID_AssocStarElement; return S_OK;}
  589. protected:
  590. virtual HRESULT _InitSource()
  591. { return _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, L"*", FALSE, &_pqs); }
  592. };
  593. HRESULT CAssocShellElement::_DefaultVerbSource(IQuerySource **ppqsVerb)
  594. {
  595. IQuerySource *pqsShell;
  596. HRESULT hr = _pqs->OpenSource(L"shell", FALSE, &pqsShell);
  597. if (SUCCEEDED(hr))
  598. {
  599. PWSTR pszFree = NULL;
  600. PCWSTR pszVerb;
  601. // see if something is specified...
  602. if (SUCCEEDED(pqsShell->QueryValueString(NULL, NULL, &pszFree)))
  603. {
  604. pszVerb = pszFree;
  605. }
  606. else
  607. {
  608. // default to "open"
  609. pszVerb = L"open";
  610. }
  611. hr = pqsShell->OpenSource(pszVerb, FALSE, ppqsVerb);
  612. if (FAILED(hr))
  613. {
  614. if (pszFree)
  615. {
  616. // try to find one of the ordered verbs
  617. int c = StrCSpnW(pszFree, L" ,");
  618. if (c != lstrlenW(pszFree))
  619. {
  620. pszFree[c] = 0;
  621. hr = pqsShell->OpenSource(pszFree, FALSE, ppqsVerb);
  622. }
  623. }
  624. else if (_UseEnumForDefaultVerb())
  625. {
  626. // APPCOMPAT - regitems need to have the open verb - ZekeL - 30-JAN-2001
  627. // so that the IQA and ICM will behave the same,
  628. // and regitem folders will always default to
  629. // folder\shell\open unless they implement open
  630. // or specify default verbs.
  631. //
  632. // everything else, just use the first key we find....
  633. IEnumString *penum;
  634. if (SUCCEEDED(pqsShell->EnumSources(&penum)))
  635. {
  636. ULONG c;
  637. CSmartCoTaskMem<OLECHAR> spszEnum;
  638. if (S_OK == penum->Next(1, &spszEnum, &c))
  639. {
  640. hr = pqsShell->OpenSource(spszEnum, FALSE, ppqsVerb);
  641. }
  642. penum->Release();
  643. }
  644. }
  645. }
  646. if (pszFree)
  647. CoTaskMemFree(pszFree);
  648. pqsShell->Release();
  649. }
  650. return hr;
  651. }
  652. HRESULT QSOpen2(IQuerySource *pqs, PCWSTR pszSub1, PCWSTR pszSub2, BOOL fCreate, IQuerySource **ppqs)
  653. {
  654. WCHAR szKey[MAX_PATH];
  655. _PathAppend(pszSub1, pszSub2, szKey, SIZECHARS(szKey));
  656. return pqs->OpenSource(szKey, fCreate, ppqs);
  657. }
  658. HRESULT CAssocShellElement::_GetVerbDelegate(PCWSTR pszVerb, IAssociationElement **ppae)
  659. {
  660. HRESULT hr = _pqs ? S_OK : E_FAIL;
  661. if (SUCCEEDED(hr))
  662. {
  663. // we will recalc each time.
  664. // the array will cache appropriately
  665. IQuerySource *pqs;
  666. if (pszVerb)
  667. {
  668. hr = QSOpen2(_pqs, L"shell", pszVerb, FALSE, &pqs);
  669. }
  670. else
  671. {
  672. hr = _DefaultVerbSource(&pqs);
  673. }
  674. if (SUCCEEDED(hr))
  675. {
  676. CAssocShellVerbElement *pave = new CAssocShellVerbElement(_IsAppSource());
  677. if (pave)
  678. {
  679. hr = pave->SetSource(pqs);
  680. // this cant fail...
  681. ASSERT(SUCCEEDED(hr));
  682. *ppae = pave;
  683. }
  684. else
  685. hr = E_OUTOFMEMORY;
  686. pqs->Release();
  687. }
  688. }
  689. return hr;
  690. }
  691. HRESULT CAssocShellElement::QueryObject(ASSOCQUERY query, PCWSTR pszCue, REFIID riid, void **ppv)
  692. {
  693. HRESULT hr = E_INVALIDARG;
  694. if (AQF_CUEIS_SHELLVERB & query)
  695. {
  696. IAssociationElement *pae;
  697. hr = _GetVerbDelegate(pszCue, &pae);
  698. if (SUCCEEDED(hr))
  699. {
  700. if (AQVO_SHELLVERB_DELEGATE == query)
  701. hr = pae->QueryInterface(riid, ppv);
  702. else
  703. hr = pae->QueryObject(query, NULL, riid, ppv);
  704. pae->Release();
  705. }
  706. }
  707. return hr;
  708. }
  709. HKEY _OpenProgidKey(PCWSTR pszProgid)
  710. {
  711. HKEY hkOut;
  712. if (SUCCEEDED(_AssocOpenRegKey(HKEY_CLASSES_ROOT, pszProgid, &hkOut)))
  713. {
  714. // Check for a newer version of the ProgID
  715. WCHAR sz[64];
  716. DWORD cb = sizeof(sz);
  717. //
  718. // APPCOMPAT LEGACY - Quattro Pro 2000 and Excel 2000 dont get along - ZekeL - 7-MAR-2000
  719. // mill bug #129525. the problem is if Quattro is installed
  720. // first, then excel picks up quattro's CurVer key for some
  721. // reason. then we end up using Quattro.Worksheet as the current
  722. // version of the Excel.Sheet. this is bug in both of their code.
  723. // since quattro cant even open the file when we give it to them,
  724. // they never should take the assoc in the first place, and when excel
  725. // takes over it shouldnt have preserved the CurVer key from the
  726. // previous association. we could add some code to insure that the
  727. // CurVer key follows the OLE progid naming conventions and that it must
  728. // be derived from the same app name as the progid in order to take
  729. // precedence but for now we will block CurVer from working whenever
  730. // the progid is excel.sheet.8 (excel 2000)
  731. //
  732. if (StrCmpIW(L"Excel.Sheet.8", pszProgid)
  733. && ERROR_SUCCESS == SHGetValueW(hkOut, L"CurVer", NULL, NULL, sz, &cb)
  734. && (cb > sizeof(WCHAR)))
  735. {
  736. // cache this bubby
  737. HKEY hkTemp = hkOut;
  738. if (SUCCEEDED(_AssocOpenRegKey(HKEY_CLASSES_ROOT, sz, &hkOut)))
  739. {
  740. //
  741. // APPCOMPAT LEGACY - order of preference - ZekeL - 22-JUL-99
  742. // this is to support associations that installed empty curver
  743. // keys, like microsoft project.
  744. //
  745. // 1. curver with shell subkey
  746. // 2. progid with shell subkey
  747. // 3. curver without shell subkey
  748. // 4. progid without shell subkey
  749. //
  750. HKEY hkShell;
  751. if (SUCCEEDED(_AssocOpenRegKey(hkOut, L"shell", &hkShell)))
  752. {
  753. RegCloseKey(hkShell);
  754. RegCloseKey(hkTemp); // close old ProgID key
  755. }
  756. else if (SUCCEEDED(_AssocOpenRegKey(hkTemp, L"shell", &hkShell)))
  757. {
  758. RegCloseKey(hkShell);
  759. RegCloseKey(hkOut);
  760. hkOut = hkTemp;
  761. }
  762. else
  763. RegCloseKey(hkTemp);
  764. }
  765. else // reset!
  766. hkOut = hkTemp;
  767. }
  768. }
  769. return hkOut;
  770. }
  771. HRESULT CAssocProgidElement::_InitSource()
  772. {
  773. HRESULT hr = S_OK;
  774. // we need to init from an extension or Progid.
  775. // we also support redirection
  776. LPWSTR pszProgid;
  777. if (_pszInit[0] == L'.')
  778. {
  779. hr = _QuerySourceCreateFromKey(HKEY_CLASSES_ROOT, _pszInit, FALSE, &_pqsExt);
  780. if (SUCCEEDED(hr))
  781. hr = _pqsExt->QueryValueString(NULL, NULL, &pszProgid);
  782. }
  783. else
  784. pszProgid = _pszInit;
  785. if (SUCCEEDED(hr))
  786. {
  787. HKEY hk = _OpenProgidKey(pszProgid);
  788. if (hk)
  789. {
  790. hr = _QuerySourceCreateFromKey(hk, NULL, FALSE, &_pqs);
  791. RegCloseKey(hk);
  792. }
  793. else
  794. hr = E_UNEXPECTED;
  795. if (pszProgid != _pszInit)
  796. CoTaskMemFree(pszProgid);
  797. }
  798. // for legacy compat reasons, we support
  799. // falling back to "HKEY_CLASSES_ROOT\.ext"
  800. if (FAILED(hr) && _pqsExt)
  801. {
  802. _pqs = _pqsExt;
  803. _pqsExt = NULL;
  804. hr = S_FALSE;
  805. }
  806. return hr;
  807. }
  808. HRESULT CAssocProgidElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  809. {
  810. HRESULT hr = CAssocShellElement::QueryString(query, pszCue, ppsz);
  811. if (FAILED(hr))
  812. {
  813. if ((AQF_QUERY_INITCLASS & query) && _pqsExt)
  814. hr = _QueryKeyValAny(_QuerySourceString, s_rgqkvExt, ARRAYSIZE(s_rgqkvExt), _pqsExt, query, pszCue, ppsz);
  815. else if (_pqs)
  816. {
  817. switch (query)
  818. {
  819. case AQS_FRIENDLYTYPENAME:
  820. // we like to query the default value
  821. hr = _pqs->QueryValueString(NULL, NULL, ppsz);
  822. break;
  823. }
  824. }
  825. }
  826. return hr;
  827. }
  828. STDAPI _SHAllocLoadString(HINSTANCE hinst, int ids, PWSTR *ppsz)
  829. {
  830. WCHAR sz[MAX_PATH];
  831. LoadStringW(hinst, ids, sz, ARRAYSIZE(sz));
  832. return SHStrDupW(sz, ppsz);
  833. }
  834. HRESULT CAssocFolderElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  835. {
  836. if (query == AQS_FRIENDLYTYPENAME)
  837. return _SHAllocLoadString(HINST_THISDLL, IDS_FOLDERTYPENAME, ppsz);
  838. else
  839. return CAssocShellElement::QueryString(query, pszCue, ppsz);
  840. }
  841. HRESULT _GetFileTypeName(PWSTR pszExt, PWSTR *ppsz)
  842. {
  843. if (pszExt && pszExt[0] == L'.' && pszExt[1])
  844. {
  845. WCHAR sz[MAX_PATH];
  846. WCHAR szTemplate[128]; // "%s File"
  847. CharUpperW(pszExt);
  848. LoadStringW(HINST_THISDLL, IDS_EXTTYPETEMPLATE, szTemplate, ARRAYSIZE(szTemplate));
  849. wnsprintfW(sz, ARRAYSIZE(sz), szTemplate, pszExt + 1);
  850. return SHStrDupW(sz, ppsz);
  851. }
  852. else
  853. {
  854. // load the file description "File"
  855. return _SHAllocLoadString(HINST_THISDLL, IDS_FILETYPENAME, ppsz);
  856. }
  857. }
  858. HRESULT CAssocStarElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  859. {
  860. if (query == AQS_FRIENDLYTYPENAME)
  861. return _GetFileTypeName(_pszInit, ppsz);
  862. else
  863. return CAssocShellElement::QueryString(query, pszCue, ppsz);
  864. }
  865. #define IsWhite(c) ((DWORD) (c) > 32 ? FALSE : TRUE)
  866. BOOL PathIsAbsolute(PCWSTR pszPath)
  867. {
  868. return PathIsUNCW(pszPath) || ((-1 != PathGetDriveNumberW(pszPath)) && (pszPath[2] == L'\\'));
  869. }
  870. inline HRESULT _PathExeExists(PWSTR pszPath)
  871. {
  872. DWORD attrs;
  873. return (PathFileExistsDefExtAndAttributesW(pszPath, PFOPEX_CMD | PFOPEX_COM | PFOPEX_BAT | PFOPEX_PIF | PFOPEX_EXE | PFOPEX_OPTIONAL, &attrs) && !(attrs & FILE_ATTRIBUTE_DIRECTORY)) ? S_OK : CO_E_APPNOTFOUND;
  874. }
  875. inline HRESULT _PathFileExists(PWSTR pszPath)
  876. {
  877. DWORD attrs;
  878. return (PathFileExistsAndAttributesW(pszPath, &attrs) && !(attrs & FILE_ATTRIBUTE_DIRECTORY)) ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  879. }
  880. HRESULT _CopyExe(PWSTR pszDst, size_t cchDst, PCWSTR pszSrc, size_t cchSrc)
  881. {
  882. *pszDst = 0;
  883. HRESULT hr = StringCchCatNW(pszDst, cchDst, pszSrc, cchSrc);
  884. if (SUCCEEDED(hr))
  885. {
  886. StrTrimW(pszDst, L" \t");
  887. }
  888. return hr;
  889. }
  890. HRESULT _PathFindInFolder(int csidl, PCWSTR pszName, PWSTR pszPath, size_t cchPath)
  891. {
  892. ASSERT(cchPath >= MAX_PATH);
  893. HRESULT hr = SHGetFolderPathW(NULL, csidl, NULL, SHGFP_TYPE_CURRENT, pszPath);
  894. if (SUCCEEDED(hr))
  895. {
  896. StringCchCatW(pszPath, cchPath, L"\\");
  897. hr = StringCchCatW(pszPath, cchPath, pszName);
  898. if (SUCCEEDED(hr))
  899. {
  900. hr = _PathExeExists(pszPath);
  901. }
  902. }
  903. return hr;
  904. }
  905. HRESULT _PathFindInSystem(PWSTR pszExe, size_t cchExe)
  906. {
  907. WCHAR szPath[MAX_PATH];
  908. HRESULT hr = _PathFindInFolder(CSIDL_SYSTEM, pszExe, szPath, ARRAYSIZE(szPath));
  909. if (FAILED(hr))
  910. {
  911. hr = _PathFindInFolder(CSIDL_WINDOWS, pszExe, szPath, ARRAYSIZE(szPath));
  912. }
  913. if (SUCCEEDED(hr))
  914. {
  915. hr = StringCchCopyW(pszExe, cchExe, szPath);
  916. }
  917. return hr;
  918. }
  919. BOOL _PathMatchesSuspicious(PCWSTR pszPath)
  920. {
  921. size_t cch = lstrlenW(pszPath);
  922. WCHAR sz[MAX_PATH];
  923. SHGetFolderPathW(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, sz);
  924. return 0 == StrCmpNIW(pszPath, sz, cch);
  925. }
  926. PWSTR _PathGuessNextBestArgs(PCWSTR pszArgs)
  927. {
  928. PCWSTR pchSpace = NULL;
  929. BOOL fContinue = TRUE;
  930. ASSERT(*pszArgs);
  931. while (fContinue && *pszArgs)
  932. {
  933. switch (*pszArgs)
  934. {
  935. case L'\\':
  936. // only count whacks that arent in quotes
  937. // or preceded by a :, since it looks like a path
  938. fContinue = !PathIsUNCW(pszArgs);
  939. if (fContinue)
  940. {
  941. pchSpace = NULL;
  942. }
  943. break;
  944. case L' ':
  945. if (!pchSpace)
  946. pchSpace = pszArgs;
  947. break;
  948. case L'%':
  949. case L'"':
  950. // we see this as the beginning of the args regardless
  951. // since shellexec() would probably replace it
  952. fContinue = FALSE;
  953. break;
  954. default:
  955. fContinue = PathIsValidCharW(*pszArgs, PIVC_LFN_NAME);
  956. break;
  957. }
  958. pszArgs++;
  959. }
  960. if (pchSpace)
  961. {
  962. while (*pchSpace == L' ')
  963. pchSpace++;
  964. return (PWSTR) pchSpace;
  965. }
  966. else
  967. {
  968. // if we dont want to continue, then
  969. // we return NULL
  970. if (!fContinue)
  971. pszArgs = NULL;
  972. return (PWSTR) pszArgs;
  973. }
  974. }
  975. BOOL _ParamIsApp(PCWSTR pszCmdTemplate)
  976. {
  977. return (0 == StrCmpNW(pszCmdTemplate, L"%1", ARRAYSIZE(L"%1")-1))
  978. || (0 == StrCmpNW(pszCmdTemplate, L"\"%1\"", ARRAYSIZE(L"\"%1\"")-1));
  979. }
  980. LPWSTR _PathGetArgsLikeCreateProcess(LPCWSTR pszPath)
  981. {
  982. //
  983. // NTBUG#634668 - check for quoted commandlines without spaces - ZekeL
  984. // it turns out apps can do something like:
  985. // "c:\pfiles\app.exe""%1"
  986. // "notepad"txt
  987. // notice no space between the params and quoted path.
  988. // CreateProcess(), of course, supports this. so we need to as well...
  989. // we only do it if the original command line was quoted
  990. // then we check for a possible early termination
  991. //
  992. PCWSTR pchArgs = (pszPath[0] == L'"') ? StrChrW(pszPath + 1, L'"') : StrChrW(pszPath, L' ');
  993. if (pchArgs)
  994. {
  995. pchArgs++;
  996. }
  997. else
  998. {
  999. pchArgs = pszPath + lstrlenW(pszPath);
  1000. }
  1001. return (LPWSTR)pchArgs;
  1002. }
  1003. LWSTDAPI SHEvaluateSystemCommandTemplate(PCWSTR pszCmdTemplate, PWSTR *ppszApplication, PWSTR *ppszCommandLine, PWSTR *ppszParameters)
  1004. {
  1005. ASSERT(!IsWhite(pszCmdTemplate[0]));
  1006. PWSTR pchArgs = _PathGetArgsLikeCreateProcess(pszCmdTemplate);
  1007. WCHAR szExe[MAX_PATH];
  1008. // default to using the path in *ppszCommandLine
  1009. PCWSTR pszName = szExe;
  1010. HRESULT hr = _CopyExe(szExe, ARRAYSIZE(szExe), pszCmdTemplate, pchArgs - pszCmdTemplate);
  1011. //
  1012. // We check for %1 since it is what appears under (for example) HKEY_CLASSES_ROOT\exefile\shell\open\command
  1013. // This will save us from hitting the disk with something we know is not there
  1014. // context menu on a shortcut to an .exe or .bat file.
  1015. if (SUCCEEDED(hr))
  1016. {
  1017. BOOL fQuoted = (szExe[0] == L'"');
  1018. if (fQuoted)
  1019. PathUnquoteSpacesW(szExe);
  1020. if (PathIsAbsolute(szExe))
  1021. {
  1022. // do no searching.
  1023. // require quotes based on policy
  1024. // if (fQuoted || !SHIsRestricted(QUOTEDFILEASSOCS) )
  1025. // we should log an event when we restrict
  1026. if (fQuoted || !_PathMatchesSuspicious(szExe))
  1027. hr = _PathExeExists(szExe);
  1028. else
  1029. hr = E_ACCESSDENIED;
  1030. if (FAILED(hr) && !fQuoted && *pchArgs)
  1031. {
  1032. //
  1033. // sometimes the path is not properly quoted.
  1034. // these keys will still work because of the
  1035. // way CreateProcess works, but we need to do
  1036. // some fiddling to figure that out.
  1037. //
  1038. do
  1039. {
  1040. // next space is our break
  1041. pchArgs = _PathGuessNextBestArgs(pchArgs);
  1042. if (pchArgs)
  1043. {
  1044. hr = _CopyExe(szExe, ARRAYSIZE(szExe), pszCmdTemplate, pchArgs - pszCmdTemplate);
  1045. if (SUCCEEDED(hr))
  1046. {
  1047. hr = _PathExeExists(szExe);
  1048. }
  1049. }
  1050. } while (FAILED(hr) && pchArgs && *pchArgs);
  1051. }
  1052. }
  1053. else if (PathIsFileSpecW(szExe))
  1054. {
  1055. if (_GetAppPath(szExe, szExe, ARRAYSIZE(szExe)))
  1056. {
  1057. hr = _PathExeExists(szExe);
  1058. }
  1059. else
  1060. {
  1061. // maybe do our special stuff for system 32?
  1062. // we allow for non-quoted stuff from system32
  1063. // if (!SHIsRestricted(RELATIVEFILEASSOCS)
  1064. hr = _PathFindInSystem(szExe, ARRAYSIZE(szExe));
  1065. // if this failed, should we search spaces???
  1066. // right now assume that no system files have spaces.
  1067. }
  1068. // use the relative name in ppszCommandLine
  1069. pszName = PathFindFileNameW(szExe);
  1070. }
  1071. else
  1072. {
  1073. // disallow most relative paths like \foo.exe or ..\foo.exe
  1074. // since these are completely indeterminate
  1075. hr = E_ACCESSDENIED;
  1076. }
  1077. }
  1078. *ppszApplication = NULL;
  1079. if (ppszCommandLine)
  1080. *ppszCommandLine = NULL;
  1081. if (ppszParameters)
  1082. *ppszParameters = NULL;
  1083. if (SUCCEEDED(hr))
  1084. {
  1085. hr = SHStrDupW(szExe, ppszApplication);
  1086. // if there were no args, then use empty args
  1087. if (!pchArgs)
  1088. pchArgs = L"";
  1089. if (SUCCEEDED(hr) && ppszCommandLine)
  1090. {
  1091. size_t cchCommandLine = lstrlenW(pszName) + lstrlenW(pchArgs) + ARRAYSIZE(L"\"%s\" %s");
  1092. hr = SHCoAlloc(CbFromCchW(cchCommandLine), ppszCommandLine);
  1093. if (SUCCEEDED(hr))
  1094. {
  1095. hr = StringCchPrintfW(*ppszCommandLine, cchCommandLine, L"\"%s\" %s", pszName, pchArgs);
  1096. ASSERT(SUCCEEDED(hr));
  1097. }
  1098. }
  1099. if (SUCCEEDED(hr) && ppszParameters)
  1100. {
  1101. hr = SHStrDupW(pchArgs, ppszParameters);
  1102. }
  1103. if (FAILED(hr))
  1104. {
  1105. if (*ppszApplication)
  1106. {
  1107. CoTaskMemFree(*ppszApplication);
  1108. *ppszApplication = NULL;
  1109. }
  1110. if (ppszCommandLine && *ppszCommandLine)
  1111. {
  1112. CoTaskMemFree(*ppszCommandLine);
  1113. *ppszCommandLine = NULL;
  1114. }
  1115. // dont have to worry about ppszParameters because there is no failure
  1116. // after allocation
  1117. ASSERT(!*ppszParameters);
  1118. }
  1119. }
  1120. return hr;
  1121. }
  1122. HRESULT _ExeFromCmd(PCWSTR pszCommand, PWSTR *ppsz)
  1123. {
  1124. // if this is an EXE we act kinda funny
  1125. if (_ParamIsApp(pszCommand))
  1126. {
  1127. return SHStrDupW(L"%1", ppsz);
  1128. }
  1129. PWSTR pszArgs;
  1130. HRESULT hr = SHEvaluateSystemCommandTemplate(pszCommand, ppsz, NULL, &pszArgs);
  1131. if (SUCCEEDED(hr))
  1132. {
  1133. if (S_OK == hr && 0 == StrCmpIW(PathFindFileNameW(*ppsz), L"rundll32.exe"))
  1134. {
  1135. // ok this is a rundll. all run dlls end up looking the same
  1136. // so we think of it really being the dll
  1137. CoTaskMemFree(*ppsz);
  1138. *ppsz = NULL;
  1139. // SHEvaluateSystemCommandTemplate() guarantees PathGetArgs() to return the right thing
  1140. PWSTR pchComma = StrChrW(pszArgs, L',');
  1141. // make the comma the beginning of the args
  1142. if (pchComma)
  1143. {
  1144. // now we need to copy
  1145. WCHAR szDll[MAX_PATH];
  1146. hr = _CopyExe(szDll, ARRAYSIZE(szDll), pszArgs, pchComma - pszArgs);
  1147. if (SUCCEEDED(hr))
  1148. {
  1149. PathUnquoteSpacesW(szDll);
  1150. // can we instead just do PFOPX()
  1151. // cuz i think that rundll just checks for
  1152. // the comma
  1153. if (!*(PathFindExtensionW(szDll)))
  1154. {
  1155. // no extension, assume dll
  1156. StringCchCatW(szDll, ARRAYSIZE(szDll), L".dll");
  1157. }
  1158. if (PathIsAbsolute(szDll))
  1159. {
  1160. hr = _PathFileExists(szDll);
  1161. }
  1162. else if (PathIsFileSpecW(szDll))
  1163. {
  1164. hr = _PathFindInSystem(szDll, ARRAYSIZE(szDll));
  1165. }
  1166. else
  1167. {
  1168. // disallow most relative paths like \foo.exe or ..\foo.exe
  1169. // since these are completely indeterminate
  1170. hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  1171. }
  1172. if (SUCCEEDED(hr))
  1173. {
  1174. hr = SHStrDupW(szDll, ppsz);
  1175. }
  1176. }
  1177. }
  1178. else
  1179. {
  1180. // disallow most relative paths like \foo.exe or ..\foo.exe
  1181. // since these are completely indeterminate
  1182. hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
  1183. }
  1184. }
  1185. CoTaskMemFree(pszArgs);
  1186. }
  1187. return hr;
  1188. }
  1189. HRESULT CAssocShellVerbElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  1190. {
  1191. HRESULT hr = CAssocElement::QueryString(query, pszCue, ppsz);
  1192. if (FAILED(hr))
  1193. {
  1194. // we havent scored yet
  1195. switch (query)
  1196. {
  1197. case AQVS_DDEAPPLICATION:
  1198. // we make one up
  1199. hr = QueryString(AQVS_APPLICATION_PATH, NULL, ppsz);
  1200. if (SUCCEEDED(hr))
  1201. {
  1202. PathRemoveExtensionW(*ppsz);
  1203. PathStripPathW(*ppsz);
  1204. ASSERT(**ppsz);
  1205. }
  1206. break;
  1207. case AQVS_DDETOPIC:
  1208. hr = SHStrDupW(L"System", ppsz);
  1209. break;
  1210. case AQVS_APPLICATION_FRIENDLYNAME:
  1211. // need to delegate to the application element
  1212. if (!_fIsApp)
  1213. {
  1214. IAssociationElement *pae;
  1215. hr = _GetAppDelegate(IID_PPV_ARG(IAssociationElement, &pae));
  1216. if (SUCCEEDED(hr))
  1217. {
  1218. hr = pae->QueryString(AQVS_APPLICATION_FRIENDLYNAME, NULL, ppsz);
  1219. pae->Release();
  1220. }
  1221. }
  1222. break;
  1223. case AQVS_APPLICATION_PATH:
  1224. {
  1225. CSmartCoTaskMem<OLECHAR> spszCmd;
  1226. hr = CAssocElement::QueryString(AQVS_COMMAND, NULL, &spszCmd);
  1227. if (SUCCEEDED(hr))
  1228. {
  1229. hr = _ExeFromCmd(spszCmd, ppsz);
  1230. }
  1231. }
  1232. }
  1233. }
  1234. return hr;
  1235. }
  1236. HRESULT CAssocShellVerbElement::QueryObject(ASSOCQUERY query, PCWSTR pszCue, REFIID riid, void **ppv)
  1237. {
  1238. HRESULT hr = E_INVALIDARG;
  1239. if (query == AQVO_APPLICATION_DELEGATE)
  1240. {
  1241. hr = _GetAppDelegate(riid, ppv);
  1242. }
  1243. return hr;
  1244. }
  1245. HRESULT CAssocShellVerbElement::_GetAppDelegate(REFIID riid, void **ppv)
  1246. {
  1247. CSmartCoTaskMem<OLECHAR> spszApp;
  1248. HRESULT hr = QueryString(AQVS_APPLICATION_PATH, NULL, &spszApp);
  1249. if (SUCCEEDED(hr))
  1250. {
  1251. IPersistString2 *pips;
  1252. hr = AssocCreateElement(CLSID_AssocApplicationElement, IID_PPV_ARG(IPersistString2, &pips));
  1253. if (SUCCEEDED(hr))
  1254. {
  1255. hr = pips->SetString(spszApp);
  1256. if (SUCCEEDED(hr))
  1257. hr = pips->QueryInterface(riid, ppv);
  1258. pips->Release();
  1259. }
  1260. }
  1261. return hr;
  1262. }
  1263. HRESULT CAssocPerceivedElement::_InitSource()
  1264. {
  1265. // maybe support Content Type?
  1266. WCHAR sz[64];
  1267. DWORD cb = sizeof(sz);
  1268. if (ERROR_SUCCESS == SHGetValueW(HKEY_CLASSES_ROOT, _pszInit, L"PerceivedType", NULL, sz, &cb))
  1269. {
  1270. return _QuerySourceCreateFromKey2(HKEY_CLASSES_ROOT, L"SystemFileAssociations", sz, &_pqs);
  1271. }
  1272. return E_FAIL;
  1273. }
  1274. class CAssocClientElement : public CAssocShellElement
  1275. {
  1276. public:
  1277. // overload QS to return default MUI strings
  1278. STDMETHODIMP QueryString(
  1279. ASSOCQUERY query,
  1280. PCWSTR pszCue,
  1281. PWSTR *ppsz);
  1282. // IPersist
  1283. STDMETHODIMP GetClassID(CLSID *pclsid)
  1284. { *pclsid = CLSID_AssocClientElement; return S_OK;}
  1285. protected:
  1286. virtual HRESULT _InitSource();
  1287. private:
  1288. HRESULT _InitSourceFromKey(HKEY hkRoot, LPCWSTR pszKey);
  1289. HRESULT _FixNetscapeRegistration();
  1290. BOOL _CreateRepairedNetscapeRegistration(HKEY hkNSCopy);
  1291. };
  1292. HRESULT CAssocClientElement::QueryString(ASSOCQUERY query, PCWSTR pszCue, PWSTR *ppsz)
  1293. {
  1294. HRESULT hr;
  1295. switch (query)
  1296. {
  1297. case AQS_FRIENDLYTYPENAME:
  1298. // First try LocalizedString; if that fails, then use the default value
  1299. // for backwards compatibility.
  1300. hr = CAssocShellElement::QueryString(AQNS_NAMED_MUI_STRING, L"LocalizedString", ppsz);
  1301. if (FAILED(hr))
  1302. {
  1303. hr = CAssocShellElement::QueryString(AQN_NAMED_VALUE, NULL, ppsz);
  1304. }
  1305. break;
  1306. case AQS_DEFAULTICON:
  1307. // First try DefaultIcon; if that fails then use the first icon of the EXE
  1308. // associated with the "open" verb.
  1309. hr = CAssocShellElement::QueryString(AQS_DEFAULTICON, pszCue, ppsz);
  1310. if (FAILED(hr))
  1311. {
  1312. hr = CAssocShellElement::QueryString(AQVS_APPLICATION_PATH, L"open", ppsz);
  1313. }
  1314. break;
  1315. default:
  1316. hr = CAssocShellElement::QueryString(query, pszCue, ppsz);
  1317. break;
  1318. }
  1319. return hr;
  1320. }
  1321. HRESULT CAssocClientElement::_InitSourceFromKey(HKEY hkRoot, LPCWSTR pszKey)
  1322. {
  1323. DWORD dwType, cbSize;
  1324. WCHAR szClient[80];
  1325. cbSize = sizeof(szClient);
  1326. LONG lRc = SHGetValueW(hkRoot, pszKey, NULL, &dwType, szClient, &cbSize);
  1327. if (lRc == ERROR_SUCCESS && dwType == REG_SZ && szClient[0])
  1328. {
  1329. // Client info is kept in HKLM
  1330. HRESULT hr = _QuerySourceCreateFromKey2(HKEY_LOCAL_MACHINE, pszKey, szClient, &_pqs);
  1331. //
  1332. // If this is the Mail client and the client is Netscape Messenger,
  1333. // then we need to do extra work to detect the broken Netscape
  1334. // Navigator 4.75 mail client and fix its registration because
  1335. // Netscape registered incorrectly. They always registered
  1336. // incorrectly, but since the only access point before Windows XP
  1337. // was an obscure menu option under IE/Tools/Mail and News, they
  1338. // never noticed that it was wrong.
  1339. //
  1340. if (SUCCEEDED(hr) &&
  1341. StrCmpICW(_pszInit, L"mail") == 0 &&
  1342. StrCmpICW(szClient, L"Netscape Messenger") == 0 &&
  1343. FAILED(QueryExists(AQVS_COMMAND, L"open")))
  1344. {
  1345. hr = _FixNetscapeRegistration();
  1346. }
  1347. return hr;
  1348. }
  1349. else
  1350. {
  1351. return E_FAIL; // no registered client
  1352. }
  1353. }
  1354. // Create a volatile copy of the Netscape registration and repair it.
  1355. // We don't touch the original registration because...
  1356. //
  1357. // 1. Its existence may break the Netscape uninstaller, and
  1358. // 2. We may be running as non-administrator so don't have write access
  1359. // anyway.
  1360. HRESULT CAssocClientElement::_FixNetscapeRegistration()
  1361. {
  1362. HKEY hkMail;
  1363. HRESULT hr = E_FAIL;
  1364. if (ERROR_SUCCESS == RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Clients\\Mail",
  1365. 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
  1366. &hkMail, NULL))
  1367. {
  1368. HKEY hkNSCopy;
  1369. DWORD dwDisposition;
  1370. if (ERROR_SUCCESS == RegCreateKeyExW(hkMail, L"Netscape Messenger",
  1371. 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL,
  1372. &hkNSCopy, &dwDisposition))
  1373. {
  1374. if (dwDisposition == REG_OPENED_EXISTING_KEY ||
  1375. _CreateRepairedNetscapeRegistration(hkNSCopy))
  1376. {
  1377. // Now swap in the good registration for the bad one
  1378. _pqs->Release();
  1379. hr = _QuerySourceCreateFromKey(hkNSCopy, NULL, FALSE, &_pqs);
  1380. }
  1381. RegCloseKey(hkNSCopy);
  1382. }
  1383. if (FAILED(hr))
  1384. {
  1385. SHDeleteKeyW(hkMail, L"Netscape Messenger");
  1386. }
  1387. RegCloseKey(hkMail);
  1388. }
  1389. return hr;
  1390. }
  1391. LONG _RegQueryString(HKEY hk, PCWSTR pszSub, LPWSTR pszBuf, LONG cbBuf)
  1392. {
  1393. return RegQueryValueW(hk, pszSub, pszBuf, &cbBuf);
  1394. }
  1395. LONG _RegSetVolatileString(HKEY hk, PCWSTR pszSub, LPCWSTR pszBuf)
  1396. {
  1397. HKEY hkSub;
  1398. LONG lRc;
  1399. if (!pszSub || pszSub[0] == L'\0')
  1400. {
  1401. lRc = RegOpenKeyEx(hk, NULL, 0, KEY_WRITE, &hkSub);
  1402. }
  1403. else
  1404. {
  1405. lRc = RegCreateKeyExW(hk, pszSub,
  1406. 0, NULL, REG_OPTION_VOLATILE, KEY_WRITE, NULL,
  1407. &hkSub, NULL);
  1408. }
  1409. if (lRc == ERROR_SUCCESS)
  1410. {
  1411. lRc = RegSetValueW(hkSub, NULL, REG_SZ, pszBuf, (lstrlenW(pszBuf) + 1) * sizeof(pszBuf[0]));
  1412. RegCloseKey(hkSub);
  1413. }
  1414. return lRc;
  1415. }
  1416. BOOL CAssocClientElement::_CreateRepairedNetscapeRegistration(HKEY hkNSCopy)
  1417. {
  1418. BOOL fSuccess = FALSE;
  1419. HKEY hkSrc;
  1420. // Sadly, we cannot use SHCopyKey because SHCopyKey does not work
  1421. // on volatile keys. So we just copy the keys we care about.
  1422. WCHAR szBuf[MAX_PATH];
  1423. if (ERROR_SUCCESS == RegOpenKeyExW(HKEY_LOCAL_MACHINE,
  1424. L"Software\\Clients\\Mail\\Netscape Messenger",
  1425. 0, KEY_READ, &hkSrc))
  1426. {
  1427. // Copy default icon but don't panic if it's not there.
  1428. if (ERROR_SUCCESS == _RegQueryString(hkSrc, L"Protocols\\mailto\\DefaultIcon", szBuf, ARRAYSIZE(szBuf)))
  1429. {
  1430. // Great, Netscape also registers the wrong icon so we have to fix that too.
  1431. PathParseIconLocationW(szBuf);
  1432. StrCatBuffW(szBuf, L",-1349", ARRAYSIZE(szBuf));
  1433. _RegSetVolatileString(hkNSCopy, L"DefaultIcon", szBuf);
  1434. }
  1435. // Copy friendly name
  1436. if (ERROR_SUCCESS == _RegQueryString(hkSrc, NULL, szBuf, ARRAYSIZE(szBuf)) &&
  1437. ERROR_SUCCESS == _RegSetVolatileString(hkNSCopy, NULL, szBuf))
  1438. {
  1439. PWSTR pszExe;
  1440. // Copy command line, but with a new command line parameter
  1441. if (ERROR_SUCCESS == _RegQueryString(hkSrc, L"Protocols\\mailto\\shell\\open\\command", szBuf, ARRAYSIZE(szBuf)) &&
  1442. SUCCEEDED(_ExeFromCmd(szBuf, &pszExe)))
  1443. {
  1444. lstrcpynW(szBuf, pszExe, ARRAYSIZE(szBuf));
  1445. SHFree(pszExe);
  1446. PathQuoteSpacesW(szBuf);
  1447. StrCatBuffW(szBuf, L" -mail", ARRAYSIZE(szBuf));
  1448. if (ERROR_SUCCESS == _RegSetVolatileString(hkNSCopy, L"shell\\open\\command", szBuf))
  1449. {
  1450. fSuccess = TRUE;
  1451. }
  1452. }
  1453. }
  1454. RegCloseKey(hkSrc);
  1455. }
  1456. return fSuccess;
  1457. }
  1458. HRESULT CAssocClientElement::_InitSource()
  1459. {
  1460. // First try HKCU; if that doesn't work (no value set in HKCU or
  1461. // the value in HKCU is bogus), then try again with HKLM.
  1462. WCHAR szKey[MAX_PATH];
  1463. wnsprintfW(szKey, ARRAYSIZE(szKey), L"Software\\Clients\\%s", _pszInit);
  1464. HRESULT hr = _InitSourceFromKey(HKEY_CURRENT_USER, szKey);
  1465. if (FAILED(hr))
  1466. {
  1467. hr = _InitSourceFromKey(HKEY_LOCAL_MACHINE, szKey);
  1468. }
  1469. return hr;
  1470. }
  1471. HRESULT AssocCreateElement(REFCLSID clsid, REFIID riid, void **ppv)
  1472. {
  1473. IAssociationElement *pae = NULL;
  1474. if (clsid == CLSID_AssocShellElement)
  1475. pae = new CAssocShellElement();
  1476. else if (clsid == CLSID_AssocProgidElement)
  1477. pae = new CAssocProgidElement();
  1478. else if (clsid == CLSID_AssocClsidElement)
  1479. pae = new CAssocClsidElement();
  1480. else if (clsid == CLSID_AssocSystemElement)
  1481. pae = new CAssocSystemExtElement();
  1482. else if (clsid == CLSID_AssocPerceivedElement)
  1483. pae = new CAssocPerceivedElement();
  1484. else if (clsid == CLSID_AssocApplicationElement)
  1485. pae = new CAssocApplicationElement();
  1486. else if (clsid == CLSID_AssocFolderElement)
  1487. pae = new CAssocFolderElement();
  1488. else if (clsid == CLSID_AssocStarElement)
  1489. pae = new CAssocStarElement();
  1490. else if (clsid == CLSID_AssocClientElement)
  1491. pae = new CAssocClientElement();
  1492. HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
  1493. if (pae)
  1494. {
  1495. hr = pae->QueryInterface(riid, ppv);
  1496. pae->Release();
  1497. }
  1498. else
  1499. *ppv = 0;
  1500. return hr;
  1501. }