Source code of Windows XP (NT5)
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.

5851 lines
176 KiB

  1. #include "shellprv.h"
  2. #include <shlobjp.h>
  3. #include "shelllnk.h"
  4. #include "datautil.h"
  5. #include "vdate.h" // For VDATEINPUTBUF
  6. #include "ids.h" // For String Resource identifiers
  7. #include "pif.h" // For manipulating PIF files
  8. #include "trayp.h" // For WMTRAY_* messages
  9. #include "views.h" // For FSIDM_OPENPRN
  10. #include "os.h" // For Win32MoveFile ...
  11. #include "util.h" // For GetMenuIndexForCanonicalVerb
  12. #include "defcm.h" // For CDefFolderMenu_Create2Ex
  13. #include "uemapp.h"
  14. #include <filterr.h>
  15. #include "folder.h"
  16. #include <msi.h>
  17. #include <msip.h>
  18. #include "treewkcb.h"
  19. #define GetLastHRESULT() HRESULT_FROM_WIN32(GetLastError())
  20. // Flags for FindInFilder.fifFlags
  21. //
  22. // The drive referred to by the shortcut does not exist.
  23. // Let pTracker search for it, but do not perform an old-style
  24. // ("downlevel") search of our own.
  25. #define FIF_NODRIVE 0x0001
  26. // Only if the file we found scores more than this number do we
  27. // even show the user this result, any thing less than this would
  28. // be too shameful of us to show the user.
  29. #define MIN_SHOW_USER_SCORE 10
  30. // magic score that stops searches and causes us not to warn
  31. // whe the link is actually found
  32. #define MIN_NO_UI_SCORE 40
  33. // If no User Interface will be provided during the search,
  34. // then do not search more than 3 seconds.
  35. #define NOUI_SEARCH_TIMEOUT (3 * 1000)
  36. // If a User Interface will be provided during the search,
  37. // then search as much as 2 minutes.
  38. #define UI_SEARCH_TIMEOUT (120 * 1000)
  39. #define LNKTRACK_HINTED_UPLEVELS 4 // directory levels to search upwards from last know object locn
  40. #define LNKTRACK_DESKTOP_DOWNLEVELS 4 // infinite downlevels
  41. #define LNKTRACK_ROOT_DOWNLEVELS 4 // levels down from root of fixed disks
  42. #define LNKTRACK_HINTED_DOWNLEVELS 4 // levels down at each level on way up during hinted uplevels
  43. class CLinkResolver : public CBaseTreeWalkerCB
  44. {
  45. public:
  46. CLinkResolver(CTracker *ptrackerobject, const WIN32_FIND_DATA *pofd, UINT dwResolveFlags, DWORD TrackerRestrictions, DWORD fifFlags);
  47. int Resolve(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszCurFile);
  48. void GetResult(LPTSTR psz, UINT cch);
  49. // IShellTreeWalkerCallBack
  50. STDMETHODIMP FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
  51. STDMETHODIMP EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd);
  52. private:
  53. ~CLinkResolver();
  54. static DWORD CALLBACK _ThreadStartCallBack(void *pv);
  55. static DWORD CALLBACK _SearchThreadProc(void *pv);
  56. static BOOL_PTR CALLBACK _DlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam);
  57. void _HeuristicSearch();
  58. void _InitDlg(HWND hDlg);
  59. DWORD _Search();
  60. DWORD _GetTimeOut();
  61. int _ScoreFindData(const WIN32_FIND_DATA *pfd);
  62. HRESULT _ProcessFoundFile(LPCTSTR pszPath, WIN32_FIND_DATAW * pwfdw);
  63. BOOL _SearchInFolder(LPCTSTR pszFolder, int cLevels);
  64. HRESULT _InitWalkObject();
  65. HANDLE _hThread;
  66. DWORD _dwTimeOutDelta;
  67. HWND _hDlg;
  68. UINT_PTR _idtDelayedShow; // timer for delayed-show
  69. DWORD _fifFlags; // FIF_ flags
  70. CTracker *_ptracker; // Implements ObjectID-based link tracking
  71. DWORD _TrackerRestrictions; // Flags from the TrkMendRestrictions enumeration
  72. DWORD _dwSearchFlags;
  73. int _iFolderBonus;
  74. WCHAR _wszSearchSpec[64]; // holds file extension filter for search
  75. LPCWSTR _pwszSearchSpec; // NULL for folders
  76. IShellTreeWalker *_pstw;
  77. BOOL _fFindLnk; // are we looking for a lnk file?
  78. DWORD _dwMatch; // must match attributes
  79. WIN32_FIND_DATA _ofd; // original find data
  80. DWORD _dwTimeLimit; // don't go past this
  81. BOOL _bContinue; // keep going
  82. LPCTSTR _pszSearchOrigin; // path where current search originated, to help avoid dup searchs
  83. LPCTSTR _pszSearchOriginFirst; // path where search originated, to help avoid dup searchs
  84. int _iScore; // score for current item
  85. WIN32_FIND_DATA _fdFound; // results
  86. WIN32_FIND_DATA _sfd; // to save stack space
  87. UINT _dwResolveFlags; // SLR_ flags
  88. TCHAR _szSearchStart[MAX_PATH];
  89. };
  90. // NOTE:(seanf) This is sleazy - This fn is defined in shlobj.h, but only if urlmon.h
  91. // was included first. Rather than monkey with the include order in
  92. // shellprv.h, we'll duplicate the prototype here, where SOFTDISTINFO
  93. // is now defined.
  94. SHDOCAPI_(DWORD) SoftwareUpdateMessageBox(HWND hWnd,
  95. LPCWSTR pszDistUnit,
  96. DWORD dwFlags,
  97. LPSOFTDISTINFO psdi);
  98. // The following strings are used to support the shell link set path hack that
  99. // allows us to bless links for Darwin without exposing stuff from IShellLinkDataList
  100. #define DARWINGUID_TAG TEXT("::{9db1186e-40df-11d1-aa8c-00c04fb67863}:")
  101. #define LOGO3GUID_TAG TEXT("::{9db1186f-40df-11d1-aa8c-00c04fb67863}:")
  102. #define TF_DEBUGLINKCODE 0x00800000
  103. EXTERN_C BOOL IsFolderShortcut(LPCTSTR pszName);
  104. class CDarwinContextMenuCB : public IContextMenuCB
  105. {
  106. public:
  107. CDarwinContextMenuCB() : _cRef(1) { }
  108. // IUnknown
  109. STDMETHOD(QueryInterface)(REFIID riid, void **ppv)
  110. {
  111. static const QITAB qit[] = {
  112. QITABENT(CDarwinContextMenuCB, IContextMenuCB), // IID_IContextMenuCB
  113. { 0 },
  114. };
  115. return QISearch(this, qit, riid, ppv);
  116. }
  117. STDMETHOD_(ULONG,AddRef)()
  118. {
  119. return InterlockedIncrement(&_cRef);
  120. }
  121. STDMETHOD_(ULONG,Release)()
  122. {
  123. if (InterlockedDecrement(&_cRef))
  124. return _cRef;
  125. delete this;
  126. return 0;
  127. }
  128. // IContextMenuCB
  129. STDMETHOD(CallBack)(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam);
  130. public:
  131. void SetProductCodeFromDarwinID(LPCTSTR szDarwinID)
  132. {
  133. MsiDecomposeDescriptor(szDarwinID, _szProductCode, NULL, NULL, NULL);
  134. }
  135. private:
  136. LONG _cRef;
  137. TCHAR _szProductCode[MAX_PATH];
  138. };
  139. CShellLink::CShellLink() : _cRef(1)
  140. {
  141. _ptracker = new CTracker(this);
  142. _ResetPersistData();
  143. }
  144. CShellLink::~CShellLink()
  145. {
  146. _ResetPersistData(); // free all data
  147. if (_pcbDarwin)
  148. {
  149. _pcbDarwin->Release();
  150. }
  151. if (_pdtSrc)
  152. {
  153. _pdtSrc->Release();
  154. }
  155. if (_pxi)
  156. {
  157. _pxi->Release();
  158. }
  159. if (_pxiA)
  160. {
  161. _pxiA->Release();
  162. }
  163. if (_pxthumb)
  164. {
  165. _pxthumb->Release();
  166. }
  167. Str_SetPtr(&_pszCurFile, NULL);
  168. Str_SetPtr(&_pszRelSource, NULL);
  169. if (_ptracker)
  170. {
  171. delete _ptracker;
  172. }
  173. }
  174. // Private interface used for testing
  175. /* 7c9e512f-41d7-11d1-8e2e-00c04fb9386d */
  176. EXTERN_C const IID IID_ISLTracker = { 0x7c9e512f, 0x41d7, 0x11d1, {0x8e, 0x2e, 0x00, 0xc0, 0x4f, 0xb9, 0x38, 0x6d} };
  177. STDMETHODIMP CShellLink::QueryInterface(REFIID riid, void **ppvObj)
  178. {
  179. static const QITAB qit[] = {
  180. QITABENT(CShellLink, IShellLinkA),
  181. QITABENT(CShellLink, IShellLinkW),
  182. QITABENT(CShellLink, IPersistFile),
  183. QITABENT(CShellLink, IPersistStream),
  184. QITABENT(CShellLink, IShellExtInit),
  185. QITABENTMULTI(CShellLink, IContextMenu, IContextMenu3),
  186. QITABENTMULTI(CShellLink, IContextMenu2, IContextMenu3),
  187. QITABENT(CShellLink, IContextMenu3),
  188. QITABENT(CShellLink, IDropTarget),
  189. QITABENT(CShellLink, IExtractIconA),
  190. QITABENT(CShellLink, IExtractIconW),
  191. QITABENT(CShellLink, IShellLinkDataList),
  192. QITABENT(CShellLink, IQueryInfo),
  193. QITABENT(CShellLink, IPersistPropertyBag),
  194. QITABENT(CShellLink, IObjectWithSite),
  195. QITABENT(CShellLink, IServiceProvider),
  196. QITABENT(CShellLink, IFilter),
  197. QITABENT(CShellLink, IExtractImage2),
  198. QITABENTMULTI(CShellLink, IExtractImage, IExtractImage2),
  199. QITABENT(CShellLink, ICustomizeInfoTip),
  200. { 0 },
  201. };
  202. HRESULT hr = QISearch(this, qit, riid, ppvObj);
  203. if (FAILED(hr) && (IID_ISLTracker == riid) && _ptracker)
  204. {
  205. // ISLTracker is a private test interface, and isn't implemented
  206. *ppvObj = SAFECAST(_ptracker, ISLTracker*);
  207. _ptracker->AddRef();
  208. hr = S_OK;
  209. }
  210. return hr;
  211. }
  212. STDMETHODIMP_(ULONG) CShellLink::AddRef()
  213. {
  214. return InterlockedIncrement(&_cRef);
  215. }
  216. void CShellLink::_ClearTrackerData()
  217. {
  218. if (_ptracker)
  219. _ptracker->InitNew();
  220. }
  221. void CShellLink::_ResetPersistData()
  222. {
  223. Pidl_Set(&_pidl, NULL);
  224. _FreeLinkInfo();
  225. _ClearTrackerData();
  226. Str_SetPtr(&_pszName, NULL);
  227. Str_SetPtr(&_pszRelPath, NULL);
  228. Str_SetPtr(&_pszWorkingDir, NULL);
  229. Str_SetPtr(&_pszArgs, NULL);
  230. Str_SetPtr(&_pszIconLocation, NULL);
  231. Str_SetPtr(&_pszPrefix, NULL);
  232. if (_pExtraData)
  233. {
  234. SHFreeDataBlockList(_pExtraData);
  235. _pExtraData = NULL;
  236. }
  237. // init data members. all others are zero inited
  238. memset(&_sld, 0, sizeof(_sld));
  239. _sld.iShowCmd = SW_SHOWNORMAL;
  240. _bExpandedIcon = FALSE;
  241. }
  242. STDMETHODIMP_(ULONG) CShellLink::Release()
  243. {
  244. if (InterlockedDecrement(&_cRef))
  245. {
  246. return _cRef;
  247. }
  248. delete this;
  249. return 0;
  250. }
  251. #ifdef DEBUG
  252. void DumpPLI(PCLINKINFO pli)
  253. {
  254. DebugMsg(DM_TRACE, TEXT("DumpPLI:"));
  255. if (pli)
  256. {
  257. const void *p;
  258. if (GetLinkInfoData(pli, LIDT_VOLUME_SERIAL_NUMBER, &p))
  259. DebugMsg(DM_TRACE, TEXT("\tSerial #\t%8X"), *(DWORD *)p);
  260. if (GetLinkInfoData(pli, LIDT_DRIVE_TYPE, &p))
  261. DebugMsg(DM_TRACE, TEXT("\tDrive Type\t%d"), *(DWORD *)p);
  262. if (GetLinkInfoData(pli, LIDT_VOLUME_LABEL, &p))
  263. DebugMsg(DM_TRACE, TEXT("\tLabel\t%hs"), p);
  264. if (GetLinkInfoData(pli, LIDT_LOCAL_BASE_PATH, &p))
  265. DebugMsg(DM_TRACE, TEXT("\tBase Path\t%hs"), p);
  266. if (GetLinkInfoData(pli, LIDT_NET_RESOURCE, &p))
  267. DebugMsg(DM_TRACE, TEXT("\tNet Res\t%hs"), p);
  268. if (GetLinkInfoData(pli, LIDT_COMMON_PATH_SUFFIX, &p))
  269. DebugMsg(DM_TRACE, TEXT("\tPath Sufix\t%hs"), p);
  270. }
  271. }
  272. #else
  273. #define DumpPLI(p)
  274. #endif
  275. BOOL CShellLink::_LinkInfo(LINKINFODATATYPE info, LPTSTR psz, UINT cch)
  276. {
  277. *psz = 0;
  278. BOOL bRet = FALSE;
  279. const void *p;
  280. if (_pli && GetLinkInfoData(_pli, info, &p) && p)
  281. {
  282. switch (info)
  283. {
  284. case LIDT_VOLUME_LABEL:
  285. case LIDT_LOCAL_BASE_PATH:
  286. case LIDT_NET_RESOURCE:
  287. case LIDT_REDIRECTED_DEVICE:
  288. case LIDT_COMMON_PATH_SUFFIX:
  289. SHAnsiToTChar((LPCSTR)p, psz, cch);
  290. bRet = TRUE;
  291. break;
  292. case LIDT_VOLUME_LABELW:
  293. case LIDT_NET_RESOURCEW:
  294. case LIDT_REDIRECTED_DEVICEW:
  295. case LIDT_LOCAL_BASE_PATHW:
  296. case LIDT_COMMON_PATH_SUFFIXW:
  297. SHUnicodeToTChar((LPCWSTR)p, psz, cch);
  298. bRet = TRUE;
  299. break;
  300. }
  301. }
  302. else switch (info) // failure of a UNICODE var fall back and try ANSI
  303. {
  304. case LIDT_VOLUME_LABELW:
  305. bRet = _LinkInfo(LIDT_VOLUME_LABEL, psz, cch);
  306. break;
  307. case LIDT_NET_RESOURCEW:
  308. bRet = _LinkInfo(LIDT_NET_RESOURCE, psz, cch);
  309. break;
  310. case LIDT_REDIRECTED_DEVICEW:
  311. bRet = _LinkInfo(LIDT_REDIRECTED_DEVICE, psz, cch);
  312. break;
  313. case LIDT_LOCAL_BASE_PATHW:
  314. bRet = _LinkInfo(LIDT_LOCAL_BASE_PATH, psz, cch);
  315. break;
  316. case LIDT_COMMON_PATH_SUFFIXW:
  317. bRet = _LinkInfo(LIDT_COMMON_PATH_SUFFIX, psz, cch);
  318. break;
  319. }
  320. return bRet;
  321. }
  322. BOOL CShellLink::_GetUNCPath(LPTSTR pszName)
  323. {
  324. TCHAR szRoot[MAX_PATH], szBase[MAX_PATH];
  325. *pszName = 0;
  326. if (_LinkInfo(LIDT_NET_RESOURCEW, szRoot, ARRAYSIZE(szRoot)) &&
  327. _LinkInfo(LIDT_COMMON_PATH_SUFFIXW, szBase, ARRAYSIZE(szBase)))
  328. {
  329. PathCombine(pszName, szRoot, szBase);
  330. }
  331. return PathIsUNC(pszName);
  332. }
  333. // Compare _sld to a WIN32_FIND_DATA
  334. BOOL CShellLink::_IsEqualFindData(const WIN32_FIND_DATA *pfd)
  335. {
  336. return (pfd->dwFileAttributes == _sld.dwFileAttributes) &&
  337. (CompareFileTime(&pfd->ftCreationTime, &_sld.ftCreationTime) == 0) &&
  338. (CompareFileTime(&pfd->ftLastWriteTime, &_sld.ftLastWriteTime) == 0) &&
  339. (pfd->nFileSizeLow == _sld.nFileSizeLow);
  340. }
  341. BOOL CShellLink::_SetFindData(const WIN32_FIND_DATA *pfd)
  342. {
  343. if (!_IsEqualFindData(pfd))
  344. {
  345. _sld.dwFileAttributes = pfd->dwFileAttributes;
  346. _sld.ftCreationTime = pfd->ftCreationTime;
  347. _sld.ftLastAccessTime = pfd->ftLastAccessTime;
  348. _sld.ftLastWriteTime = pfd->ftLastWriteTime;
  349. _sld.nFileSizeLow = pfd->nFileSizeLow;
  350. _bDirty = TRUE;
  351. return TRUE;
  352. }
  353. return FALSE;
  354. }
  355. // make a copy into LocalAlloc memory, to avoid having to load linkinfo.dll
  356. // just to call DestroyLinkInfo()
  357. PLINKINFO CopyLinkInfo(PCLINKINFO pcliSrc)
  358. {
  359. ASSERT(pcliSrc);
  360. DWORD dwSize = pcliSrc->ucbSize; // size of this thing
  361. PLINKINFO pli = (PLINKINFO)LocalAlloc(LPTR, dwSize); // make a copy
  362. if (pli)
  363. CopyMemory(pli, pcliSrc, dwSize);
  364. return pli;
  365. }
  366. void CShellLink::_FreeLinkInfo()
  367. {
  368. if (_pli)
  369. {
  370. LocalFree((HLOCAL)_pli);
  371. _pli = NULL;
  372. }
  373. }
  374. // creates a LINKINFO _pli from a given file name
  375. //
  376. // returns:
  377. //
  378. // success, pointer to the LINKINFO
  379. // NULL this link does not have LINKINFO
  380. PLINKINFO CShellLink::_GetLinkInfo(LPCTSTR pszPath)
  381. {
  382. // this bit disables LINKINFO tracking on a per link basis, this is set
  383. // externally by admins to make links more "transparent"
  384. if (!(_sld.dwFlags & SLDF_FORCE_NO_LINKINFO))
  385. {
  386. if (pszPath)
  387. {
  388. PLINKINFO pliNew;
  389. if (CreateLinkInfo(pszPath, &pliNew))
  390. {
  391. // avoid marking the link dirty if the linkinfo
  392. // blocks are the same, comparing the bits
  393. // gives us an accurate positive test
  394. if (!_pli || (_pli->ucbSize != pliNew->ucbSize) || memcmp(_pli, pliNew, pliNew->ucbSize))
  395. {
  396. _FreeLinkInfo();
  397. _pli = CopyLinkInfo(pliNew);
  398. _bDirty = TRUE;
  399. }
  400. DumpPLI(_pli);
  401. DestroyLinkInfo(pliNew);
  402. }
  403. }
  404. }
  405. return _pli;
  406. }
  407. void PathGetRelative(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszRel)
  408. {
  409. TCHAR szRoot[MAX_PATH];
  410. lstrcpy(szRoot, pszFrom);
  411. if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY))
  412. {
  413. PathRemoveFileSpec(szRoot);
  414. }
  415. ASSERT(PathIsRelative(pszRel));
  416. PathCombine(pszPath, szRoot, pszRel);
  417. }
  418. //
  419. // update the working dir to match changes being made to the link target
  420. //
  421. void CShellLink::_UpdateWorkingDir(LPCTSTR pszNew)
  422. {
  423. TCHAR szOld[MAX_PATH], szPath[MAX_PATH];
  424. if ((_sld.dwFlags & SLDF_HAS_DARWINID) ||
  425. (_pszWorkingDir == NULL) ||
  426. (_pszWorkingDir[0] == 0) ||
  427. StrChr(_pszWorkingDir, TEXT('%')) ||
  428. (_pidl == NULL) ||
  429. !SHGetPathFromIDList(_pidl, szOld) ||
  430. (lstrcmpi(szOld, pszNew) == 0))
  431. {
  432. return;
  433. }
  434. if (PathRelativePathTo(szPath, szOld, _sld.dwFileAttributes, _pszWorkingDir, FILE_ATTRIBUTE_DIRECTORY))
  435. {
  436. PathGetRelative(szOld, pszNew, GetFileAttributes(pszNew), szPath); // get result is szOld
  437. if (PathIsDirectory(szOld))
  438. {
  439. DebugMsg(DM_TRACE, TEXT("working dir updated to %s"), szOld);
  440. Str_SetPtr(&_pszWorkingDir, szOld);
  441. _bDirty = TRUE;
  442. }
  443. }
  444. }
  445. HRESULT CShellLink::_SetSimplePIDL(LPCTSTR pszPath)
  446. {
  447. LPITEMIDLIST pidl;
  448. WIN32_FIND_DATA fd = {0};
  449. fd.dwFileAttributes = _sld.dwFileAttributes;
  450. HRESULT hr = SHSimpleIDListFromFindData(pszPath, &fd, &pidl);
  451. if (SUCCEEDED(hr))
  452. {
  453. hr = _SetPIDLPath(pidl, NULL, FALSE);
  454. ILFree(pidl);
  455. }
  456. return hr;
  457. }
  458. // set the pidl either based on a new pidl or a path
  459. // this will set the dirty flag if this info is different from the current
  460. //
  461. // in:
  462. // pidlNew if non-null, use as new PIDL for link
  463. // pszPath if non-null, create a pidl for this and set it
  464. //
  465. // returns:
  466. // hr based on success
  467. // FAILED() codes on failure (parsing failure for path case)
  468. HRESULT CShellLink::_SetPIDLPath(LPCITEMIDLIST pidl, LPCTSTR pszPath, BOOL bUpdateTrackingData)
  469. {
  470. LPITEMIDLIST pidlCreated;
  471. HRESULT hr;
  472. if (pszPath && !pidl)
  473. {
  474. // path as input. this can map the pidl into the alias form (relative to
  475. // ::{my docs} for example) but allow link to override that behavior
  476. ILCFP_FLAGS ilcfpFlags = (_sld.dwFlags & SLDF_NO_PIDL_ALIAS) ? ILCFP_FLAG_NO_MAP_ALIAS : ILCFP_FLAG_NORMAL;
  477. hr = ILCreateFromPathEx(pszPath, NULL, ilcfpFlags, &pidlCreated, NULL);
  478. // Force a SHGetPathFromIDList later so that the linkinfo will not get confused by letter case changing
  479. // as in c:\Winnt\System32\App.exe versus C:\WINNT\system32\app.exe
  480. pszPath = NULL;
  481. }
  482. else if (!pszPath && pidl)
  483. {
  484. // pidl as input, make copy that we will keep
  485. hr = SHILClone(pidl, &pidlCreated);
  486. }
  487. else if (!pszPath && !pidl)
  488. {
  489. pidlCreated = NULL;
  490. // setting to empty
  491. hr = S_OK;
  492. }
  493. else
  494. {
  495. // can't set path and pidl at the same time
  496. hr = E_FAIL;
  497. }
  498. if (SUCCEEDED(hr))
  499. {
  500. // this data needs to be kept in sync with _pidl
  501. _RemoveExtraDataSection(EXP_SPECIAL_FOLDER_SIG);
  502. if (pidlCreated)
  503. {
  504. TCHAR szPath[MAX_PATH];
  505. if (!_pidl || !ILIsEqual(_pidl, pidlCreated))
  506. {
  507. // new pidl
  508. _bDirty = TRUE;
  509. }
  510. if (!pszPath && SHGetPathFromIDList(pidlCreated, szPath))
  511. {
  512. pszPath = szPath;
  513. }
  514. if (pszPath)
  515. {
  516. // needs old _pidl to work
  517. _UpdateWorkingDir(pszPath);
  518. }
  519. ILFree(_pidl);
  520. _pidl = pidlCreated;
  521. if (pszPath)
  522. {
  523. if (bUpdateTrackingData)
  524. {
  525. // this is a file/folder, get tracking info (ignore failures)
  526. _GetLinkInfo(pszPath); // the LinkInfo (_pli)
  527. _GetFindDataAndTracker(pszPath); // tracker & find data
  528. }
  529. }
  530. else
  531. {
  532. // not a file, clear the tracking info
  533. WIN32_FIND_DATA fd = {0};
  534. _SetFindData(&fd);
  535. _ClearTrackerData();
  536. _FreeLinkInfo();
  537. }
  538. }
  539. else
  540. {
  541. // clear out the contents of the link
  542. _ResetPersistData();
  543. _bDirty = TRUE;
  544. }
  545. }
  546. return hr;
  547. }
  548. // compute the relative path for the target is there is one
  549. // pszPath is optionatl to test if there is a relative path
  550. BOOL CShellLink::_GetRelativePath(LPTSTR pszPath)
  551. {
  552. BOOL bRet = FALSE;
  553. LPCTSTR pszPathRel = _pszRelSource ? _pszRelSource : _pszCurFile;
  554. if (pszPathRel && _pszRelPath)
  555. {
  556. TCHAR szRoot[MAX_PATH];
  557. lstrcpy(szRoot, pszPathRel);
  558. PathRemoveFileSpec(szRoot); // pszPathRel is a file (not a directory)
  559. // this can fail for really deep paths
  560. if (PathCombine(pszPath, szRoot, _pszRelPath))
  561. {
  562. bRet = TRUE;
  563. }
  564. }
  565. return bRet;
  566. }
  567. void CShellLink::_GetFindData(WIN32_FIND_DATA *pfd)
  568. {
  569. ZeroMemory(pfd, sizeof(*pfd));
  570. pfd->dwFileAttributes = _sld.dwFileAttributes;
  571. pfd->ftCreationTime = _sld.ftCreationTime;
  572. pfd->ftLastAccessTime = _sld.ftLastAccessTime;
  573. pfd->ftLastWriteTime = _sld.ftLastWriteTime;
  574. pfd->nFileSizeLow = _sld.nFileSizeLow;
  575. TCHAR szPath[MAX_PATH];
  576. SHGetPathFromIDList(_pidl, szPath);
  577. ASSERT(szPath[0]); // no one should call this on a pidl without a path
  578. lstrcpy(pfd->cFileName, PathFindFileName(szPath));
  579. }
  580. STDMETHODIMP CShellLink::GetPath(LPWSTR pszFile, int cchFile, WIN32_FIND_DATAW *pfd, DWORD fFlags)
  581. {
  582. TCHAR szPath[MAX_PATH];
  583. VDATEINPUTBUF(pszFile, TCHAR, cchFile);
  584. if (_sld.dwFlags & SLDF_HAS_DARWINID)
  585. {
  586. // For darwin enabled links, we do NOT want to have to go and call
  587. // ParseDarwinID here because that could possible force the app to install.
  588. // So, instead we return the path to the icon as the path for darwin enable
  589. // shortcuts. This allows the icon to be correct and since the darwin icon
  590. // will always be an .exe, ensuring that the context menu will be correct.
  591. SHExpandEnvironmentStrings(_pszIconLocation ? _pszIconLocation : TEXT(""), szPath, ARRAYSIZE(szPath));
  592. }
  593. else
  594. {
  595. DumpPLI(_pli);
  596. if (!_pidl || !SHGetPathFromIDListEx(_pidl, szPath, (fFlags & SLGP_SHORTPATH) ? GPFIDL_ALTNAME : 0))
  597. szPath[0] = 0;
  598. // Must do the pfd thing before we munge szPath, because the stuff
  599. // we do to szPath might render it unsuitable for PathFindFileName.
  600. // (For example, "C:\WINNT\Profiles\Bob" might turn into "%USERPROFILE%",
  601. // and we want to make sure we save "Bob" before it's too late.)
  602. if (pfd)
  603. {
  604. memset(pfd, 0, sizeof(*pfd));
  605. if (szPath[0])
  606. {
  607. pfd->dwFileAttributes = _sld.dwFileAttributes;
  608. pfd->ftCreationTime = _sld.ftCreationTime;
  609. pfd->ftLastAccessTime = _sld.ftLastAccessTime;
  610. pfd->ftLastWriteTime = _sld.ftLastWriteTime;
  611. pfd->nFileSizeLow = _sld.nFileSizeLow;
  612. SHTCharToUnicode(PathFindFileName(szPath), pfd->cFileName, ARRAYSIZE(pfd->cFileName));
  613. }
  614. }
  615. if ((_sld.dwFlags & SLDF_HAS_EXP_SZ) && (fFlags & SLGP_RAWPATH))
  616. {
  617. // Special case where we grab the Target name from
  618. // the extra data section of the link rather than from
  619. // the pidl. We do this after we grab the name from the pidl
  620. // so that if we fail, then there is still some hope that a
  621. // name can be returned.
  622. LPEXP_SZ_LINK pszl = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_LINK_SIG);
  623. if (pszl)
  624. {
  625. SHUnicodeToTChar(pszl->swzTarget, szPath, ARRAYSIZE(szPath));
  626. DebugMsg(DM_TRACE, TEXT("CShellLink::GetPath() %s (from xtra data)"), szPath);
  627. }
  628. }
  629. }
  630. if (pszFile)
  631. {
  632. SHTCharToUnicode(szPath, pszFile, cchFile);
  633. }
  634. // note the lame return semantics, check for S_OK to be sure you have a path
  635. return szPath[0] ? S_OK : S_FALSE;
  636. }
  637. STDMETHODIMP CShellLink::GetIDList(LPITEMIDLIST *ppidl)
  638. {
  639. if (_pidl)
  640. {
  641. return SHILClone(_pidl, ppidl);
  642. }
  643. *ppidl = NULL;
  644. return S_FALSE; // success but empty
  645. }
  646. #ifdef DEBUG
  647. #define DumpTimes(ftCreate, ftAccessed, ftWrite) \
  648. DebugMsg(DM_TRACE, TEXT("create %8x%8x"), ftCreate.dwLowDateTime, ftCreate.dwHighDateTime); \
  649. DebugMsg(DM_TRACE, TEXT("accessed %8x%8x"), ftAccessed.dwLowDateTime, ftAccessed.dwHighDateTime); \
  650. DebugMsg(DM_TRACE, TEXT("write %8x%8x"), ftWrite.dwLowDateTime, ftWrite.dwHighDateTime);
  651. #else
  652. #define DumpTimes(ftCreate, ftAccessed, ftWrite)
  653. #endif
  654. void CheckAndFixNullCreateTime(LPCTSTR pszFile, FILETIME *pftCreationTime, const FILETIME *pftLastWriteTime)
  655. {
  656. if (IsNullTime(pftCreationTime) && !IsNullTime(pftLastWriteTime))
  657. {
  658. // this file has a bogus create time, set it to the last accessed time
  659. HANDLE hfile = CreateFile(pszFile, GENERIC_READ | GENERIC_WRITE,
  660. FILE_SHARE_READ | FILE_SHARE_WRITE,
  661. NULL, OPEN_EXISTING, 0, NULL);
  662. if (INVALID_HANDLE_VALUE != hfile)
  663. {
  664. DebugMsg(DM_TRACE, TEXT("create %8x%8x"), pftCreationTime->dwLowDateTime, pftCreationTime->dwHighDateTime);
  665. if (SetFileTime(hfile, pftLastWriteTime, NULL, NULL))
  666. {
  667. // get the time back to make sure we match the precision of the file system
  668. *pftCreationTime = *pftLastWriteTime; // patch this up
  669. #ifdef DEBUG
  670. {
  671. FILETIME ftCreate, ftAccessed, ftWrite;
  672. if (GetFileTime((HANDLE)hfile, &ftCreate, &ftAccessed, &ftWrite))
  673. {
  674. // we can't be sure that ftCreate == pftCreationTime because the GetFileTime
  675. // spec says that the granularity of Set and Get may be different.
  676. DumpTimes(ftCreate, ftAccessed, ftWrite);
  677. }
  678. }
  679. #endif
  680. }
  681. else
  682. {
  683. DebugMsg(DM_TRACE, TEXT("unable to set create time"));
  684. }
  685. CloseHandle(hfile);
  686. }
  687. }
  688. }
  689. //
  690. // sets the current links find data and link tracker based on a path.
  691. //
  692. // returns:
  693. // S_OK the file/folder is there
  694. // FAILED(hr) the file could not be found
  695. // _bDirty set if the find data for the file (or tracker data) has been updated
  696. //
  697. HRESULT CShellLink::_GetFindDataAndTracker(LPCTSTR pszPath)
  698. {
  699. WIN32_FIND_DATA fd = {0};
  700. HRESULT hr = S_OK;
  701. // Open the file or directory or root path. We have to set FILE_FLAG_BACKUP_SEMANTICS
  702. // to get CreateFile to give us directory handles.
  703. HANDLE hFile = CreateFile(pszPath,
  704. FILE_READ_ATTRIBUTES,
  705. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
  706. NULL, OPEN_EXISTING,
  707. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
  708. NULL);
  709. if (INVALID_HANDLE_VALUE != hFile)
  710. {
  711. // Get the file attributes
  712. BY_HANDLE_FILE_INFORMATION fi;
  713. if (GetFileInformationByHandle(hFile, &fi))
  714. {
  715. fd.dwFileAttributes = fi.dwFileAttributes;
  716. fd.ftCreationTime = fi.ftCreationTime;
  717. fd.ftLastAccessTime = fi.ftLastAccessTime;
  718. fd.ftLastWriteTime = fi.ftLastWriteTime;
  719. fd.nFileSizeLow = fi.nFileSizeLow;
  720. // save the Object IDs as well.
  721. if (_ptracker)
  722. {
  723. if (SUCCEEDED(_ptracker->InitFromHandle(hFile, pszPath)))
  724. {
  725. if (_ptracker->IsDirty())
  726. _bDirty = TRUE;
  727. }
  728. else
  729. {
  730. // Save space in the .lnk file
  731. _ptracker->InitNew();
  732. }
  733. }
  734. }
  735. else
  736. {
  737. hr = GetLastHRESULT();
  738. }
  739. CloseHandle(hFile);
  740. }
  741. else
  742. {
  743. hr = GetLastHRESULT();
  744. }
  745. if (SUCCEEDED(hr))
  746. {
  747. // If this file doesn't have a create time for some reason, set it to be the
  748. // current last-write time.
  749. CheckAndFixNullCreateTime(pszPath, &fd.ftCreationTime, &fd.ftLastWriteTime);
  750. _SetFindData(&fd); // update _bDirty
  751. }
  752. return hr;
  753. }
  754. // IShellLink::SetIDList()
  755. //
  756. // note: the error returns here are really poor, they don't express
  757. // any failures that might have occured (out of memory for example)
  758. STDMETHODIMP CShellLink::SetIDList(LPCITEMIDLIST pidlnew)
  759. {
  760. _SetPIDLPath(pidlnew, NULL, TRUE);
  761. return S_OK; // return
  762. }
  763. BOOL DifferentStrings(LPCTSTR psz1, LPCTSTR psz2)
  764. {
  765. if (psz1 && psz2)
  766. {
  767. return lstrcmp(psz1, psz2);
  768. }
  769. else
  770. {
  771. return (!psz1 && psz2) || (psz1 && !psz2);
  772. }
  773. }
  774. // NOTE: NULL string ptr is valid argument for this function
  775. HRESULT CShellLink::_SetField(LPTSTR *ppszField, LPCWSTR pszValueW)
  776. {
  777. TCHAR szValue[INFOTIPSIZE], *pszValue;
  778. if (pszValueW)
  779. {
  780. SHUnicodeToTChar(pszValueW, szValue, ARRAYSIZE(szValue));
  781. pszValue = szValue;
  782. }
  783. else
  784. {
  785. pszValue = NULL;
  786. }
  787. if (DifferentStrings(*ppszField, pszValue))
  788. {
  789. _bDirty = TRUE;
  790. }
  791. Str_SetPtr(ppszField, pszValue);
  792. return S_OK;
  793. }
  794. HRESULT CShellLink::_SetField(LPTSTR *ppszField, LPCSTR pszValueA)
  795. {
  796. TCHAR szValue[INFOTIPSIZE], *pszValue;
  797. if (pszValueA)
  798. {
  799. SHAnsiToTChar(pszValueA, szValue, ARRAYSIZE(szValue));
  800. pszValue = szValue;
  801. }
  802. else
  803. {
  804. pszValue = NULL;
  805. }
  806. if (DifferentStrings(*ppszField, pszValue))
  807. {
  808. _bDirty = TRUE;
  809. }
  810. Str_SetPtr(ppszField, pszValue);
  811. return S_OK;
  812. }
  813. HRESULT CShellLink::_GetField(LPCTSTR pszField, LPWSTR pszValue, int cchValue)
  814. {
  815. if (pszField == NULL)
  816. {
  817. *pszValue = 0;
  818. }
  819. else
  820. {
  821. SHLoadIndirectString(pszField, pszValue, cchValue, NULL);
  822. }
  823. return S_OK;
  824. }
  825. HRESULT CShellLink::_GetField(LPCTSTR pszField, LPSTR pszValue, int cchValue)
  826. {
  827. LPWSTR pwsz = (LPWSTR)alloca(cchValue * sizeof(WCHAR));
  828. _GetField(pszField, pwsz, cchValue);
  829. SHUnicodeToAnsi(pwsz, pszValue, cchValue);
  830. return S_OK;
  831. }
  832. // order is important
  833. const int c_rgcsidlUserFolders[] = {
  834. CSIDL_MYPICTURES | TEST_SUBFOLDER,
  835. CSIDL_PERSONAL | TEST_SUBFOLDER,
  836. CSIDL_DESKTOPDIRECTORY | TEST_SUBFOLDER,
  837. CSIDL_COMMON_DESKTOPDIRECTORY | TEST_SUBFOLDER,
  838. };
  839. STDAPI_(void) SHMakeDescription(LPCITEMIDLIST pidlDesc, int ids, LPTSTR pszDesc, UINT cch)
  840. {
  841. LPCITEMIDLIST pidlName = pidlDesc;
  842. TCHAR szPath[MAX_PATH], szFormat[64];
  843. DWORD gdn;
  844. ASSERT(pidlDesc);
  845. //
  846. // we want to only show the INFOLDER name for
  847. // folders the user sees often. so in the desktop
  848. // or mydocs or mypics we just show that name.
  849. // otherwise show the whole path.
  850. //
  851. // NOTE - there can be some weirdness if you start making
  852. // shortcuts to special folders off the desktop
  853. // specifically if you make a shortcut to mydocs the comment
  854. // ends up being %USERPROFILE%, but this is a rare enough
  855. // case that i dont think we need to worry too much.
  856. //
  857. SHGetNameAndFlags(pidlDesc, SHGDN_FORPARSING, szPath, ARRAYSIZE(szPath), NULL);
  858. int csidl = GetSpecialFolderID(szPath, c_rgcsidlUserFolders, ARRAYSIZE(c_rgcsidlUserFolders));
  859. if (-1 != csidl)
  860. {
  861. gdn = SHGDN_INFOLDER | SHGDN_FORADDRESSBAR;
  862. switch (csidl)
  863. {
  864. case CSIDL_DESKTOPDIRECTORY:
  865. case CSIDL_COMMON_DESKTOPDIRECTORY:
  866. {
  867. ULONG cb;
  868. if (csidl == GetSpecialFolderParentIDAndOffset(pidlDesc, &cb))
  869. {
  870. // reorient based off the desktop.
  871. pidlName = (LPCITEMIDLIST)(((BYTE *)pidlDesc) + cb);
  872. }
  873. }
  874. break;
  875. case CSIDL_PERSONAL:
  876. if (SUCCEEDED(GetMyDocumentsDisplayName(szPath, ARRAYSIZE(szPath))))
  877. pidlName = NULL;
  878. break;
  879. default:
  880. break;
  881. }
  882. }
  883. else
  884. gdn = SHGDN_FORPARSING | SHGDN_FORADDRESSBAR;
  885. if (pidlName)
  886. {
  887. SHGetNameAndFlags(pidlName, gdn, szPath, ARRAYSIZE(szPath), NULL);
  888. }
  889. #if 0 // if we ever want to handle frienly URL comments
  890. if (UrlIs(pszPath, URLIS_URL))
  891. {
  892. DWORD cchPath = SIZECHARS(szPath);
  893. if (FAILED(UrlCombine(pszPath, TEXT(""), szPath, &cchPath, 0)))
  894. {
  895. // if the URL is too big, then just use the hostname...
  896. cchPath = SIZECHARS(szPath);
  897. UrlCombine(pszPath, TEXT("/"), szPath, &cchPath, 0);
  898. }
  899. }
  900. #endif
  901. if (ids != -1)
  902. {
  903. LoadString(HINST_THISDLL, ids, szFormat, ARRAYSIZE(szFormat));
  904. wnsprintf(pszDesc, cch, szFormat, szPath);
  905. }
  906. else
  907. StrCpyN(pszDesc, szPath, cch);
  908. }
  909. void _MakeDescription(LPCITEMIDLIST pidlTo, LPTSTR pszDesc, UINT cch)
  910. {
  911. LPCITEMIDLIST pidlInner;
  912. if (ILIsRooted(pidlTo))
  913. {
  914. pidlInner = ILRootedFindIDList(pidlTo);
  915. }
  916. else
  917. {
  918. pidlInner = pidlTo;
  919. }
  920. LPITEMIDLIST pidlParent = ILCloneParent(pidlInner);
  921. if (pidlParent)
  922. {
  923. SHMakeDescription(pidlParent, IDS_LOCATION, pszDesc, cch);
  924. ILFree(pidlParent);
  925. }
  926. else
  927. {
  928. *pszDesc = 0;
  929. }
  930. }
  931. STDMETHODIMP CShellLink::GetDescription(LPWSTR pszDesc, int cchMax)
  932. {
  933. return _GetField(_pszName, pszDesc, cchMax);
  934. }
  935. STDMETHODIMP CShellLink::GetDescription(LPSTR pszDesc, int cchMax)
  936. {
  937. return _GetField(_pszName, pszDesc, cchMax);
  938. }
  939. STDMETHODIMP CShellLink::SetDescription(LPCWSTR pszDesc)
  940. {
  941. return _SetField(&_pszName, pszDesc);
  942. }
  943. STDMETHODIMP CShellLink::SetDescription(LPCSTR pszDesc)
  944. {
  945. return _SetField(&_pszName, pszDesc);
  946. }
  947. STDMETHODIMP CShellLink::GetWorkingDirectory(LPWSTR pszDir, int cchDir)
  948. {
  949. return _GetField(_pszWorkingDir, pszDir, cchDir);
  950. }
  951. STDMETHODIMP CShellLink::GetWorkingDirectory(LPSTR pszDir, int cchDir)
  952. {
  953. return _GetField(_pszWorkingDir, pszDir, cchDir);
  954. }
  955. STDMETHODIMP CShellLink::SetWorkingDirectory(LPCWSTR pszWorkingDir)
  956. {
  957. return _SetField(&_pszWorkingDir, pszWorkingDir);
  958. }
  959. STDMETHODIMP CShellLink::SetWorkingDirectory(LPCSTR pszDir)
  960. {
  961. return _SetField(&_pszWorkingDir, pszDir);
  962. }
  963. STDMETHODIMP CShellLink::GetArguments(LPWSTR pszArgs, int cchArgs)
  964. {
  965. return _GetField(_pszArgs, pszArgs, cchArgs);
  966. }
  967. STDMETHODIMP CShellLink::GetArguments(LPSTR pszArgs, int cch)
  968. {
  969. return _GetField(_pszArgs, pszArgs, cch);
  970. }
  971. STDMETHODIMP CShellLink::SetArguments(LPCWSTR pszArgs)
  972. {
  973. return _SetField(&_pszArgs, pszArgs);
  974. }
  975. STDMETHODIMP CShellLink::SetArguments(LPCSTR pszArgs)
  976. {
  977. return _SetField(&_pszArgs, pszArgs);
  978. }
  979. STDMETHODIMP CShellLink::GetHotkey(WORD *pwHotkey)
  980. {
  981. *pwHotkey = _sld.wHotkey;
  982. return S_OK;
  983. }
  984. STDMETHODIMP CShellLink::SetHotkey(WORD wHotkey)
  985. {
  986. if (_sld.wHotkey != wHotkey)
  987. {
  988. _bDirty = TRUE;
  989. _sld.wHotkey = wHotkey;
  990. }
  991. return S_OK;
  992. }
  993. STDMETHODIMP CShellLink::GetShowCmd(int *piShowCmd)
  994. {
  995. *piShowCmd = _sld.iShowCmd;
  996. return S_OK;
  997. }
  998. STDMETHODIMP CShellLink::SetShowCmd(int iShowCmd)
  999. {
  1000. if (_sld.iShowCmd != iShowCmd)
  1001. {
  1002. _bDirty = TRUE;
  1003. }
  1004. _sld.iShowCmd = iShowCmd;
  1005. return S_OK;
  1006. }
  1007. // IShellLinkW::GetIconLocation
  1008. STDMETHODIMP CShellLink::GetIconLocation(LPWSTR pszIconPath, int cchIconPath, int *piIcon)
  1009. {
  1010. VDATEINPUTBUF(pszIconPath, TCHAR, cchIconPath);
  1011. _UpdateIconFromExpIconSz();
  1012. _GetField(_pszIconLocation, pszIconPath, cchIconPath);
  1013. *piIcon = _sld.iIcon;
  1014. return S_OK;
  1015. }
  1016. // IShellLinkA::GetIconLocation
  1017. STDMETHODIMP CShellLink::GetIconLocation(LPSTR pszPath, int cch, int *piIcon)
  1018. {
  1019. WCHAR szPath[MAX_PATH];
  1020. HRESULT hr = GetIconLocation(szPath, ARRAYSIZE(szPath), piIcon);
  1021. if (SUCCEEDED(hr))
  1022. {
  1023. SHUnicodeToAnsi(szPath, pszPath, cch);
  1024. }
  1025. return hr;
  1026. }
  1027. // IShellLinkW::SetIconLocation
  1028. // NOTE:
  1029. // pszIconPath may be NULL
  1030. STDMETHODIMP CShellLink::SetIconLocation(LPCWSTR pszIconPath, int iIcon)
  1031. {
  1032. TCHAR szIconPath[MAX_PATH];
  1033. if (pszIconPath)
  1034. {
  1035. SHUnicodeToTChar(pszIconPath, szIconPath, ARRAYSIZE(szIconPath));
  1036. }
  1037. if (pszIconPath)
  1038. {
  1039. HANDLE hToken;
  1040. TCHAR szIconPathEnc[MAX_PATH];
  1041. if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken) == FALSE)
  1042. {
  1043. hToken = NULL;
  1044. }
  1045. if (PathUnExpandEnvStringsForUser(hToken, szIconPath, szIconPathEnc, ARRAYSIZE(szIconPathEnc)) != 0)
  1046. {
  1047. EXP_SZ_LINK expLink;
  1048. // mark that link has expandable strings, and add them
  1049. _sld.dwFlags |= SLDF_HAS_EXP_ICON_SZ; // should this be unique for icons?
  1050. LPEXP_SZ_LINK lpNew = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_ICON_SIG);
  1051. if (!lpNew)
  1052. {
  1053. lpNew = &expLink;
  1054. expLink.cbSize = 0;
  1055. expLink.dwSignature = EXP_SZ_ICON_SIG;
  1056. }
  1057. // store both A and W version (for no good reason!)
  1058. SHTCharToAnsi(szIconPathEnc, lpNew->szTarget, ARRAYSIZE(lpNew->szTarget));
  1059. SHTCharToUnicode(szIconPathEnc, lpNew->swzTarget, ARRAYSIZE(lpNew->swzTarget));
  1060. // See if this is a new entry that we need to add
  1061. if (lpNew->cbSize == 0)
  1062. {
  1063. lpNew->cbSize = sizeof(*lpNew);
  1064. _AddExtraDataSection((DATABLOCK_HEADER *)lpNew);
  1065. }
  1066. }
  1067. else
  1068. {
  1069. _sld.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ;
  1070. _RemoveExtraDataSection(EXP_SZ_ICON_SIG);
  1071. }
  1072. if (hToken != NULL)
  1073. {
  1074. CloseHandle(hToken);
  1075. }
  1076. }
  1077. _SetField(&_pszIconLocation, pszIconPath);
  1078. if (_sld.iIcon != iIcon)
  1079. {
  1080. _sld.iIcon = iIcon;
  1081. _bDirty = TRUE;
  1082. }
  1083. if ((_sld.dwFlags & SLDF_HAS_DARWINID) && pszIconPath)
  1084. {
  1085. // NOTE: The comment below is for darwin as it shipped in win98/IE4.01,
  1086. // and is fixed in the > NT5 versions of the shell's darwin implementation.
  1087. //
  1088. // for darwin enalbed links, we make the path point to the
  1089. // icon location (which is must be of the type (ie same ext) as the real
  1090. // destination. So, if I want a darwin link to readme.txt, the shell
  1091. // needs the icon to be icon1.txt, which is not good!!. This ensures
  1092. // that the context menu will be correct and allows us to return
  1093. // from CShellLink::GetPath & CShellLink::GetIDList without faulting the
  1094. // application in because we lie to people and tell them that we
  1095. // really point to our icon, which is the same type as the real target,
  1096. // thus making our context menu be correct.
  1097. _SetPIDLPath(NULL, szIconPath, FALSE);
  1098. }
  1099. return S_OK;
  1100. }
  1101. // IShellLinkA::SetIconLocation
  1102. STDMETHODIMP CShellLink::SetIconLocation(LPCSTR pszPath, int iIcon)
  1103. {
  1104. WCHAR szPath[MAX_PATH];
  1105. LPWSTR pszPathW;
  1106. if (pszPath)
  1107. {
  1108. SHAnsiToUnicode(pszPath, szPath, ARRAYSIZE(szPath));
  1109. pszPathW = szPath;
  1110. }
  1111. else
  1112. {
  1113. pszPathW = NULL;
  1114. }
  1115. return SetIconLocation(pszPathW, iIcon);
  1116. }
  1117. HRESULT CShellLink::_InitExtractImage()
  1118. {
  1119. HRESULT hr;
  1120. if (_pxthumb)
  1121. {
  1122. hr = S_OK;
  1123. }
  1124. else
  1125. {
  1126. hr = _GetUIObject(NULL, IID_PPV_ARG(IExtractImage, &_pxthumb));
  1127. }
  1128. return hr;
  1129. }
  1130. // IExtractImage
  1131. STDMETHODIMP CShellLink::GetLocation(LPWSTR pszPathBuffer, DWORD cch,
  1132. DWORD * pdwPriority, const SIZE * prgSize,
  1133. DWORD dwRecClrDepth, DWORD *pdwFlags)
  1134. {
  1135. HRESULT hr = _InitExtractImage();
  1136. if (SUCCEEDED(hr))
  1137. {
  1138. hr = _pxthumb->GetLocation(pszPathBuffer, cch, pdwPriority, prgSize, dwRecClrDepth, pdwFlags);
  1139. }
  1140. return hr;
  1141. }
  1142. STDMETHODIMP CShellLink::Extract(HBITMAP *phBmpThumbnail)
  1143. {
  1144. HRESULT hr = _InitExtractImage();
  1145. if (SUCCEEDED(hr))
  1146. {
  1147. hr = _pxthumb->Extract(phBmpThumbnail);
  1148. }
  1149. return hr;
  1150. }
  1151. STDMETHODIMP CShellLink::GetDateStamp(FILETIME *pftDateStamp)
  1152. {
  1153. HRESULT hr = _InitExtractImage();
  1154. if (SUCCEEDED(hr))
  1155. {
  1156. IExtractImage2 * pExtract2;
  1157. hr = _pxthumb->QueryInterface(IID_PPV_ARG(IExtractImage2, &pExtract2));
  1158. if (SUCCEEDED(hr))
  1159. {
  1160. hr = pExtract2->GetDateStamp(pftDateStamp);
  1161. pExtract2->Release();
  1162. }
  1163. }
  1164. return hr;
  1165. }
  1166. // set the relative path, this is used before a link is saved so we know what
  1167. // we should use to store the link relative to as well as before the link is resolved
  1168. // so we know the new path to use with the saved relative path.
  1169. //
  1170. // in:
  1171. // pszPathRel path to make link target relative to, must be a path to
  1172. // a file, not a directory.
  1173. //
  1174. // dwReserved must be 0
  1175. //
  1176. // returns:
  1177. // S_OK relative path is set
  1178. //
  1179. STDMETHODIMP CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD dwRes)
  1180. {
  1181. if (dwRes != 0)
  1182. {
  1183. return E_INVALIDARG;
  1184. }
  1185. return _SetField(&_pszRelSource, pszPathRel);
  1186. }
  1187. STDMETHODIMP CShellLink::SetRelativePath(LPCSTR pszPathRel, DWORD dwRes)
  1188. {
  1189. if (dwRes != 0)
  1190. {
  1191. return E_INVALIDARG;
  1192. }
  1193. return _SetField(&_pszRelSource, pszPathRel);
  1194. }
  1195. // IShellLink::Resolve()
  1196. //
  1197. // If SLR_UPDATE isn't set, check IPersistFile::IsDirty after
  1198. // calling this to see if the link info has changed and save it.
  1199. //
  1200. // returns:
  1201. // S_OK all things good
  1202. // S_FALSE user canceled (bummer, should be ERROR_CANCELLED)
  1203. STDMETHODIMP CShellLink::Resolve(HWND hwnd, DWORD dwResolveFlags)
  1204. {
  1205. HRESULT hr = _Resolve(hwnd, dwResolveFlags, 0);
  1206. if (HRESULT_FROM_WIN32(ERROR_CANCELLED) == hr)
  1207. {
  1208. hr = S_FALSE;
  1209. }
  1210. return hr;
  1211. }
  1212. // converts version in text format (a,b,c,d) into two dwords (a,b), (c,d)
  1213. // The printed version number is of format a.b.d (but, we don't care)
  1214. // NOTE: Stolen from inet\urlmon\download\helpers.cxx
  1215. HRESULT GetVersionFromString(TCHAR *szBuf, DWORD *pdwFileVersionMS, DWORD *pdwFileVersionLS)
  1216. {
  1217. const TCHAR *pch = szBuf;
  1218. TCHAR ch;
  1219. USHORT n = 0;
  1220. USHORT a = 0;
  1221. USHORT b = 0;
  1222. USHORT c = 0;
  1223. USHORT d = 0;
  1224. enum HAVE { HAVE_NONE, HAVE_A, HAVE_B, HAVE_C, HAVE_D } have = HAVE_NONE;
  1225. *pdwFileVersionMS = 0;
  1226. *pdwFileVersionLS = 0;
  1227. if (!pch) // default to zero if none provided
  1228. return S_OK;
  1229. if (lstrcmp(pch, TEXT("-1,-1,-1,-1")) == 0)
  1230. {
  1231. *pdwFileVersionMS = 0xffffffff;
  1232. *pdwFileVersionLS = 0xffffffff;
  1233. return S_OK;
  1234. }
  1235. for (ch = *pch++;;ch = *pch++)
  1236. {
  1237. if ((ch == ',') || (ch == '\0'))
  1238. {
  1239. switch (have)
  1240. {
  1241. case HAVE_NONE:
  1242. a = n;
  1243. have = HAVE_A;
  1244. break;
  1245. case HAVE_A:
  1246. b = n;
  1247. have = HAVE_B;
  1248. break;
  1249. case HAVE_B:
  1250. c = n;
  1251. have = HAVE_C;
  1252. break;
  1253. case HAVE_C:
  1254. d = n;
  1255. have = HAVE_D;
  1256. break;
  1257. case HAVE_D:
  1258. return E_INVALIDARG; // invalid arg
  1259. }
  1260. if (ch == '\0')
  1261. {
  1262. // all done convert a,b,c,d into two dwords of version
  1263. *pdwFileVersionMS = ((a << 16)|b);
  1264. *pdwFileVersionLS = ((c << 16)|d);
  1265. return S_OK;
  1266. }
  1267. n = 0; // reset
  1268. }
  1269. else if ((ch < '0') || (ch > '9'))
  1270. return E_INVALIDARG; // invalid arg
  1271. else
  1272. n = n*10 + (ch - '0');
  1273. } /* end forever */
  1274. // NEVERREACHED
  1275. }
  1276. // Purpose: A _Resolve-time check to see if the link has
  1277. // Logo3 application channel
  1278. //
  1279. // Inputs: [LPCTSTR] - pszLogo3ID - the id/keyname for
  1280. // our Logo3 software.
  1281. //
  1282. // Outputs: [BOOL]
  1283. // - TRUE if our peek at the registry
  1284. // indicates we have an ad to show
  1285. // - FALSE indicates no new version
  1286. // to advertise.
  1287. //
  1288. // Algorithm: Check the software update registry info for the
  1289. // ID embedded in the link. This is a sleazy hack
  1290. // to avoid loading shdocvw and urlmon, which are
  1291. // the normal code path for this check.
  1292. // NOTE: The version checking logic is stolen from
  1293. // shell\shdocvw\sftupmb.cpp
  1294. HRESULT GetLogo3SoftwareUpdateInfo(LPCTSTR pszLogo3ID, LPSOFTDISTINFO psdi)
  1295. {
  1296. HRESULT hr = S_OK;
  1297. HKEY hkeyDistInfo = 0;
  1298. HKEY hkeyAvail = 0;
  1299. HKEY hkeyAdvertisedVersion = 0;
  1300. DWORD lResult = 0;
  1301. DWORD dwSize = 0;
  1302. DWORD dwType;
  1303. TCHAR szBuffer[MAX_PATH];
  1304. TCHAR szVersionBuf[MAX_PATH];
  1305. DWORD dwLen = 0;
  1306. DWORD dwCurAdvMS = 0;
  1307. DWORD dwCurAdvLS = 0;
  1308. wsprintf(szBuffer,
  1309. TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"),
  1310. pszLogo3ID);
  1311. if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuffer, 0, KEY_READ,
  1312. &hkeyDistInfo) != ERROR_SUCCESS)
  1313. {
  1314. hr = E_FAIL;
  1315. goto Exit;
  1316. }
  1317. if (RegOpenKeyEx(hkeyDistInfo, TEXT("AvailableVersion"), 0, KEY_READ,
  1318. &hkeyAvail) != ERROR_SUCCESS)
  1319. {
  1320. hr = E_FAIL;
  1321. goto Exit;
  1322. }
  1323. dwSize = sizeof(lResult);
  1324. if (SHQueryValueEx(hkeyAvail, TEXT("Precache"), 0, &dwType,
  1325. (unsigned char *)&lResult, &dwSize) == ERROR_SUCCESS)
  1326. {
  1327. // Precached value was the code download HR
  1328. if (lResult == S_OK)
  1329. psdi->dwFlags = SOFTDIST_FLAG_USAGE_PRECACHE;
  1330. }
  1331. dwSize = sizeof(szVersionBuf);
  1332. if (SHQueryValueEx(hkeyAvail, TEXT("AdvertisedVersion"), NULL, &dwType,
  1333. szVersionBuf, &dwSize) == ERROR_SUCCESS)
  1334. {
  1335. GetVersionFromString(szVersionBuf, &psdi->dwAdvertisedVersionMS, &psdi->dwAdvertisedVersionLS);
  1336. // Get the AdState, if any
  1337. dwSize = sizeof(psdi->dwAdState);
  1338. SHQueryValueEx(hkeyAvail, TEXT("AdState"), NULL, NULL, &psdi->dwAdState, &dwSize);
  1339. }
  1340. dwSize = sizeof(szVersionBuf);
  1341. if (SHQueryValueEx(hkeyAvail, NULL, NULL, &dwType, szVersionBuf, &dwSize) != ERROR_SUCCESS)
  1342. {
  1343. hr = S_FALSE;
  1344. goto Exit;
  1345. }
  1346. if (FAILED(GetVersionFromString(szVersionBuf, &psdi->dwUpdateVersionMS, &psdi->dwUpdateVersionLS)))
  1347. {
  1348. hr = S_FALSE;
  1349. goto Exit;
  1350. }
  1351. dwLen = sizeof(psdi->dwInstalledVersionMS);
  1352. if (SHQueryValueEx(hkeyDistInfo, TEXT("VersionMajor"), 0, &dwType,
  1353. &psdi->dwInstalledVersionMS, &dwLen) != ERROR_SUCCESS)
  1354. {
  1355. hr = S_FALSE;
  1356. goto Exit;
  1357. }
  1358. dwLen = sizeof(psdi->dwInstalledVersionLS);
  1359. if (SHQueryValueEx(hkeyDistInfo, TEXT("VersionMinor"), 0, &dwType,
  1360. &psdi->dwInstalledVersionLS, &dwLen) != ERROR_SUCCESS)
  1361. {
  1362. hr = S_FALSE;
  1363. goto Exit;
  1364. }
  1365. if (psdi->dwUpdateVersionMS > psdi->dwInstalledVersionMS ||
  1366. (psdi->dwUpdateVersionMS == psdi->dwInstalledVersionMS &&
  1367. psdi->dwUpdateVersionLS > psdi->dwInstalledVersionLS))
  1368. {
  1369. hr = S_OK;
  1370. }
  1371. else
  1372. {
  1373. hr = S_FALSE;
  1374. }
  1375. Exit:
  1376. if (hkeyAdvertisedVersion)
  1377. {
  1378. RegCloseKey(hkeyAdvertisedVersion);
  1379. }
  1380. if (hkeyAvail)
  1381. {
  1382. RegCloseKey(hkeyAvail);
  1383. }
  1384. if (hkeyDistInfo)
  1385. {
  1386. RegCloseKey(hkeyDistInfo);
  1387. }
  1388. return hr;
  1389. }
  1390. // Purpose: A _Resolve-time check to see if the link has
  1391. // Logo3 application channel
  1392. //
  1393. // Inputs: [LPCTSTR] - pszLogo3ID - the id/keyname for
  1394. // our Logo3 software.
  1395. //
  1396. // Outputs: [BOOL]
  1397. // - TRUE if our peek at the registry
  1398. // indicates we have an ad to show
  1399. // - FALSE indicates no new version
  1400. // to advertise.
  1401. //
  1402. // Algorithm: Check the software update registry info for the
  1403. // ID embedded in the link. This is a sleazy hack
  1404. // to avoid loading shdocvw and urlmon, which are
  1405. // the normal code path for this check.
  1406. // The version checking logic is stolen from
  1407. BOOL FLogo3RegPeek(LPCTSTR pszLogo3ID)
  1408. {
  1409. BOOL bHaveAd = FALSE;
  1410. SOFTDISTINFO sdi = { 0 };
  1411. DWORD dwAdStateNew = SOFTDIST_ADSTATE_NONE;
  1412. HRESULT hr = GetLogo3SoftwareUpdateInfo(pszLogo3ID, &sdi);
  1413. // we need an HREF to work properly. The title and abstract are negotiable.
  1414. if (SUCCEEDED(hr))
  1415. {
  1416. // see if this is an update the user already knows about.
  1417. // If it is, then skip the dialog.
  1418. if ( (sdi.dwUpdateVersionMS >= sdi.dwInstalledVersionMS ||
  1419. (sdi.dwUpdateVersionMS == sdi.dwInstalledVersionMS &&
  1420. sdi.dwUpdateVersionLS >= sdi.dwInstalledVersionLS)) &&
  1421. (sdi.dwUpdateVersionMS >= sdi.dwAdvertisedVersionMS ||
  1422. (sdi.dwUpdateVersionMS == sdi.dwAdvertisedVersionMS &&
  1423. sdi.dwUpdateVersionLS >= sdi.dwAdvertisedVersionLS)))
  1424. {
  1425. if (hr == S_OK) // new version
  1426. {
  1427. // we have a pending update, either on the net, or downloaded
  1428. if (sdi.dwFlags & SOFTDIST_FLAG_USAGE_PRECACHE)
  1429. {
  1430. dwAdStateNew = SOFTDIST_ADSTATE_DOWNLOADED;
  1431. }
  1432. else
  1433. {
  1434. dwAdStateNew = SOFTDIST_ADSTATE_AVAILABLE;
  1435. }
  1436. }
  1437. else if (sdi.dwUpdateVersionMS == sdi.dwInstalledVersionMS &&
  1438. sdi.dwUpdateVersionLS == sdi.dwInstalledVersionLS)
  1439. {
  1440. // if installed version matches advertised, then we autoinstalled already
  1441. // NOTE: If the user gets gets channel notification, then runs out
  1442. // to the store and buys the new version, then installs it, we'll
  1443. // mistake this for an auto-install.
  1444. dwAdStateNew = SOFTDIST_ADSTATE_INSTALLED;
  1445. }
  1446. // only show the dialog if we've haven't been in this ad state before for
  1447. // this update version
  1448. if (dwAdStateNew > sdi.dwAdState)
  1449. {
  1450. bHaveAd = TRUE;
  1451. }
  1452. } // if update is a newer version than advertised
  1453. }
  1454. return bHaveAd;
  1455. }
  1456. // Purpose: A _Resolve-time check to see if the link has
  1457. // Logo3 application channel
  1458. //
  1459. // Inputs: [HWND] hwnd
  1460. // - The parent window (which could be the desktop).
  1461. // [DWORD] dwResolveFlags
  1462. // - Flags from the SLR_FLAGS enumeration.
  1463. //
  1464. // returns:
  1465. // S_OK The user wants to pursue the
  1466. // software update thus we should not continue
  1467. //
  1468. // S_FALSE No software update, or the user doesn't want it now.
  1469. // proceed with regular resolve path
  1470. //
  1471. // Algorithm: Check the software update registry info for the
  1472. // ID embedded in the link. If there's a new version
  1473. // advertised, prompt the user with shdocvw's message
  1474. // box. If the mb says update, tell the caller we
  1475. // don't want the link target, as we're headed to the
  1476. // link update page.
  1477. HRESULT CShellLink::_ResolveLogo3Link(HWND hwnd, DWORD dwResolveFlags)
  1478. {
  1479. HRESULT hr = S_FALSE; // default to no update.
  1480. if ((_sld.dwFlags & SLDF_HAS_LOGO3ID) &&
  1481. !SHRestricted(REST_NOLOGO3CHANNELNOTIFY))
  1482. {
  1483. LPEXP_DARWIN_LINK pdl = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, EXP_LOGO3_ID_SIG);
  1484. if (pdl)
  1485. {
  1486. TCHAR szLogo3ID[MAX_PATH];
  1487. WCHAR szwLogo3ID[MAX_PATH];
  1488. int cchBlessData;
  1489. WCHAR *pwch;
  1490. TCHAR *pch = pdl->szwDarwinID;
  1491. // Ideally, we support multiple, semi-colon delmited IDs, for now
  1492. // just grab the first one.
  1493. for (pwch = pdl->szwDarwinID, cchBlessData = 0;
  1494. *pch != ';' && *pch != '\0' && cchBlessData < MAX_PATH;
  1495. pch++, pwch++, cchBlessData++)
  1496. {
  1497. szLogo3ID[cchBlessData] = *pch;
  1498. szwLogo3ID[cchBlessData] = *pwch;
  1499. }
  1500. // and terminate
  1501. szLogo3ID[cchBlessData] = '\0';
  1502. szwLogo3ID[cchBlessData] = L'\0';
  1503. // Before well haul in shdocvw, we'll sneak a peak at our Logo3 reg goo
  1504. if (!(dwResolveFlags & SLR_NO_UI) && FLogo3RegPeek(szLogo3ID))
  1505. {
  1506. // stuff stolen from shdocvw\util.cpp's CheckSoftwareUpdateUI
  1507. BOOL fLaunchUpdate = FALSE;
  1508. SOFTDISTINFO sdi = { 0 };
  1509. sdi.cbSize = sizeof(sdi);
  1510. int nRes = SoftwareUpdateMessageBox(hwnd, szwLogo3ID, 0, &sdi);
  1511. if (nRes != IDABORT)
  1512. {
  1513. if (nRes == IDYES)
  1514. {
  1515. // NOTE: This differ's from Shdocvw in that we don't
  1516. // have the cool internal navigation stuff to play with.
  1517. // Originally, this was done with ShellExecEx. This failed
  1518. // because the http hook wasn't 100% reliable on Win95.
  1519. //ShellExecuteW(NULL, NULL, sdi.szHREF, NULL, NULL, 0);
  1520. hr = HlinkNavigateString(NULL, sdi.szHREF);
  1521. } // if user wants update
  1522. if (sdi.szTitle != NULL)
  1523. SHFree(sdi.szTitle);
  1524. if (sdi.szAbstract != NULL)
  1525. SHFree(sdi.szAbstract);
  1526. if (sdi.szHREF != NULL)
  1527. SHFree(sdi.szHREF);
  1528. fLaunchUpdate = nRes == IDYES && SUCCEEDED(hr);
  1529. } // if no message box abort (error)
  1530. if (fLaunchUpdate)
  1531. {
  1532. hr = S_OK;
  1533. }
  1534. }
  1535. }
  1536. }
  1537. return hr;
  1538. }
  1539. BOOL _TryRestoreConnection(HWND hwnd, LPCTSTR pszPath)
  1540. {
  1541. BOOL bRet = FALSE;
  1542. if (!PathIsUNC(pszPath) && IsDisconnectedNetDrive(DRIVEID(pszPath)))
  1543. {
  1544. TCHAR szDrive[4];
  1545. szDrive[0] = *pszPath;
  1546. szDrive[1] = TEXT(':');
  1547. szDrive[2] = 0;
  1548. bRet = WNetRestoreConnection(hwnd, szDrive) == WN_SUCCESS;
  1549. }
  1550. return bRet;
  1551. }
  1552. //
  1553. // updates then resolves LinkInfo associated with a CShellLink instance
  1554. // if the resolve results in a new path updates the pidl to the new path
  1555. //
  1556. // in:
  1557. // hwnd to post resolve UI on (if dwFlags indicates UI)
  1558. // dwResolveFlags IShellLink::Resolve() flags
  1559. //
  1560. // in/out:
  1561. // pszPath may be updated with new path to use in case of failure
  1562. //
  1563. // returns:
  1564. // FAILED() we failed the update, either UI cancel or memory failure,
  1565. // be sure to respect ERROR_CANCELLED
  1566. // S_OK we have a valid pli and pidl read to be used OR
  1567. // we should search for this path using the link search code
  1568. HRESULT CShellLink::_ResolveLinkInfo(HWND hwnd, DWORD dwResolveFlags, LPTSTR pszPath, DWORD *pfifFlags)
  1569. {
  1570. HRESULT hr;
  1571. if (SHRestricted(REST_LINKRESOLVEIGNORELINKINFO))
  1572. {
  1573. _TryRestoreConnection((dwResolveFlags & SLR_NO_UI) ? NULL : hwnd, pszPath);
  1574. hr = _SetPIDLPath(NULL, pszPath, TRUE);
  1575. }
  1576. else
  1577. {
  1578. ASSERTMSG(_pli != NULL, "_ResolveLinkInfo should only be called when _pli != NULL");
  1579. DWORD dwLinkInfoFlags = (RLI_IFL_CONNECT | RLI_IFL_TEMPORARY);
  1580. if (!PathIsRoot(pszPath))
  1581. dwLinkInfoFlags |= RLI_IFL_LOCAL_SEARCH;
  1582. if (!(dwResolveFlags & SLR_NO_UI))
  1583. dwLinkInfoFlags |= RLI_IFL_ALLOW_UI;
  1584. ASSERT(!(dwLinkInfoFlags & RLI_IFL_UPDATE));
  1585. TCHAR szResolvedPath[MAX_PATH];
  1586. DWORD dwOutFlags;
  1587. if (ResolveLinkInfo(_pli, szResolvedPath, dwLinkInfoFlags, hwnd, &dwOutFlags, NULL))
  1588. {
  1589. ASSERT(!(dwOutFlags & RLI_OFL_UPDATED));
  1590. PathRemoveBackslash(szResolvedPath); // remove extra trailing slashes
  1591. lstrcpy(pszPath, szResolvedPath); // in case of failure, use this
  1592. // net connection might have been re-established, try again
  1593. hr = _SetPIDLPath(NULL, pszPath, TRUE);
  1594. }
  1595. else
  1596. {
  1597. // don't try searching this drive/volume again
  1598. *pfifFlags |= FIF_NODRIVE;
  1599. hr = GetLastHRESULT();
  1600. }
  1601. }
  1602. return hr;
  1603. }
  1604. DWORD TimeoutDeltaFromResolveFlags(DWORD dwResolveFlags)
  1605. {
  1606. DWORD dwTimeOutDelta;
  1607. if (SLR_NO_UI & dwResolveFlags)
  1608. {
  1609. dwTimeOutDelta = HIWORD(dwResolveFlags);
  1610. if (dwTimeOutDelta == 0)
  1611. {
  1612. dwTimeOutDelta = NOUI_SEARCH_TIMEOUT;
  1613. }
  1614. else if (dwTimeOutDelta == 0xFFFF)
  1615. {
  1616. TCHAR szTimeOut[10];
  1617. LONG cbTimeOut = sizeof(szTimeOut);
  1618. if (ERROR_SUCCESS == SHRegQueryValue(HKEY_LOCAL_MACHINE,
  1619. TEXT("Software\\Microsoft\\Tracking\\TimeOut"),
  1620. szTimeOut,
  1621. &cbTimeOut))
  1622. {
  1623. dwTimeOutDelta = StrToInt(szTimeOut);
  1624. }
  1625. else
  1626. {
  1627. dwTimeOutDelta = NOUI_SEARCH_TIMEOUT;
  1628. }
  1629. }
  1630. }
  1631. else
  1632. {
  1633. dwTimeOutDelta = UI_SEARCH_TIMEOUT;
  1634. }
  1635. return dwTimeOutDelta;
  1636. }
  1637. // allows the name space to be able to hook the resolve process and thus
  1638. // provide custom behavior. this is used for reg items and shortcuts to the
  1639. // MyDocs folder
  1640. //
  1641. // this also resolves by re-parsing the relative parsing name as the optimal way
  1642. // to run the success case of ::Resolve()
  1643. //
  1644. // returns:
  1645. // S_OK this resolution was taken care of
  1646. // HRESULT_FROM_WIN32(ERROR_CANCELLED) UI cancel
  1647. // HRESULT_FROM_WIN32(ERROR_TIMEOUT) timeout on the parse
  1648. // other FAILED() codes (implies name space did not resolve for you)
  1649. HRESULT CShellLink::_ResolveIDList(HWND hwnd, DWORD dwResolveFlags)
  1650. {
  1651. ASSERT(!(_sld.dwFlags & SLDF_HAS_DARWINID));
  1652. HRESULT hr = E_FAIL; // generic failure, we did not handle this
  1653. IShellFolder* psf;
  1654. LPCITEMIDLIST pidlChild;
  1655. if (_pidl && SUCCEEDED(SHBindToIDListParent(_pidl, IID_PPV_ARG(IShellFolder, &psf), &pidlChild)))
  1656. {
  1657. IResolveShellLink *prl = NULL;
  1658. // 2 ways to get the link resolve object
  1659. // 1. ask the folder for the resolver for the item
  1660. if (FAILED(psf->GetUIObjectOf(NULL, 1, &pidlChild, IID_PPV_ARG_NULL(IResolveShellLink, &prl))))
  1661. {
  1662. // 2. bind to the object directly and ask it (CreateViewObject)
  1663. IShellFolder *psfItem;
  1664. if (SUCCEEDED(psf->BindToObject(pidlChild, NULL, IID_PPV_ARG(IShellFolder, &psfItem))))
  1665. {
  1666. psfItem->CreateViewObject(NULL, IID_PPV_ARG(IResolveShellLink, &prl));
  1667. psfItem->Release();
  1668. }
  1669. }
  1670. if (prl)
  1671. {
  1672. hr = prl->ResolveShellLink(SAFECAST(this, IShellLink*), hwnd, dwResolveFlags);
  1673. prl->Release();
  1674. }
  1675. else
  1676. {
  1677. // perf short circuit: avoid the many net round trips that happen in
  1678. // _SetPIDLPath() in the common success case where the file is there
  1679. // we validate the target based on reparsing the relative name
  1680. //
  1681. // this is a universal way to "resolve" an object in the name space
  1682. // note, code here is very similart to SHGetRealIDL() but this version
  1683. // does not mask the error cases that we need to detect
  1684. TCHAR szName[MAX_PATH];
  1685. if (SUCCEEDED(DisplayNameOf(psf, pidlChild, SHGDN_FORPARSING | SHGDN_INFOLDER, szName, ARRAYSIZE(szName))))
  1686. {
  1687. // we limit this to file system items for compat with some name spaces
  1688. // (WinCE) that support parse, but do a bad job of it
  1689. if (SHGetAttributes(psf, pidlChild, SFGAO_FILESYSTEM))
  1690. {
  1691. IBindCtx *pbcTimeout;
  1692. BindCtx_CreateWithTimeoutDelta(TimeoutDeltaFromResolveFlags(dwResolveFlags), &pbcTimeout);
  1693. if (dwResolveFlags & SLR_NO_UI)
  1694. {
  1695. hwnd = NULL; // make sure parse does not get this
  1696. }
  1697. LPITEMIDLIST pidlChildNew;
  1698. hr = psf->ParseDisplayName(hwnd, pbcTimeout, szName, NULL, &pidlChildNew, NULL);
  1699. if (SUCCEEDED(hr))
  1700. {
  1701. // no construct the new full IDList and set that
  1702. // note many pidls here, make sure we don't leak any
  1703. LPITEMIDLIST pidlParent = ILCloneParent(_pidl);
  1704. if (pidlParent)
  1705. {
  1706. LPITEMIDLIST pidlFull = ILCombine(pidlParent, pidlChildNew);
  1707. if (pidlFull)
  1708. {
  1709. // we set this as the new target of this link,
  1710. // with FALSE for bUpdateTrackingData to avoid the cost of that
  1711. hr = _SetPIDLPath(pidlFull, NULL, FALSE);
  1712. ILFree(pidlFull);
  1713. }
  1714. ILFree(pidlParent);
  1715. }
  1716. ILFree(pidlChildNew);
  1717. }
  1718. if (pbcTimeout)
  1719. pbcTimeout->Release();
  1720. }
  1721. }
  1722. }
  1723. psf->Release();
  1724. }
  1725. return hr;
  1726. }
  1727. BOOL CShellLink::_ResolveDarwin(HWND hwnd, DWORD dwResolveFlags, HRESULT *phr)
  1728. {
  1729. // check to see if this is a Darwin link
  1730. BOOL bIsDrawinLink = _sld.dwFlags & SLDF_HAS_DARWINID;
  1731. if (bIsDrawinLink)
  1732. {
  1733. HRESULT hr = S_OK;
  1734. // we only envoke darwin if they are passing the correct SLR_INVOKE_MSI
  1735. // flag. This prevents poor apps from going and calling resolve and
  1736. // faulting in a bunch of darwin apps.
  1737. if ((dwResolveFlags & SLR_INVOKE_MSI) && IsDarwinEnabled())
  1738. {
  1739. LPEXP_DARWIN_LINK pdl = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, EXP_DARWIN_ID_SIG);
  1740. if (pdl)
  1741. {
  1742. TCHAR szDarwinCommand[MAX_PATH];
  1743. hr = ParseDarwinID(pdl->szwDarwinID, szDarwinCommand, SIZECHARS(szDarwinCommand));
  1744. if (FAILED(hr) ||
  1745. HRESULT_CODE(hr) == ERROR_SUCCESS_REBOOT_REQUIRED ||
  1746. HRESULT_CODE(hr) == ERROR_SUCCESS_REBOOT_INITIATED)
  1747. {
  1748. switch (HRESULT_CODE(hr))
  1749. {
  1750. case ERROR_INSTALL_USEREXIT: // User pressed cancel. They don't need UI.
  1751. case ERROR_SUCCESS_REBOOT_INITIATED: // Machine is going to reboot
  1752. case ERROR_SUCCESS_REBOOT_REQUIRED:
  1753. // dont run the darwin app in all of the above cases,
  1754. // ERROR_CANCELLED suppresses further error UI
  1755. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1756. break;
  1757. default:
  1758. if (!(dwResolveFlags & SLR_NO_UI))
  1759. {
  1760. TCHAR szTemp[MAX_PATH];
  1761. FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, HRESULT_CODE(hr), 0, szTemp, ARRAYSIZE(szTemp), NULL);
  1762. ShellMessageBox(HINST_THISDLL, hwnd, szTemp,
  1763. MAKEINTRESOURCE(IDS_LINKERROR),
  1764. MB_OK | MB_ICONSTOP, NULL, NULL);
  1765. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1766. }
  1767. break;
  1768. }
  1769. }
  1770. else
  1771. {
  1772. // We want to fire an event for the product code, not the path. Do this here since we've got the product code.
  1773. if (_pcbDarwin)
  1774. {
  1775. _pcbDarwin->SetProductCodeFromDarwinID(pdl->szwDarwinID);
  1776. }
  1777. PathUnquoteSpaces(szDarwinCommand);
  1778. hr = _SetPIDLPath(NULL, szDarwinCommand, FALSE);
  1779. }
  1780. }
  1781. }
  1782. *phr = hr;
  1783. }
  1784. return bIsDrawinLink;
  1785. }
  1786. // if the link has encoded env vars we will set them now, possibly updating _pidl
  1787. void CShellLink::_SetIDListFromEnvVars()
  1788. {
  1789. TCHAR szPath[MAX_PATH];
  1790. // check to see whether this link has expandable environment strings
  1791. if (_GetExpandedPath(szPath, ARRAYSIZE(szPath)))
  1792. {
  1793. if (FAILED(_SetPIDLPath(NULL, szPath, TRUE)))
  1794. {
  1795. // The target file is no longer valid so we should dump the EXP_SZ section before
  1796. // we continue. Note that we don't set bDirty here, that is only set later if
  1797. // we actually resolve this link to a new path or pidl. The result is we'll only
  1798. // save this modification if a new target is found and accepted by the user.
  1799. _sld.dwFlags &= ~SLDF_HAS_EXP_SZ;
  1800. _SetSimplePIDL(szPath);
  1801. }
  1802. }
  1803. }
  1804. HRESULT CShellLink::_ResolveRemovable(HWND hwnd, LPCTSTR pszPath)
  1805. {
  1806. HANDLE hfind;
  1807. WIN32_FIND_DATA fd;
  1808. HRESULT hr = FindFirstRetryRemovable(hwnd, _punkSite, pszPath, &fd, &hfind);
  1809. if (S_OK == hr)
  1810. {
  1811. FindClose(hfind); // throw that out
  1812. hr = _SetPIDLPath(NULL, pszPath, TRUE);
  1813. }
  1814. return hr;
  1815. }
  1816. _inline BOOL FAILED_AND_NOT_STOP_ERROR(HRESULT hr)
  1817. {
  1818. return FAILED(hr) && (HRESULT_FROM_WIN32(ERROR_CANCELLED) != hr) && (HRESULT_FROM_WIN32(ERROR_TIMEOUT) != hr);
  1819. }
  1820. //
  1821. // implementation for IShellLink::Resolve and IShellLinkTracker::Resolve
  1822. //
  1823. // Inputs: hwnd
  1824. // - The parent window (which could be the desktop).
  1825. // dwResolveFlags
  1826. // - Flags from the SLR_FLAGS enumeration.
  1827. // dwTracker
  1828. // - Restrict CTracker::Resolve from the
  1829. // TrkMendRestrictions enumeration
  1830. //
  1831. // Outputs: S_OK resolution was successful
  1832. //
  1833. // Algorithm: Look for the link target and update the link path and IDList.
  1834. // Check IPersistFile::IsDirty after calling this to see if the
  1835. // link info has changed as a result.
  1836. //
  1837. HRESULT CShellLink::_Resolve(HWND hwnd, DWORD dwResolveFlags, DWORD dwTracker)
  1838. {
  1839. if (S_OK == _ResolveLogo3Link(hwnd, dwResolveFlags))
  1840. {
  1841. // the link is being updated or the user canceled
  1842. // either case we bail
  1843. return HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1844. }
  1845. HRESULT hr = S_OK;
  1846. if (!_ResolveDarwin(hwnd, dwResolveFlags, &hr))
  1847. {
  1848. _SetIDListFromEnvVars(); // possibly sets _pidl via env vars
  1849. // normal link resolve sequence starts here
  1850. hr = _ResolveIDList(hwnd, dwResolveFlags);
  1851. if (FAILED_AND_NOT_STOP_ERROR(hr))
  1852. {
  1853. TCHAR szPath[MAX_PATH];
  1854. if (_pidl == NULL)
  1855. {
  1856. // APP COMPAT! Inso Quick View Plus demands S_OK on empty .lnk
  1857. hr = S_OK;
  1858. }
  1859. else if (SHGetPathFromIDList(_pidl, szPath) && !PathIsRoot(szPath))
  1860. {
  1861. DWORD fifFlags = 0;
  1862. // file system specific link tracking kicks in now
  1863. // see if it is where it was before...
  1864. // see if it there is a UNC or net path alias, if so try that
  1865. if (!(dwResolveFlags & SLR_NOLINKINFO) && _pli)
  1866. {
  1867. hr = _ResolveLinkInfo(hwnd, dwResolveFlags, szPath, &fifFlags);
  1868. }
  1869. else
  1870. {
  1871. hr = E_FAIL;
  1872. }
  1873. if (FAILED_AND_NOT_CANCELED(hr))
  1874. {
  1875. // use the relative path info if that is available
  1876. TCHAR szNew[MAX_PATH];
  1877. if (_GetRelativePath(szNew))
  1878. {
  1879. if (StrCmpI(szNew, szPath))
  1880. {
  1881. lstrcpy(szPath, szNew); // use this in case of failure
  1882. hr = _SetPIDLPath(NULL, szPath, TRUE);
  1883. }
  1884. }
  1885. }
  1886. if (FAILED_AND_NOT_CANCELED(hr) && !(dwResolveFlags & SLR_NO_UI) &&
  1887. PathRetryRemovable(hr, szPath))
  1888. {
  1889. // do prompt for removable media if approprate
  1890. hr = _ResolveRemovable(hwnd, szPath);
  1891. fifFlags &= ~FIF_NODRIVE; // now it is back
  1892. }
  1893. if (FAILED_AND_NOT_CANCELED(hr))
  1894. {
  1895. WIN32_FIND_DATA fd;
  1896. _GetFindData(&fd); // fd input to search
  1897. // standard places failed, now do the search/track stuff
  1898. CLinkResolver *prs = new CLinkResolver(_ptracker, &fd, dwResolveFlags, dwTracker, fifFlags);
  1899. if (prs)
  1900. {
  1901. int id = prs->Resolve(hwnd, szPath, _pszCurFile);
  1902. if (IDOK == id)
  1903. {
  1904. // get fully qualified result
  1905. prs->GetResult(szPath, ARRAYSIZE(szPath));
  1906. hr = _SetPIDLPath(NULL, szPath, TRUE);
  1907. ASSERT(SUCCEEDED(hr) ? _bDirty : TRUE) // must be dirty on success
  1908. }
  1909. else
  1910. {
  1911. ASSERT(!_bDirty); // should not be dirty now
  1912. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1913. }
  1914. prs->Release();
  1915. }
  1916. else
  1917. {
  1918. hr = E_OUTOFMEMORY;
  1919. }
  1920. }
  1921. }
  1922. else
  1923. {
  1924. // non file system target, validate it. this is another way to "resolve" name space
  1925. // objects. the other method is inside of _ResolveIDList() where we do the
  1926. // name -> pidl round trip via parse calls. that version is restricted to
  1927. // file system parts of the name space to avoid compat issues so we end up
  1928. // here for all other name spaces
  1929. ULONG dwAttrib = SFGAO_VALIDATE; // to check for existance
  1930. hr = SHGetNameAndFlags(_pidl, SHGDN_NORMAL, szPath, ARRAYSIZE(szPath), &dwAttrib);
  1931. if (FAILED(hr))
  1932. {
  1933. if (!(dwResolveFlags & SLR_NO_UI))
  1934. {
  1935. ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTFINDORIGINAL), NULL,
  1936. MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND, szPath);
  1937. hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
  1938. }
  1939. }
  1940. }
  1941. }
  1942. }
  1943. // if the link is dirty update it (if it was loaded from a file)
  1944. if (SUCCEEDED(hr) && _bDirty && (dwResolveFlags & SLR_UPDATE))
  1945. Save((LPCOLESTR)NULL, TRUE);
  1946. ASSERT(SUCCEEDED(hr) ? S_OK == hr : TRUE); // make sure no S_FALSE values get through
  1947. return hr;
  1948. }
  1949. // This will just add a section to the end of the extra data -- it does
  1950. // not check to see if the section already exists, etc.
  1951. void CShellLink::_AddExtraDataSection(DATABLOCK_HEADER *peh)
  1952. {
  1953. if (SHAddDataBlock(&_pExtraData, peh))
  1954. {
  1955. _bDirty = TRUE;
  1956. }
  1957. }
  1958. // This will remove the extra data section with the given signature.
  1959. void CShellLink::_RemoveExtraDataSection(DWORD dwSig)
  1960. {
  1961. if (SHRemoveDataBlock(&_pExtraData, dwSig))
  1962. {
  1963. _bDirty = TRUE;
  1964. }
  1965. }
  1966. // currently this function is used for NT shell32 builds only
  1967. void * CShellLink::_ReadExtraDataSection(DWORD dwSig)
  1968. {
  1969. DATABLOCK_HEADER *pdb;
  1970. CopyDataBlock(dwSig, (void **)&pdb);
  1971. return (void *)pdb;
  1972. }
  1973. // Darwin and Logo3 blessings share the same structure
  1974. HRESULT CShellLink::BlessLink(LPCTSTR *ppszPath, DWORD dwSignature)
  1975. {
  1976. EXP_DARWIN_LINK expLink;
  1977. TCHAR szBlessID[MAX_PATH];
  1978. int cchBlessData;
  1979. TCHAR *pch;
  1980. // Copy the blessing data and advance *ppszPath to the end of the data.
  1981. for (pch = szBlessID, cchBlessData = 0; **ppszPath != ':' && **ppszPath != '\0' && cchBlessData < MAX_PATH; pch++, (*ppszPath)++, cchBlessData++)
  1982. {
  1983. *pch = **ppszPath;
  1984. }
  1985. // Terminate the blessing data
  1986. *pch = 0;
  1987. // Set the magic flag
  1988. if (dwSignature == EXP_DARWIN_ID_SIG)
  1989. {
  1990. _sld.dwFlags |= SLDF_HAS_DARWINID;
  1991. }
  1992. else if (dwSignature == EXP_LOGO3_ID_SIG)
  1993. {
  1994. _sld.dwFlags |= SLDF_HAS_LOGO3ID;
  1995. }
  1996. else
  1997. {
  1998. TraceMsg(TF_WARNING, "BlessLink was passed a bad data block signature.");
  1999. return E_INVALIDARG;
  2000. }
  2001. // locate the old block, if it's there
  2002. LPEXP_DARWIN_LINK lpNew = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, dwSignature);
  2003. // if not, use our stack var
  2004. if (!lpNew)
  2005. {
  2006. lpNew = &expLink;
  2007. expLink.dbh.cbSize = 0;
  2008. expLink.dbh.dwSignature = dwSignature;
  2009. }
  2010. SHTCharToAnsi(szBlessID, lpNew->szDarwinID, ARRAYSIZE(lpNew->szDarwinID));
  2011. SHTCharToUnicode(szBlessID, lpNew->szwDarwinID, ARRAYSIZE(lpNew->szwDarwinID));
  2012. // See if this is a new entry that we need to add
  2013. if (lpNew->dbh.cbSize == 0)
  2014. {
  2015. lpNew->dbh.cbSize = sizeof(*lpNew);
  2016. _AddExtraDataSection((DATABLOCK_HEADER *)lpNew);
  2017. }
  2018. return S_OK;
  2019. }
  2020. // in/out:
  2021. // ppszPathIn
  2022. HRESULT CShellLink::_CheckForLinkBlessing(LPCTSTR *ppszPathIn)
  2023. {
  2024. HRESULT hr = S_FALSE; // default to no-error, no blessing
  2025. while (SUCCEEDED(hr) && (*ppszPathIn)[0] == ':' && (*ppszPathIn)[1] == ':')
  2026. {
  2027. // identify type of link blessing and perform
  2028. if (StrCmpNI(*ppszPathIn, DARWINGUID_TAG, ARRAYSIZE(DARWINGUID_TAG) - 1) == 0)
  2029. {
  2030. *ppszPathIn = *ppszPathIn + ARRAYSIZE(DARWINGUID_TAG) - 1;
  2031. hr = BlessLink(ppszPathIn, EXP_DARWIN_ID_SIG);
  2032. }
  2033. else if (StrCmpNI(*ppszPathIn, LOGO3GUID_TAG, ARRAYSIZE(LOGO3GUID_TAG) - 1) == 0)
  2034. {
  2035. *ppszPathIn = *ppszPathIn + ARRAYSIZE(LOGO3GUID_TAG) - 1;
  2036. HRESULT hrBless = BlessLink(ppszPathIn, EXP_LOGO3_ID_SIG);
  2037. // if the blessing failed, report the error, otherwise keep the
  2038. // default hr == S_FALSE or the result of the Darwin blessing.
  2039. if (FAILED(hrBless))
  2040. hr = hrBless;
  2041. }
  2042. else
  2043. {
  2044. break;
  2045. }
  2046. }
  2047. return hr;
  2048. }
  2049. // TODO: Remove OLD_DARWIN stuff once we have transitioned Darwin to
  2050. // the new link blessing syntax.
  2051. #define OLD_DARWIN
  2052. int CShellLink::_IsOldDarwin(LPCTSTR pszPath)
  2053. {
  2054. #ifdef OLD_DARWIN
  2055. int iLength = lstrlen(pszPath);
  2056. if ((pszPath[0] == TEXT('[')) && (pszPath[iLength - 1] == TEXT(']')))
  2057. {
  2058. return iLength;
  2059. }
  2060. #endif
  2061. return 0;
  2062. }
  2063. // we have a path that is enclosed in []'s,
  2064. // so this must be a Darwin link.
  2065. HRESULT CShellLink::_SetPathOldDarwin(LPCTSTR pszPath)
  2066. {
  2067. TCHAR szDarwinID[MAX_PATH];
  2068. // strip off the []'s
  2069. lstrcpy(szDarwinID, &pszPath[1]);
  2070. szDarwinID[lstrlen(pszPath) - 1] = 0;
  2071. _sld.dwFlags |= SLDF_HAS_DARWINID;
  2072. EXP_DARWIN_LINK expLink;
  2073. LPEXP_DARWIN_LINK pedl = (LPEXP_DARWIN_LINK)SHFindDataBlock(_pExtraData, EXP_DARWIN_ID_SIG);
  2074. if (!pedl)
  2075. {
  2076. pedl = &expLink;
  2077. expLink.dbh.cbSize = 0;
  2078. expLink.dbh.dwSignature = EXP_DARWIN_ID_SIG;
  2079. }
  2080. SHTCharToAnsi(szDarwinID, pedl->szDarwinID, ARRAYSIZE(pedl->szDarwinID));
  2081. SHTCharToUnicode(szDarwinID, pedl->szwDarwinID, ARRAYSIZE(pedl->szwDarwinID));
  2082. // See if this is a new entry that we need to add
  2083. if (pedl->dbh.cbSize == 0)
  2084. {
  2085. pedl->dbh.cbSize = sizeof(*pedl);
  2086. _AddExtraDataSection((DATABLOCK_HEADER *)pedl);
  2087. }
  2088. // For darwin links, we ignore the path and pidl for now. We would
  2089. // normally call _SetPIDLPath and SetIDList but we skip these
  2090. // steps for darwin links because all _SetPIDLPath does is set the pidl
  2091. // and all SetIDList does is set fd (the WIN32_FIND_DATA)
  2092. // for the target, and we dont have a target since we are a darwin link.
  2093. return S_OK;
  2094. }
  2095. // IShellLink::SetPath()
  2096. STDMETHODIMP CShellLink::SetPath(LPCWSTR pszPathW)
  2097. {
  2098. HRESULT hr;
  2099. TCHAR szPath[MAX_PATH];
  2100. LPCTSTR pszPath;
  2101. // NOTE: all the other Set* functions allow NULL pointer to be passed in, but this
  2102. // one does not because it would AV.
  2103. if (!pszPathW)
  2104. {
  2105. return E_INVALIDARG;
  2106. }
  2107. else if (_sld.dwFlags & SLDF_HAS_DARWINID)
  2108. {
  2109. return S_FALSE; // a darwin link already, then we dont allow the path to change
  2110. }
  2111. SHUnicodeToTChar(pszPathW, szPath, ARRAYSIZE(szPath));
  2112. pszPath = szPath;
  2113. int iLength = _IsOldDarwin(pszPath);
  2114. if (iLength)
  2115. {
  2116. hr = _SetPathOldDarwin(pszPath);
  2117. }
  2118. else
  2119. {
  2120. // Check for ::<guid>:<data>: prefix, which signals us to bless the
  2121. // the lnk with extra data. NOTE: we pass the &pszPath here so that this fn can
  2122. // advance the string pointer past the ::<guid>:<data>: sections and point to
  2123. // the path, if there is one.
  2124. hr = _CheckForLinkBlessing(&pszPath);
  2125. if (S_OK != hr)
  2126. {
  2127. // Check to see if the target has any expandable environment strings
  2128. // in it. If so, set the appropriate information in the CShellLink
  2129. // data.
  2130. TCHAR szExpPath[MAX_PATH];
  2131. SHExpandEnvironmentStrings(pszPath, szExpPath, ARRAYSIZE(szExpPath));
  2132. if (lstrcmp(szExpPath, pszPath))
  2133. {
  2134. _sld.dwFlags |= SLDF_HAS_EXP_SZ; // link has expandable strings
  2135. EXP_SZ_LINK expLink;
  2136. LPEXP_SZ_LINK pel = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_LINK_SIG);
  2137. if (!pel)
  2138. {
  2139. pel = &expLink;
  2140. expLink.cbSize = 0;
  2141. expLink.dwSignature = EXP_SZ_LINK_SIG;
  2142. }
  2143. // store both A and W version (for no good reason!)
  2144. SHTCharToAnsi(pszPath, pel->szTarget, ARRAYSIZE(pel->szTarget));
  2145. SHTCharToUnicode(pszPath, pel->swzTarget, ARRAYSIZE(pel->swzTarget));
  2146. // See if this is a new entry that we need to add
  2147. if (pel->cbSize == 0)
  2148. {
  2149. pel->cbSize = sizeof(*pel);
  2150. _AddExtraDataSection((DATABLOCK_HEADER *)pel);
  2151. }
  2152. hr = _SetPIDLPath(NULL, szExpPath, TRUE);
  2153. }
  2154. else
  2155. {
  2156. _sld.dwFlags &= ~SLDF_HAS_EXP_SZ;
  2157. _RemoveExtraDataSection(EXP_SZ_LINK_SIG);
  2158. hr = _SetPIDLPath(NULL, pszPath, TRUE);
  2159. }
  2160. if (FAILED(hr))
  2161. {
  2162. PathResolve(szExpPath, NULL, PRF_TRYPROGRAMEXTENSIONS);
  2163. hr = _SetSimplePIDL(szExpPath);
  2164. }
  2165. }
  2166. }
  2167. return hr;
  2168. }
  2169. STDMETHODIMP CShellLink::GetClassID(CLSID *pClassID)
  2170. {
  2171. *pClassID = CLSID_ShellLink;
  2172. return S_OK;
  2173. }
  2174. STDMETHODIMP CShellLink::IsDirty()
  2175. {
  2176. return _bDirty ? S_OK : S_FALSE;
  2177. }
  2178. HRESULT LinkInfo_LoadFromStream(IStream *pstm, PLINKINFO *ppli, DWORD cbMax)
  2179. {
  2180. DWORD dwSize;
  2181. ULONG cbBytesRead;
  2182. if (*ppli)
  2183. {
  2184. LocalFree((HLOCAL)*ppli);
  2185. *ppli = NULL;
  2186. }
  2187. HRESULT hr = pstm->Read(&dwSize, sizeof(dwSize), &cbBytesRead); // size of data
  2188. if (SUCCEEDED(hr) && (cbBytesRead == sizeof(dwSize)))
  2189. {
  2190. if (dwSize <= cbMax)
  2191. {
  2192. if (dwSize >= sizeof(dwSize)) // must be at least this big
  2193. {
  2194. /* Yes. Read remainder of LinkInfo into local memory. */
  2195. PLINKINFO pli = (PLINKINFO)LocalAlloc(LPTR, dwSize);
  2196. if (pli)
  2197. {
  2198. *(DWORD *)pli = dwSize; // Copy size
  2199. dwSize -= sizeof(dwSize); // Read remainder of LinkInfo
  2200. hr = pstm->Read(((DWORD *)pli) + 1, dwSize, &cbBytesRead);
  2201. // Note that if the linkinfo is invalid, we still return S_OK
  2202. // because linkinfo is not essential to the shortcut
  2203. if (SUCCEEDED(hr) && (cbBytesRead == dwSize) && IsValidLinkInfo(pli))
  2204. *ppli = pli; // LinkInfo read successfully
  2205. else
  2206. LocalFree((HLOCAL)pli);
  2207. }
  2208. }
  2209. }
  2210. else
  2211. {
  2212. // This will happen if the .lnk is corrupted and the size in the stream
  2213. // is larger than the physical file on disk.
  2214. hr = E_FAIL;
  2215. }
  2216. }
  2217. return hr;
  2218. }
  2219. // Decodes the CSIDL_ relative target pidl
  2220. void CShellLink::_DecodeSpecialFolder()
  2221. {
  2222. LPEXP_SPECIAL_FOLDER pData = (LPEXP_SPECIAL_FOLDER)SHFindDataBlock(_pExtraData, EXP_SPECIAL_FOLDER_SIG);
  2223. if (pData)
  2224. {
  2225. LPITEMIDLIST pidlFolder = SHCloneSpecialIDList(NULL, pData->idSpecialFolder, FALSE);
  2226. if (pidlFolder)
  2227. {
  2228. ASSERT(IS_VALID_PIDL(_pidl));
  2229. LPITEMIDLIST pidlTarget = _ILSkip(_pidl, pData->cbOffset);
  2230. LPITEMIDLIST pidlSanityCheck = _pidl;
  2231. while (!ILIsEmpty(pidlSanityCheck) && (pidlSanityCheck < pidlTarget))
  2232. {
  2233. // We go one step at a time until pidlSanityCheck == pidlTarget. If we reach the end
  2234. // of pidlSanityCheck, or if we go past pidlTarget, before this condition is met then
  2235. // we have an invalid pData->cbOffset.
  2236. pidlSanityCheck = _ILNext(pidlSanityCheck);
  2237. }
  2238. if (pidlSanityCheck == pidlTarget)
  2239. {
  2240. LPITEMIDLIST pidlNew = ILCombine(pidlFolder, pidlTarget);
  2241. if (pidlNew)
  2242. {
  2243. _SetPIDLPath(pidlNew, NULL, FALSE);
  2244. ILFree(pidlNew);
  2245. }
  2246. }
  2247. ILFree(pidlFolder);
  2248. }
  2249. // in case above stuff fails for some reason
  2250. _RemoveExtraDataSection(EXP_SPECIAL_FOLDER_SIG);
  2251. }
  2252. }
  2253. HRESULT CShellLink::_UpdateIconFromExpIconSz()
  2254. {
  2255. HRESULT hr = S_FALSE;
  2256. // only try once per link instance
  2257. if (!_bExpandedIcon)
  2258. {
  2259. TCHAR szExpIconPath[MAX_PATH];
  2260. if (_sld.dwFlags & SLDF_HAS_EXP_ICON_SZ)
  2261. {
  2262. LPEXP_SZ_LINK pszl = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_ICON_SIG);
  2263. if (pszl)
  2264. {
  2265. if (SHExpandEnvironmentStringsW(pszl->swzTarget, szExpIconPath, ARRAYSIZE(szExpIconPath)) &&
  2266. PathFileExists(szExpIconPath))
  2267. {
  2268. hr = S_OK;
  2269. }
  2270. }
  2271. else
  2272. {
  2273. ASSERTMSG(FALSE, "CShellLink::_UpdateIconAtLoad - lnk has SLDF_HAS_EXP_ICON_SZ but no actual datablock!!");
  2274. hr = E_FAIL;
  2275. }
  2276. }
  2277. if (hr == S_OK)
  2278. {
  2279. // update _pszIconLocation if its different from the expanded string
  2280. if (lstrcmpi(_pszIconLocation, szExpIconPath) != 0)
  2281. {
  2282. _SetField(&_pszIconLocation, szExpIconPath);
  2283. _bDirty = TRUE;
  2284. }
  2285. }
  2286. _bExpandedIcon = TRUE;
  2287. }
  2288. return hr;
  2289. }
  2290. STDMETHODIMP CShellLink::Load(IStream *pstm)
  2291. {
  2292. ULONG cbBytes;
  2293. DWORD cbSize;
  2294. TraceMsg(TF_DEBUGLINKCODE, "Loading link from stream.");
  2295. _ResetPersistData(); // clear out our state
  2296. HRESULT hr = pstm->Read(&cbSize, sizeof(cbSize), &cbBytes);
  2297. if (SUCCEEDED(hr))
  2298. {
  2299. if (cbBytes == sizeof(cbSize))
  2300. {
  2301. if (cbSize == sizeof(_sld))
  2302. {
  2303. hr = pstm->Read((LPBYTE)&_sld + sizeof(cbSize), sizeof(_sld) - sizeof(cbSize), &cbBytes);
  2304. if (SUCCEEDED(hr) && cbBytes == (sizeof(_sld) - sizeof(cbSize)) && IsEqualGUID(_sld.clsid, CLSID_ShellLink))
  2305. {
  2306. _sld.cbSize = sizeof(_sld);
  2307. switch (_sld.iShowCmd)
  2308. {
  2309. case SW_SHOWNORMAL:
  2310. case SW_SHOWMINNOACTIVE:
  2311. case SW_SHOWMAXIMIZED:
  2312. break;
  2313. default:
  2314. DebugMsg(DM_TRACE, TEXT("Shortcut Load, mapping bogus ShowCmd: %d"), _sld.iShowCmd);
  2315. _sld.iShowCmd = SW_SHOWNORMAL;
  2316. break;
  2317. }
  2318. // save so we can generate notify on save
  2319. _wOldHotkey = _sld.wHotkey;
  2320. // read all of the members
  2321. if (_sld.dwFlags & SLDF_HAS_ID_LIST)
  2322. {
  2323. hr = ILLoadFromStream(pstm, &_pidl);
  2324. if (SUCCEEDED(hr))
  2325. {
  2326. // Check for a valid pidl. File corruption can cause pidls to become bad which will cause
  2327. // explorer to AV unless we catch it here. Also, people have been known to write invalid
  2328. // pidls into link files from time to time.
  2329. if (!SHIsValidPidl(_pidl))
  2330. {
  2331. // In theory this will only happen due to file corruption, but I've seen this too
  2332. // often not to suspect that we might be doing something wrong.
  2333. // turn off the flag, which we know is on to start with
  2334. _sld.dwFlags &= ~SLDF_HAS_ID_LIST;
  2335. Pidl_Set(&_pidl, NULL);
  2336. _bDirty = TRUE;
  2337. // continue as though there was no SLDF_HAS_ID_LIST flag to start with
  2338. // REVIEW: should we only continue if certain other sections are also included
  2339. // in the link? What will happen if SLDF_HAS_ID_LIST was the only data set for
  2340. // this link file? We would get a null link.
  2341. }
  2342. }
  2343. }
  2344. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_LINK_INFO))
  2345. {
  2346. DWORD cbMaxRead;
  2347. // We need to worry about link files that are corrupt. So read the link
  2348. // size so we don't keep reading for ever in case the stream has an invalid
  2349. // size in it.
  2350. // We need to check if it is a valid pidl because hackers will
  2351. // try to create invalid pidls to crash the system or run buffer
  2352. // over run attacks. -BryanSt
  2353. STATSTG stat;
  2354. if (SUCCEEDED(pstm->Stat(&stat, STATFLAG_NONAME)))
  2355. cbMaxRead = stat.cbSize.LowPart;
  2356. else
  2357. cbMaxRead = 0xFFFFFFFF;
  2358. hr = LinkInfo_LoadFromStream(pstm, &_pli, cbMaxRead);
  2359. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_FORCE_NO_LINKINFO))
  2360. {
  2361. _FreeLinkInfo(); // labotimizing link
  2362. }
  2363. }
  2364. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_NAME))
  2365. {
  2366. TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Name...");
  2367. hr = Str_SetFromStream(pstm, &_pszName, _sld.dwFlags & SLDF_UNICODE);
  2368. }
  2369. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_RELPATH))
  2370. {
  2371. hr = Str_SetFromStream(pstm, &_pszRelPath, _sld.dwFlags & SLDF_UNICODE);
  2372. if (!_pidl && SUCCEEDED(hr))
  2373. {
  2374. TCHAR szTmp[MAX_PATH];
  2375. if (_GetRelativePath(szTmp))
  2376. _SetPIDLPath(NULL, szTmp, TRUE);
  2377. }
  2378. }
  2379. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_WORKINGDIR))
  2380. {
  2381. TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Working Dir...");
  2382. hr = Str_SetFromStream(pstm, &_pszWorkingDir, _sld.dwFlags & SLDF_UNICODE);
  2383. }
  2384. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ARGS))
  2385. {
  2386. TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Arguments...");
  2387. hr = Str_SetFromStream(pstm, &_pszArgs, _sld.dwFlags & SLDF_UNICODE);
  2388. }
  2389. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ICONLOCATION))
  2390. {
  2391. TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Icon Location...");
  2392. hr = Str_SetFromStream(pstm, &_pszIconLocation, _sld.dwFlags & SLDF_UNICODE);
  2393. }
  2394. if (SUCCEEDED(hr))
  2395. {
  2396. TraceMsg(TF_DEBUGLINKCODE, " CShellLink: Loading Data Block...");
  2397. hr = SHReadDataBlockList(pstm, &_pExtraData);
  2398. }
  2399. // reset the darwin info on load
  2400. if (_sld.dwFlags & SLDF_HAS_DARWINID)
  2401. {
  2402. // since darwin links rely so heavily on the icon, do this now
  2403. _UpdateIconFromExpIconSz();
  2404. // we should never have a darwin link that is missing
  2405. // the icon path
  2406. if (_pszIconLocation)
  2407. {
  2408. // we always put back the icon path as the pidl at
  2409. // load time since darwin could change the path or
  2410. // to the app (eg: new version of the app)
  2411. TCHAR szPath[MAX_PATH];
  2412. // expand any env. strings in the icon path before
  2413. // creating the pidl.
  2414. SHExpandEnvironmentStrings(_pszIconLocation, szPath, ARRAYSIZE(szPath));
  2415. _SetPIDLPath(NULL, szPath, FALSE);
  2416. }
  2417. }
  2418. else
  2419. {
  2420. // The Darwin stuff above creates a new pidl, which
  2421. // would cause this stuff to blow up. We should never
  2422. // get both at once, but let's be extra robust...
  2423. //
  2424. // Since we store the offset into the pidl here, and
  2425. // the pidl can change for various reasons, we can
  2426. // only do this once at load time. Do it here.
  2427. //
  2428. if (_pidl)
  2429. {
  2430. _DecodeSpecialFolder();
  2431. }
  2432. }
  2433. if (SUCCEEDED(hr) && _ptracker)
  2434. {
  2435. // load the tracker from extra data
  2436. EXP_TRACKER *pData = (LPEXP_TRACKER)SHFindDataBlock(_pExtraData, EXP_TRACKER_SIG);
  2437. if (pData)
  2438. {
  2439. hr = _ptracker->Load(pData->abTracker, pData->cbSize - sizeof(EXP_TRACKER));
  2440. if (FAILED(hr))
  2441. {
  2442. // Failure of the Tracker isn't just cause to make
  2443. // the shortcut unusable. So just re-init it and move on.
  2444. _ptracker->InitNew();
  2445. hr = S_OK;
  2446. }
  2447. }
  2448. }
  2449. if (SUCCEEDED(hr))
  2450. _bDirty = FALSE;
  2451. }
  2452. else
  2453. {
  2454. DebugMsg(DM_TRACE, TEXT("failed to read link struct"));
  2455. hr = E_FAIL; // invalid file size
  2456. }
  2457. }
  2458. else
  2459. {
  2460. DebugMsg(DM_TRACE, TEXT("invalid length field in link:%d"), cbBytes);
  2461. hr = E_FAIL; // invalid file size
  2462. }
  2463. }
  2464. else if (cbBytes == 0)
  2465. {
  2466. _sld.cbSize = 0; // zero length file is ok
  2467. }
  2468. else
  2469. {
  2470. hr = E_FAIL; // invalid file size
  2471. }
  2472. }
  2473. return hr;
  2474. }
  2475. // set the relative path
  2476. // in:
  2477. // pszRelSource fully qualified path to a file (must be file, not directory)
  2478. // to be used to find a relative path with the link target.
  2479. //
  2480. // returns:
  2481. // S_OK relative path is set
  2482. // S_FALSE pszPathRel is not relative to the destination or the
  2483. // destionation is not a file (could be link to a pidl only)
  2484. // notes:
  2485. // set the dirty bit if this is a new relative path
  2486. //
  2487. HRESULT CShellLink::_SetRelativePath(LPCTSTR pszRelSource)
  2488. {
  2489. TCHAR szPath[MAX_PATH], szDest[MAX_PATH];
  2490. ASSERT(!PathIsRelative(pszRelSource));
  2491. if (_pidl == NULL || !SHGetPathFromIDList(_pidl, szDest))
  2492. {
  2493. DebugMsg(DM_TRACE, TEXT("SetRelative called on non path link"));
  2494. return S_FALSE;
  2495. }
  2496. // assume pszRelSource is a file, not a directory
  2497. if (PathRelativePathTo(szPath, pszRelSource, 0, szDest, _sld.dwFileAttributes))
  2498. {
  2499. pszRelSource = szPath;
  2500. }
  2501. else
  2502. {
  2503. DebugMsg(DM_TRACE, TEXT("paths are not relative"));
  2504. pszRelSource = NULL; // clear the stored relative path below
  2505. }
  2506. _SetField(&_pszRelPath, pszRelSource);
  2507. return S_OK;
  2508. }
  2509. BOOL CShellLink::_EncodeSpecialFolder()
  2510. {
  2511. BOOL bRet = FALSE;
  2512. if (_pidl)
  2513. {
  2514. // make sure we don't already have a EXP_SPECIAL_FOLDER_SIG data block, otherwise we would
  2515. // end up with two of these and the first one would win on read.
  2516. // If you hit this ASSERT in a debugger, contact ToddB with a remote. We need to figure out
  2517. // why we are corrupting our shortcuts.
  2518. ASSERT(NULL == SHFindDataBlock(_pExtraData, EXP_SPECIAL_FOLDER_SIG));
  2519. EXP_SPECIAL_FOLDER exp;
  2520. exp.idSpecialFolder = GetSpecialFolderParentIDAndOffset(_pidl, &exp.cbOffset);
  2521. if (exp.idSpecialFolder)
  2522. {
  2523. exp.cbSize = sizeof(exp);
  2524. exp.dwSignature = EXP_SPECIAL_FOLDER_SIG;
  2525. _AddExtraDataSection((DATABLOCK_HEADER *)&exp);
  2526. bRet = TRUE;
  2527. }
  2528. }
  2529. return bRet;
  2530. }
  2531. HRESULT LinkInfo_SaveToStream(IStream *pstm, PCLINKINFO pcli)
  2532. {
  2533. ULONG cbBytes;
  2534. DWORD dwSize = *(DWORD *)pcli; // Get LinkInfo size
  2535. HRESULT hr = pstm->Write(pcli, dwSize, &cbBytes);
  2536. if (SUCCEEDED(hr) && (cbBytes != dwSize))
  2537. hr = E_FAIL;
  2538. return hr;
  2539. }
  2540. //
  2541. // Replaces the tracker extra data with current tracker state
  2542. //
  2543. HRESULT CShellLink::_UpdateTracker()
  2544. {
  2545. ULONG ulSize = _ptracker->GetSize();
  2546. if (!_ptracker->IsLoaded())
  2547. {
  2548. _RemoveExtraDataSection(EXP_TRACKER_SIG);
  2549. return S_OK;
  2550. }
  2551. if (!_ptracker->IsDirty())
  2552. {
  2553. return S_OK;
  2554. }
  2555. HRESULT hr = E_FAIL;
  2556. // Make sure the Tracker size is a multiple of DWORDs.
  2557. // If we hit this assert then we would have mis-aligned stuff stored in the extra data.
  2558. //
  2559. if (EVAL(0 == (ulSize & 3)))
  2560. {
  2561. EXP_TRACKER *pExpTracker = (EXP_TRACKER *)LocalAlloc(LPTR, ulSize + sizeof(DATABLOCK_HEADER));
  2562. if (pExpTracker)
  2563. {
  2564. _RemoveExtraDataSection(EXP_TRACKER_SIG);
  2565. pExpTracker->cbSize = ulSize + sizeof(DATABLOCK_HEADER);
  2566. pExpTracker->dwSignature = EXP_TRACKER_SIG;
  2567. _ptracker->Save(pExpTracker->abTracker, ulSize);
  2568. _AddExtraDataSection((DATABLOCK_HEADER *)&pExpTracker->cbSize);
  2569. DebugMsg(DM_TRACE, TEXT("_UpdateTracker: EXP_TRACKER at %08X."), &pExpTracker->cbSize);
  2570. LocalFree(pExpTracker);
  2571. hr = S_OK;
  2572. }
  2573. }
  2574. return hr;
  2575. }
  2576. STDMETHODIMP CShellLink::Save(IStream *pstm, BOOL fClearDirty)
  2577. {
  2578. ULONG cbBytes;
  2579. BOOL fEncode;
  2580. _sld.cbSize = sizeof(_sld);
  2581. _sld.clsid = CLSID_ShellLink;
  2582. // _sld.dwFlags = 0;
  2583. // We do the following & instead of zeroing because the SLDF_HAS_EXP_SZ and
  2584. // SLDF_RUN_IN_SEPARATE and SLDF_RUNAS_USER and SLDF_HAS_DARWINID are passed to us and are valid,
  2585. // the others can be reconstructed below, but these three can not, so we need to
  2586. // preserve them!
  2587. _sld.dwFlags &= (SLDF_HAS_EXP_SZ |
  2588. SLDF_HAS_EXP_ICON_SZ |
  2589. SLDF_RUN_IN_SEPARATE |
  2590. SLDF_HAS_DARWINID |
  2591. SLDF_HAS_LOGO3ID |
  2592. SLDF_RUNAS_USER |
  2593. SLDF_RUN_WITH_SHIMLAYER);
  2594. if (_pszRelSource)
  2595. {
  2596. _SetRelativePath(_pszRelSource);
  2597. }
  2598. _sld.dwFlags |= SLDF_UNICODE;
  2599. fEncode = FALSE;
  2600. if (_pidl)
  2601. {
  2602. _sld.dwFlags |= SLDF_HAS_ID_LIST;
  2603. // we dont want to have special folder tracking for darwin links
  2604. if (!(_sld.dwFlags & SLDF_HAS_DARWINID))
  2605. fEncode = _EncodeSpecialFolder();
  2606. }
  2607. if (_pli)
  2608. _sld.dwFlags |= SLDF_HAS_LINK_INFO;
  2609. if (_pszName && _pszName[0])
  2610. _sld.dwFlags |= SLDF_HAS_NAME;
  2611. if (_pszRelPath && _pszRelPath[0])
  2612. _sld.dwFlags |= SLDF_HAS_RELPATH;
  2613. if (_pszWorkingDir && _pszWorkingDir[0])
  2614. _sld.dwFlags |= SLDF_HAS_WORKINGDIR;
  2615. if (_pszArgs && _pszArgs[0])
  2616. _sld.dwFlags |= SLDF_HAS_ARGS;
  2617. if (_pszIconLocation && _pszIconLocation[0])
  2618. _sld.dwFlags |= SLDF_HAS_ICONLOCATION;
  2619. HRESULT hr = pstm->Write(&_sld, sizeof(_sld), &cbBytes);
  2620. if (SUCCEEDED(hr) && (cbBytes == sizeof(_sld)))
  2621. {
  2622. if (_pidl)
  2623. hr = ILSaveToStream(pstm, _pidl);
  2624. if (SUCCEEDED(hr) && _pli)
  2625. hr = LinkInfo_SaveToStream(pstm, _pli);
  2626. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_NAME))
  2627. hr = Stream_WriteString(pstm, _pszName, _sld.dwFlags & SLDF_UNICODE);
  2628. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_RELPATH))
  2629. hr = Stream_WriteString(pstm, _pszRelPath, _sld.dwFlags & SLDF_UNICODE);
  2630. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_WORKINGDIR))
  2631. hr = Stream_WriteString(pstm, _pszWorkingDir, _sld.dwFlags & SLDF_UNICODE);
  2632. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ARGS))
  2633. hr = Stream_WriteString(pstm, _pszArgs, _sld.dwFlags & SLDF_UNICODE);
  2634. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_HAS_ICONLOCATION))
  2635. hr = Stream_WriteString(pstm, _pszIconLocation, _sld.dwFlags & SLDF_UNICODE);
  2636. if (SUCCEEDED(hr) && _ptracker && _ptracker->WasLoadedAtLeastOnce())
  2637. hr = _UpdateTracker();
  2638. if (SUCCEEDED(hr))
  2639. {
  2640. hr = SHWriteDataBlockList(pstm, _pExtraData);
  2641. }
  2642. if (SUCCEEDED(hr) && fClearDirty)
  2643. _bDirty = FALSE;
  2644. }
  2645. else
  2646. {
  2647. DebugMsg(DM_TRACE, TEXT("Failed to write link"));
  2648. hr = E_FAIL;
  2649. }
  2650. if (fEncode)
  2651. {
  2652. _RemoveExtraDataSection(EXP_SPECIAL_FOLDER_SIG);
  2653. }
  2654. return hr;
  2655. }
  2656. STDMETHODIMP CShellLink::GetSizeMax(ULARGE_INTEGER *pcbSize)
  2657. {
  2658. pcbSize->LowPart = 16 * 1024; // 16k? who knows...
  2659. pcbSize->HighPart = 0;
  2660. return S_OK;
  2661. }
  2662. BOOL PathIsPif(LPCTSTR pszPath)
  2663. {
  2664. return lstrcmpi(PathFindExtension(pszPath), TEXT(".pif")) == 0;
  2665. }
  2666. HRESULT CShellLink::_LoadFromPIF(LPCTSTR pszPath)
  2667. {
  2668. HANDLE hPif = PifMgr_OpenProperties(pszPath, NULL, 0, 0);
  2669. if (hPif == 0)
  2670. return E_FAIL;
  2671. PROPPRG ProgramProps = {0};
  2672. if (!PifMgr_GetProperties(hPif, (LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, sizeof(ProgramProps), 0))
  2673. {
  2674. return E_FAIL;
  2675. }
  2676. SetDescription(ProgramProps.achTitle);
  2677. SetWorkingDirectory(ProgramProps.achWorkDir);
  2678. SetArguments(PathGetArgsA(ProgramProps.achCmdLine));
  2679. SetHotkey(ProgramProps.wHotKey);
  2680. SetIconLocation(ProgramProps.achIconFile, ProgramProps.wIconIndex);
  2681. TCHAR szTemp[MAX_PATH];
  2682. SHAnsiToTChar(ProgramProps.achCmdLine, szTemp, ARRAYSIZE(szTemp));
  2683. PathRemoveArgs(szTemp);
  2684. // If this is a network path, we want to create a simple pidl
  2685. // instead of a full pidl to circumvent net hits
  2686. if (PathIsUNC(szTemp) || IsRemoteDrive(DRIVEID(szTemp)))
  2687. {
  2688. _SetSimplePIDL(szTemp);
  2689. }
  2690. else
  2691. {
  2692. _SetPIDLPath(NULL, szTemp, FALSE);
  2693. }
  2694. if (ProgramProps.flPrgInit & PRGINIT_MINIMIZED)
  2695. {
  2696. SetShowCmd(SW_SHOWMINNOACTIVE);
  2697. }
  2698. else if (ProgramProps.flPrgInit & PRGINIT_MAXIMIZED)
  2699. {
  2700. SetShowCmd(SW_SHOWMAXIMIZED);
  2701. }
  2702. else
  2703. {
  2704. SetShowCmd(SW_SHOWNORMAL);
  2705. }
  2706. PifMgr_CloseProperties(hPif, 0);
  2707. _bDirty = FALSE;
  2708. return S_OK;
  2709. }
  2710. HRESULT CShellLink::_LoadFromFile(LPCTSTR pszPath)
  2711. {
  2712. HRESULT hr;
  2713. if (PathIsPif(pszPath))
  2714. {
  2715. hr = _LoadFromPIF(pszPath);
  2716. }
  2717. else
  2718. {
  2719. IStream *pstm;
  2720. hr = SHCreateStreamOnFile(pszPath, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm);
  2721. if (SUCCEEDED(hr))
  2722. {
  2723. hr = Load(pstm);
  2724. pstm->Release();
  2725. }
  2726. }
  2727. if (SUCCEEDED(hr))
  2728. {
  2729. TCHAR szPath[MAX_PATH];
  2730. if (_pidl && SHGetPathFromIDList(_pidl, szPath) && !lstrcmpi(szPath, pszPath))
  2731. {
  2732. DebugMsg(DM_TRACE, TEXT("Link points to itself, aaahhh!"));
  2733. hr = E_FAIL;
  2734. }
  2735. else
  2736. {
  2737. Str_SetPtr(&_pszCurFile, pszPath);
  2738. }
  2739. }
  2740. else if (IsFolderShortcut(pszPath))
  2741. {
  2742. // this support here is a hack to make Office file open work. that code
  2743. // depends on loading folder shortcuts using CLSID_ShellLink. this is because
  2744. // we lie about the attributes of folder shortcuts to office to make other
  2745. // stuff work.
  2746. TCHAR szPath[MAX_PATH];
  2747. PathCombine(szPath, pszPath, TEXT("target.lnk"));
  2748. IStream *pstm;
  2749. hr = SHCreateStreamOnFile(szPath, STGM_READ | STGM_SHARE_DENY_WRITE, &pstm);
  2750. if (SUCCEEDED(hr))
  2751. {
  2752. hr = Load(pstm);
  2753. pstm->Release();
  2754. }
  2755. }
  2756. ASSERT(!_bDirty);
  2757. return hr;
  2758. }
  2759. STDMETHODIMP CShellLink::Load(LPCOLESTR pwszFile, DWORD grfMode)
  2760. {
  2761. HRESULT hr = E_INVALIDARG;
  2762. TraceMsg(TF_DEBUGLINKCODE, "Loading link from file %ls.", pwszFile);
  2763. if (pwszFile)
  2764. {
  2765. hr = _LoadFromFile(pwszFile);
  2766. // convert the succeeded code to S_OK so that THOSE DUMB apps like HitNrun
  2767. // who do hr == 0 don't fail miserably.
  2768. if (SUCCEEDED(hr))
  2769. hr = S_OK;
  2770. }
  2771. return hr;
  2772. }
  2773. HRESULT CShellLink::_SaveAsLink(LPCTSTR pszPath)
  2774. {
  2775. TraceMsg(TF_DEBUGLINKCODE, "Save link to file %s.", pszPath);
  2776. IStream *pstm;
  2777. HRESULT hr = SHCreateStreamOnFile(pszPath, STGM_CREATE | STGM_WRITE | STGM_SHARE_DENY_WRITE, &pstm);
  2778. if (SUCCEEDED(hr))
  2779. {
  2780. if (_pszRelSource == NULL)
  2781. _SetRelativePath(pszPath);
  2782. hr = Save(pstm, TRUE);
  2783. if (SUCCEEDED(hr))
  2784. {
  2785. hr = pstm->Commit(0);
  2786. }
  2787. pstm->Release();
  2788. if (FAILED(hr))
  2789. {
  2790. DeleteFile(pszPath);
  2791. }
  2792. }
  2793. return hr;
  2794. }
  2795. BOOL RenameChangeExtension(LPTSTR pszPathSave, LPCTSTR pszExt, BOOL fMove)
  2796. {
  2797. TCHAR szPathSrc[MAX_PATH];
  2798. lstrcpy(szPathSrc, pszPathSave);
  2799. PathRenameExtension(pszPathSave, pszExt);
  2800. // this may fail because the source file does not exist, but we dont care
  2801. if (fMove && lstrcmpi(szPathSrc, pszPathSave) != 0)
  2802. {
  2803. DWORD dwAttrib;
  2804. PathYetAnotherMakeUniqueName(pszPathSave, pszPathSave, NULL, NULL);
  2805. dwAttrib = GetFileAttributes(szPathSrc);
  2806. if ((dwAttrib == 0xFFFFFFFF) || (dwAttrib & FILE_ATTRIBUTE_READONLY))
  2807. {
  2808. // Source file is read only, don't want to change the extension
  2809. // because we won't be able to write any changes to the file...
  2810. return FALSE;
  2811. }
  2812. Win32MoveFile(szPathSrc, pszPathSave, FALSE);
  2813. }
  2814. return TRUE;
  2815. }
  2816. // out:
  2817. // pszDir MAX_PATH path to get directory, maybe with env expanded
  2818. //
  2819. // returns:
  2820. // TRUE has a working directory, pszDir filled in.
  2821. // FALSE no working dir, if the env expands to larger than the buffer size (MAX_PATH)
  2822. // this will be returned (FALSE)
  2823. //
  2824. BOOL CShellLink::_GetWorkingDir(LPTSTR pszDir)
  2825. {
  2826. *pszDir = 0;
  2827. if (_pszWorkingDir && _pszWorkingDir[0])
  2828. {
  2829. return (SHExpandEnvironmentStrings(_pszWorkingDir, pszDir, MAX_PATH) != 0);
  2830. }
  2831. return FALSE;
  2832. }
  2833. HRESULT CShellLink::_SaveAsPIF(LPCTSTR pszPath, BOOL fPath)
  2834. {
  2835. HANDLE hPif;
  2836. PROPPRG ProgramProps;
  2837. HRESULT hr;
  2838. TCHAR szDir[MAX_PATH];
  2839. TCHAR achPath[MAX_PATH];
  2840. //
  2841. // get filename and convert it to a short filename
  2842. //
  2843. if (fPath)
  2844. {
  2845. hr = GetPath(achPath, ARRAYSIZE(achPath), NULL, 0);
  2846. PathGetShortPath(achPath);
  2847. ASSERT(!PathIsPif(achPath));
  2848. ASSERT(LOWORD(GetExeType(achPath)) == 0x5A4D);
  2849. ASSERT(PathIsPif(pszPath));
  2850. ASSERT(hr == S_OK);
  2851. }
  2852. else
  2853. {
  2854. lstrcpy(achPath, pszPath);
  2855. }
  2856. DebugMsg(DM_TRACE, TEXT("_SaveAsPIF(%s,%s)"), achPath, pszPath);
  2857. #if 0
  2858. //
  2859. // we should use OPENPROPS_INHIBITPIF to prevent PIFMGR from making a
  2860. // temp .pif file in \windows\pif but it does not work now.
  2861. //
  2862. hPif = PifMgr_OpenProperties(achPath, pszPath, 0, OPENPROPS_INHIBITPIF);
  2863. #else
  2864. hPif = PifMgr_OpenProperties(achPath, pszPath, 0, 0);
  2865. #endif
  2866. if (hPif == 0)
  2867. {
  2868. return E_FAIL;
  2869. }
  2870. if (!PifMgr_GetProperties(hPif,(LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, sizeof(ProgramProps), 0))
  2871. {
  2872. DebugMsg(DM_TRACE, TEXT("_SaveToPIF: PifMgr_GetProperties *failed*"));
  2873. hr = E_FAIL;
  2874. goto Error1;
  2875. }
  2876. // Set a title based on the link name.
  2877. if (_pszName && _pszName[0])
  2878. {
  2879. SHTCharToAnsi(_pszName, ProgramProps.achTitle, sizeof(ProgramProps.achTitle));
  2880. }
  2881. // if no work dir. is given default to the dir of the app.
  2882. if (_GetWorkingDir(szDir))
  2883. {
  2884. TCHAR szTemp[PIFDEFPATHSIZE];
  2885. GetShortPathName(szDir, szTemp, ARRAYSIZE(szTemp));
  2886. SHTCharToAnsi(szTemp, ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir));
  2887. }
  2888. else if (fPath && !PathIsUNC(achPath))
  2889. {
  2890. TCHAR szTemp[PIFDEFPATHSIZE];
  2891. lstrcpyn(szTemp, achPath, ARRAYSIZE(szTemp));
  2892. PathRemoveFileSpec(szTemp);
  2893. SHTCharToAnsi(szTemp, ProgramProps.achWorkDir, ARRAYSIZE(ProgramProps.achWorkDir));
  2894. }
  2895. // And for those network share points we need to quote blanks...
  2896. PathQuoteSpaces(achPath);
  2897. // add the args to build the full command line
  2898. if (_pszArgs && _pszArgs[0])
  2899. {
  2900. lstrcat(achPath, c_szSpace);
  2901. lstrcat(achPath, _pszArgs);
  2902. }
  2903. if (fPath)
  2904. {
  2905. SHTCharToAnsi(achPath, ProgramProps.achCmdLine, ARRAYSIZE(ProgramProps.achCmdLine));
  2906. }
  2907. if (_sld.iShowCmd == SW_SHOWMAXIMIZED)
  2908. {
  2909. ProgramProps.flPrgInit |= PRGINIT_MAXIMIZED;
  2910. }
  2911. if ((_sld.iShowCmd == SW_SHOWMINIMIZED) || (_sld.iShowCmd == SW_SHOWMINNOACTIVE))
  2912. {
  2913. ProgramProps.flPrgInit |= PRGINIT_MINIMIZED;
  2914. }
  2915. if (_sld.wHotkey)
  2916. {
  2917. ProgramProps.wHotKey = _sld.wHotkey;
  2918. }
  2919. if (_pszIconLocation && _pszIconLocation[0])
  2920. {
  2921. SHTCharToAnsi(_pszIconLocation, ProgramProps.achIconFile, ARRAYSIZE(ProgramProps.achIconFile));
  2922. ProgramProps.wIconIndex = (WORD) _sld.iIcon;
  2923. }
  2924. if (!PifMgr_SetProperties(hPif, (LPSTR)MAKEINTATOM(GROUP_PRG), &ProgramProps, sizeof(ProgramProps), 0))
  2925. {
  2926. DebugMsg(DM_TRACE, TEXT("_SaveToPIF: PifMgr_SetProperties *failed*"));
  2927. hr = E_FAIL;
  2928. }
  2929. else
  2930. {
  2931. hr = S_OK;
  2932. }
  2933. _bDirty = FALSE;
  2934. Error1:
  2935. PifMgr_CloseProperties(hPif, 0);
  2936. return hr;
  2937. }
  2938. // This will allow global hotkeys to be available immediately instead
  2939. // of having to wait for the StartMenu to pick them up.
  2940. // Similarly this will remove global hotkeys immediately if req.
  2941. const UINT c_rgHotKeyFolders[] = {
  2942. CSIDL_PROGRAMS,
  2943. CSIDL_COMMON_PROGRAMS,
  2944. CSIDL_STARTMENU,
  2945. CSIDL_COMMON_STARTMENU,
  2946. CSIDL_DESKTOPDIRECTORY,
  2947. CSIDL_COMMON_DESKTOPDIRECTORY,
  2948. };
  2949. void HandleGlobalHotkey(LPCTSTR pszFile, WORD wHotkeyOld, WORD wHotkeyNew)
  2950. {
  2951. if (PathIsEqualOrSubFolderOf(pszFile, c_rgHotKeyFolders, ARRAYSIZE(c_rgHotKeyFolders)))
  2952. {
  2953. // Find tray?
  2954. HWND hwndTray = FindWindow(TEXT(WNDCLASS_TRAYNOTIFY), 0);
  2955. if (hwndTray)
  2956. {
  2957. // Yep.
  2958. if (wHotkeyOld)
  2959. SendMessage(hwndTray, WMTRAY_SCUNREGISTERHOTKEY, wHotkeyOld, 0);
  2960. if (wHotkeyNew)
  2961. {
  2962. ATOM atom = GlobalAddAtom(pszFile);
  2963. if (atom)
  2964. {
  2965. SendMessage(hwndTray, WMTRAY_SCREGISTERHOTKEY, wHotkeyNew, (LPARAM)atom);
  2966. GlobalDeleteAtom(atom);
  2967. }
  2968. }
  2969. }
  2970. }
  2971. }
  2972. HRESULT CShellLink::_SaveToFile(LPTSTR pszPathSave, BOOL fRemember)
  2973. {
  2974. HRESULT hr = E_FAIL;
  2975. BOOL fDosApp;
  2976. BOOL fFile;
  2977. TCHAR szPathSrc[MAX_PATH];
  2978. BOOL fWasSameFile = _pszCurFile && (lstrcmpi(pszPathSave, _pszCurFile) == 0);
  2979. BOOL bFileExisted = PathFileExistsAndAttributes(pszPathSave, NULL);
  2980. // when saving darwin links we dont want to resolve the path
  2981. if (_sld.dwFlags & SLDF_HAS_DARWINID)
  2982. {
  2983. fRemember = FALSE;
  2984. hr = _SaveAsLink(pszPathSave);
  2985. goto Update;
  2986. }
  2987. GetPath(szPathSrc, ARRAYSIZE(szPathSrc), NULL, 0);
  2988. fFile = !(_sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
  2989. fDosApp = fFile && LOWORD(GetExeType(szPathSrc)) == 0x5A4D;
  2990. // handle a link to link case. (or link to pif)
  2991. //
  2992. // NOTE: we loose all new attributes, including icon, but it's been this way since Win95.
  2993. if (fFile && (PathIsPif(szPathSrc) || PathIsLnk(szPathSrc)))
  2994. {
  2995. if (RenameChangeExtension(pszPathSave, PathFindExtension(szPathSrc), fWasSameFile))
  2996. {
  2997. if (CopyFile(szPathSrc, pszPathSave, FALSE))
  2998. {
  2999. if (PathIsPif(pszPathSave))
  3000. hr = _SaveAsPIF(pszPathSave, FALSE);
  3001. else
  3002. hr = S_OK;
  3003. }
  3004. }
  3005. else
  3006. {
  3007. hr = E_FAIL;
  3008. }
  3009. }
  3010. else if (fDosApp)
  3011. {
  3012. // if the linked to file is a DOS app, we need to write a .PIF file
  3013. if (RenameChangeExtension(pszPathSave, TEXT(".pif"), fWasSameFile))
  3014. {
  3015. hr = _SaveAsPIF(pszPathSave, TRUE);
  3016. }
  3017. else
  3018. {
  3019. hr = E_FAIL;
  3020. }
  3021. }
  3022. else
  3023. {
  3024. // else write a link file
  3025. if (PathIsPif(pszPathSave))
  3026. {
  3027. if (!RenameChangeExtension(pszPathSave, TEXT(".lnk"), fWasSameFile))
  3028. {
  3029. hr = E_FAIL;
  3030. goto Update;
  3031. }
  3032. }
  3033. hr = _SaveAsLink(pszPathSave);
  3034. }
  3035. Update:
  3036. if (SUCCEEDED(hr))
  3037. {
  3038. // Knock out file close
  3039. SHChangeNotify(bFileExisted ? SHCNE_UPDATEITEM : SHCNE_CREATE, SHCNF_PATH, pszPathSave, NULL);
  3040. SHChangeNotify(SHCNE_FREESPACE, SHCNF_PATH, pszPathSave, NULL);
  3041. if (_wOldHotkey != _sld.wHotkey)
  3042. {
  3043. HandleGlobalHotkey(pszPathSave, _wOldHotkey, _sld.wHotkey);
  3044. }
  3045. if (fRemember)
  3046. {
  3047. Str_SetPtr(&_pszCurFile, pszPathSave);
  3048. }
  3049. }
  3050. return hr;
  3051. }
  3052. STDMETHODIMP CShellLink::Save(LPCOLESTR pwszFile, BOOL fRemember)
  3053. {
  3054. TCHAR szSavePath[MAX_PATH];
  3055. if (pwszFile == NULL)
  3056. {
  3057. if (_pszCurFile == NULL)
  3058. {
  3059. // fail
  3060. return E_FAIL;
  3061. }
  3062. lstrcpy(szSavePath, _pszCurFile);
  3063. }
  3064. else
  3065. {
  3066. SHUnicodeToTChar(pwszFile, szSavePath, ARRAYSIZE(szSavePath));
  3067. }
  3068. return _SaveToFile(szSavePath, fRemember);
  3069. }
  3070. STDMETHODIMP CShellLink::SaveCompleted(LPCOLESTR pwszFile)
  3071. {
  3072. return S_OK;
  3073. }
  3074. STDMETHODIMP CShellLink::GetCurFile(LPOLESTR *ppszFile)
  3075. {
  3076. if (_pszCurFile == NULL)
  3077. {
  3078. *ppszFile = NULL;
  3079. return S_FALSE;
  3080. }
  3081. return SHStrDup(_pszCurFile, ppszFile);
  3082. }
  3083. STDMETHODIMP CShellLink::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
  3084. {
  3085. HRESULT hr;
  3086. ASSERT(_sld.iShowCmd == SW_SHOWNORMAL);
  3087. if (pdtobj)
  3088. {
  3089. STGMEDIUM medium = {0};
  3090. FORMATETC fmte = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  3091. hr = pdtobj->GetData(&fmte, &medium);
  3092. if (SUCCEEDED(hr))
  3093. {
  3094. TCHAR szPath[MAX_PATH];
  3095. DragQueryFile((HDROP)medium.hGlobal, 0, szPath, ARRAYSIZE(szPath));
  3096. hr = _LoadFromFile(szPath);
  3097. ReleaseStgMedium(&medium);
  3098. }
  3099. else
  3100. {
  3101. LPIDA pida = DataObj_GetHIDA(pdtobj, &medium);
  3102. if (pida)
  3103. {
  3104. IShellFolder *psf;
  3105. hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, IDA_GetIDListPtr(pida, -1), &psf));
  3106. if (SUCCEEDED(hr))
  3107. {
  3108. IStream *pstm;
  3109. hr = psf->BindToStorage(IDA_GetIDListPtr(pida, 0), NULL, IID_PPV_ARG(IStream, &pstm));
  3110. if (SUCCEEDED(hr))
  3111. {
  3112. hr = Load(pstm);
  3113. pstm->Release();
  3114. }
  3115. psf->Release();
  3116. }
  3117. HIDA_ReleaseStgMedium(pida, &medium);
  3118. }
  3119. }
  3120. }
  3121. else
  3122. {
  3123. hr = E_FAIL;
  3124. }
  3125. return hr;
  3126. }
  3127. STDAPI CDarwinContextMenuCB::CallBack(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
  3128. {
  3129. HRESULT hr = S_OK;
  3130. LPITEMIDLIST pidl;
  3131. switch (uMsg)
  3132. {
  3133. case DFM_MERGECONTEXTMENU:
  3134. // S_FALSE indicates no need to get verbs from extensions.
  3135. hr = S_FALSE;
  3136. break;
  3137. case DFM_MERGECONTEXTMENU_TOP:
  3138. {
  3139. UINT uFlags = (UINT)wParam;
  3140. LPQCMINFO pqcm = (LPQCMINFO)lParam;
  3141. CDefFolderMenu_MergeMenu(HINST_THISDLL,
  3142. (uFlags & CMF_EXTENDEDVERBS) ? MENU_GENERIC_CONTROLPANEL_VERBS : MENU_GENERIC_OPEN_VERBS, // if extended verbs then add "Run as..."
  3143. 0,
  3144. pqcm);
  3145. SetMenuDefaultItem(pqcm->hmenu, 0, MF_BYPOSITION);
  3146. break;
  3147. }
  3148. case DFM_GETHELPTEXT:
  3149. LoadStringA(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPSTR)lParam, HIWORD(wParam));
  3150. break;
  3151. case DFM_GETHELPTEXTW:
  3152. LoadStringW(HINST_THISDLL, LOWORD(wParam) + IDS_MH_FSIDM_FIRST, (LPWSTR)lParam, HIWORD(wParam));
  3153. break;
  3154. // NTRAID94991-2000/03/16-MikeSh- DFM_MAPCOMMANDNAME DFM_GETVERB[A|W] not implemented
  3155. case DFM_INVOKECOMMANDEX:
  3156. switch (wParam)
  3157. {
  3158. case FSIDM_OPENPRN:
  3159. case FSIDM_RUNAS:
  3160. hr = PidlFromDataObject(pdtobj, &pidl);
  3161. if (SUCCEEDED(hr))
  3162. {
  3163. CMINVOKECOMMANDINFOEX iciex;
  3164. SHELLEXECUTEINFO sei;
  3165. DFMICS* pdfmics = (DFMICS *)lParam;
  3166. LPVOID pvFree;
  3167. ICI2ICIX(pdfmics->pici, &iciex, &pvFree);
  3168. ICIX2SEI(&iciex, &sei);
  3169. sei.fMask |= SEE_MASK_IDLIST;
  3170. sei.lpIDList = pidl;
  3171. if (wParam == FSIDM_RUNAS)
  3172. {
  3173. // we only set the verb in the "Run As..." case since we want
  3174. // the "open" verb for darwin links to really execute the default action.
  3175. sei.lpVerb = TEXT("runas");
  3176. }
  3177. if (ShellExecuteEx(&sei))
  3178. {
  3179. // Tell UEM that we ran a Darwin app
  3180. if (_szProductCode[0])
  3181. {
  3182. UEMFireEvent(&UEMIID_SHELL, UEME_RUNPATH, UEMF_XEVENT, -1, (LPARAM)_szProductCode);
  3183. }
  3184. }
  3185. ILFree(pidl);
  3186. if (pvFree)
  3187. {
  3188. LocalFree(pvFree);
  3189. }
  3190. }
  3191. // Never return E_NOTIMPL or defcm will try to do a default thing
  3192. if (hr == E_NOTIMPL)
  3193. hr = E_FAIL;
  3194. break;
  3195. default:
  3196. // This is common menu items, use the default code.
  3197. hr = S_FALSE;
  3198. break;
  3199. }
  3200. break; // DFM_INVOKECOMMANDEX
  3201. default:
  3202. hr = E_NOTIMPL;
  3203. break;
  3204. }
  3205. return hr;
  3206. }
  3207. //
  3208. // CShellLink::CreateDarwinContextMenuForPidl (non-virtual)
  3209. //
  3210. // Worker function for CShellLink::CreateDarwinContextMenu that tries
  3211. // to create the context menu for the specified pidl.
  3212. HRESULT CShellLink::_CreateDarwinContextMenuForPidl(HWND hwnd, LPCITEMIDLIST pidlTarget, IContextMenu **pcmOut)
  3213. {
  3214. LPITEMIDLIST pidlFolder, pidlItem;
  3215. HRESULT hr = SHILClone(pidlTarget, &pidlFolder);
  3216. if (SUCCEEDED(hr))
  3217. {
  3218. if (ILRemoveLastID(pidlFolder) &&
  3219. (pidlItem = ILFindLastID(pidlTarget)))
  3220. {
  3221. IShellFolder *psf;
  3222. hr = SHBindToObject(NULL, IID_X_PPV_ARG(IShellFolder, pidlFolder, &psf));
  3223. if (SUCCEEDED(hr))
  3224. {
  3225. if (!_pcbDarwin)
  3226. {
  3227. _pcbDarwin = new CDarwinContextMenuCB();
  3228. }
  3229. if (_pcbDarwin)
  3230. {
  3231. HKEY ahkeys[1] = { NULL };
  3232. RegOpenKey(HKEY_CLASSES_ROOT, TEXT("MSILink"), &ahkeys[0]);
  3233. hr = CDefFolderMenu_Create2Ex(
  3234. pidlFolder,
  3235. hwnd,
  3236. 1, (LPCITEMIDLIST *)&pidlItem, psf, _pcbDarwin,
  3237. ARRAYSIZE(ahkeys), ahkeys, pcmOut);
  3238. SHRegCloseKeys(ahkeys, ARRAYSIZE(ahkeys));
  3239. }
  3240. else
  3241. {
  3242. hr = E_OUTOFMEMORY;
  3243. }
  3244. psf->Release();
  3245. }
  3246. }
  3247. else
  3248. {
  3249. // Darwin shortcut to the desktop? I don't think so.
  3250. hr = E_FAIL;
  3251. }
  3252. ILFree(pidlFolder);
  3253. }
  3254. return hr;
  3255. }
  3256. //
  3257. // CShellLink::CreateDarwinContextMenu (non-virtual)
  3258. //
  3259. // Creates a context menu for a Darwin shortcut. This is special because
  3260. // the ostensible target is an .EXE file, but in reality it could be
  3261. // anything. (It's just an .EXE file until the shortcut gets resolved.)
  3262. // Consequently, we can't create a real context menu for the item because
  3263. // we don't know what kind of context menu to create. We just cook up
  3264. // a generic-looking one.
  3265. //
  3266. // Bonus annoyance: _pidl might be invalid, so you need to have
  3267. // a fallback plan if it's not there. We will use c_idlDrives as our
  3268. // fallback. That's a pidl guaranteed actually to exist.
  3269. //
  3270. // Note that this means you can't invoke a command on the fallback object,
  3271. // but that's okay because ShellLink will always resolve the object to
  3272. // a real file and create a new context menu before invoking.
  3273. //
  3274. HRESULT CShellLink::_CreateDarwinContextMenu(HWND hwnd, IContextMenu **pcmOut)
  3275. {
  3276. HRESULT hr;
  3277. *pcmOut = NULL;
  3278. if (_pidl == NULL ||
  3279. FAILED(hr = _CreateDarwinContextMenuForPidl(hwnd, _pidl, pcmOut)))
  3280. {
  3281. // The link target is busted for some reason - use the fallback pidl
  3282. hr = _CreateDarwinContextMenuForPidl(hwnd, (LPCITEMIDLIST)&c_idlDrives, pcmOut);
  3283. }
  3284. return hr;
  3285. }
  3286. BOOL CShellLink::_GetExpandedPath(LPTSTR psz, DWORD cch)
  3287. {
  3288. if (_sld.dwFlags & SLDF_HAS_EXP_SZ)
  3289. {
  3290. LPEXP_SZ_LINK pesl = (LPEXP_SZ_LINK)SHFindDataBlock(_pExtraData, EXP_SZ_LINK_SIG);
  3291. if (pesl)
  3292. {
  3293. TCHAR sz[MAX_PATH];
  3294. sz[0] = 0;
  3295. // prefer the UNICODE version...
  3296. if (pesl->swzTarget[0])
  3297. SHUnicodeToTChar(pesl->swzTarget, sz, SIZECHARS(sz));
  3298. if (!sz[0] && pesl->szTarget[0])
  3299. SHAnsiToTChar(pesl->szTarget, sz, SIZECHARS(sz));
  3300. if (sz[0])
  3301. {
  3302. return SHExpandEnvironmentStrings(sz, psz, cch);
  3303. }
  3304. }
  3305. else
  3306. {
  3307. _sld.dwFlags &= ~SLDF_HAS_EXP_SZ;
  3308. }
  3309. }
  3310. return FALSE;
  3311. }
  3312. #define DEFAULT_TIMEOUT 7500 // 7 1/2 seconds...
  3313. DWORD g_dwNetLinkTimeout = (DWORD)-1;
  3314. DWORD _GetNetLinkTimeout()
  3315. {
  3316. if (g_dwNetLinkTimeout == -1)
  3317. {
  3318. DWORD cb = sizeof(g_dwNetLinkTimeout);
  3319. if (FAILED(SKGetValue(SHELLKEY_HKCU_EXPLORER, NULL, TEXT("NetLinkTimeout"), NULL, &g_dwNetLinkTimeout, &cb)))
  3320. g_dwNetLinkTimeout = DEFAULT_TIMEOUT;
  3321. }
  3322. return g_dwNetLinkTimeout;
  3323. }
  3324. DWORD CShellLink::_VerifyPathThreadProc(void *pv)
  3325. {
  3326. LPTSTR psz = (LPTSTR)pv;
  3327. PathStripToRoot(psz);
  3328. BOOL bFoundRoot = PathFileExistsAndAttributes(psz, NULL); // does WNet stuff for us
  3329. LocalFree(psz); // this thread owns this buffer
  3330. return bFoundRoot; // retrieved via GetExitCodeThread()
  3331. }
  3332. // since net timeouts can be very long this is a manual way to timeout
  3333. // an operation explictly rather than waiting for the net layers to do their
  3334. // long timeouts
  3335. HRESULT CShellLink::_ShortNetTimeout()
  3336. {
  3337. HRESULT hr = S_OK; // assume good
  3338. TCHAR szPath[MAX_PATH];
  3339. if (_pidl && SHGetPathFromIDList(_pidl, szPath) && PathIsNetworkPath(szPath))
  3340. {
  3341. hr = E_OUTOFMEMORY; // assume failure (2 cases below)
  3342. LPTSTR psz = StrDup(szPath); // give thread a copy of string to avoid buffer liftime issues
  3343. if (psz)
  3344. {
  3345. DWORD dwID;
  3346. HANDLE hThread = CreateThread(NULL, 0, _VerifyPathThreadProc, psz, 0, &dwID);
  3347. if (hThread)
  3348. {
  3349. // assume timeout...
  3350. hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); // timeout return value
  3351. if (WAIT_OBJECT_0 == WaitForSingleObject(hThread, _GetNetLinkTimeout()))
  3352. {
  3353. // thread finished
  3354. DWORD dw;
  3355. if (GetExitCodeThread(hThread, &dw) && dw)
  3356. {
  3357. hr = S_OK; // bool thread result maps to S_OK
  3358. }
  3359. }
  3360. CloseHandle(hThread);
  3361. }
  3362. else
  3363. {
  3364. LocalFree(psz);
  3365. }
  3366. }
  3367. }
  3368. return hr;
  3369. }
  3370. //
  3371. // This function returns the specified UI object from the link source.
  3372. //
  3373. // Parameters:
  3374. // hwnd -- optional hwnd for UI (for drop target)
  3375. // riid -- Specifies the interface (IID_IDropTarget, IID_IExtractIcon, IID_IContextMenu, ...)
  3376. // ppv -- Specifies the place to return the pointer.
  3377. //
  3378. // Notes:
  3379. // Don't put smart-resolving code here. Such a thing should be done
  3380. // BEFORE calling this function.
  3381. //
  3382. HRESULT CShellLink::_GetUIObject(HWND hwnd, REFIID riid, void **ppv)
  3383. {
  3384. *ppv = NULL; // Do this once and for all
  3385. HRESULT hr = E_FAIL;
  3386. if (_sld.dwFlags & SLDF_HAS_DARWINID)
  3387. {
  3388. // We commandeer a couple of IIDs if this is a Darwin link.
  3389. // Must do this before any pseudo-resolve goo because Darwin
  3390. // shortcuts don't resolve the normal way.
  3391. if (IsEqualIID(riid, IID_IContextMenu))
  3392. {
  3393. // Custom Darwin context menu.
  3394. hr = _CreateDarwinContextMenu(hwnd, (IContextMenu **)ppv);
  3395. }
  3396. else if (!IsEqualIID(riid, IID_IDropTarget) && _pidl)
  3397. {
  3398. hr = SHGetUIObjectFromFullPIDL(_pidl, hwnd, riid, ppv);
  3399. }
  3400. }
  3401. else
  3402. {
  3403. TCHAR szPath[MAX_PATH];
  3404. if (!_pidl && _GetExpandedPath(szPath, SIZECHARS(szPath)))
  3405. {
  3406. _SetSimplePIDL(szPath);
  3407. }
  3408. if (_pidl)
  3409. {
  3410. hr = SHGetUIObjectFromFullPIDL(_pidl, hwnd, riid, ppv);
  3411. }
  3412. }
  3413. return hr;
  3414. }
  3415. STDMETHODIMP CShellLink::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
  3416. {
  3417. HRESULT hr;
  3418. if (_cmTarget == NULL)
  3419. {
  3420. hr = _GetUIObject(NULL, IID_PPV_ARG(IContextMenu, _cmTarget.GetOutputPtr()));
  3421. if (FAILED(hr))
  3422. return hr;
  3423. ASSERT(_cmTarget);
  3424. }
  3425. // save these if in case we need to rebuild the cm because the resolve change the
  3426. // target of the link
  3427. _indexMenuSave = indexMenu;
  3428. _idCmdFirstSave = idCmdFirst;
  3429. _idCmdLastSave = idCmdLast;
  3430. _uFlagsSave = uFlags;
  3431. uFlags |= CMF_VERBSONLY;
  3432. if (_sld.dwFlags & SLDF_RUNAS_USER)
  3433. {
  3434. // "runas" for exe's is an extenede verb, so we have to ask for those as well.
  3435. uFlags |= CMF_EXTENDEDVERBS;
  3436. }
  3437. hr = _cmTarget.QueryContextMenu(this, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags);
  3438. // set default verb to "runas" if the "Run as different user" checkbox is checked
  3439. if (SUCCEEDED(hr) && (_sld.dwFlags & SLDF_RUNAS_USER))
  3440. {
  3441. int i = _cmTarget.GetMenuIndexForCanonicalVerb(this, hmenu, idCmdFirst, L"runas");
  3442. if (i != -1)
  3443. {
  3444. // we found runas, so set it as the default
  3445. SetMenuDefaultItem(hmenu, i, MF_BYPOSITION);
  3446. }
  3447. else
  3448. {
  3449. // the checkbox was enabled and checked, which means that the "runas" verb was supposed
  3450. // to be in the context menu, but we couldnt find it.
  3451. ASSERTMSG(FALSE, "CSL::QueryContextMenu - failed to set 'runas' as default context menu item!");
  3452. }
  3453. }
  3454. return hr;
  3455. }
  3456. HRESULT CShellLink::_InvokeCommandAsync(LPCMINVOKECOMMANDINFO pici)
  3457. {
  3458. TCHAR szWorkingDir[MAX_PATH];
  3459. CHAR szVerb[32];
  3460. CHAR szWorkingDirAnsi[MAX_PATH];
  3461. WCHAR szVerbW[32];
  3462. szVerb[0] = 0;
  3463. // if needed, get the canonical name in case the IContextMenu changes as
  3464. // a result of the resolve call BUT only do this for folders (to be safe)
  3465. // as that is typically the only case where this happens
  3466. // sepcifically we resolve from a D:\ -> \\SERVER\SHARE
  3467. if (IS_INTRESOURCE(pici->lpVerb) && (_sld.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  3468. {
  3469. _cmTarget.GetCommandString(this, LOWORD(pici->lpVerb), GCS_VERBA, NULL, szVerb, ARRAYSIZE(szVerb));
  3470. }
  3471. ASSERT(!_bDirty);
  3472. // we pass SLR_ENVOKE_MSI since we WANT to invoke darwin since we are
  3473. // really going to execute the link now
  3474. DWORD slrFlags = SLR_INVOKE_MSI;
  3475. if (pici->fMask & CMIC_MASK_FLAG_NO_UI)
  3476. {
  3477. slrFlags |= SLR_NO_UI;
  3478. }
  3479. HRESULT hr = _Resolve(pici->hwnd, slrFlags, 0);
  3480. if (hr == S_OK)
  3481. {
  3482. if (_bDirty)
  3483. {
  3484. // the context menu we have for this link is out of date - recreate it
  3485. _cmTarget.AtomicRelease();
  3486. hr = _GetUIObject(NULL, IID_PPV_ARG(IContextMenu, _cmTarget.GetOutputPtr()));
  3487. if (SUCCEEDED(hr))
  3488. {
  3489. HMENU hmenu = CreatePopupMenu();
  3490. if (hmenu)
  3491. {
  3492. hr = _cmTarget.QueryContextMenu(this, hmenu, _indexMenuSave, _idCmdFirstSave, _idCmdLastSave, _uFlagsSave | CMF_VERBSONLY);
  3493. DestroyMenu(hmenu);
  3494. }
  3495. }
  3496. Save((LPCOLESTR)NULL, TRUE); // don't care if this fails...
  3497. }
  3498. else
  3499. {
  3500. szVerb[0] = 0;
  3501. ASSERT(SUCCEEDED(hr));
  3502. }
  3503. if (SUCCEEDED(hr))
  3504. {
  3505. TCHAR szArgs[MAX_PATH];
  3506. TCHAR szExpArgs[MAX_PATH];
  3507. CMINVOKECOMMANDINFOEX ici;
  3508. CHAR szArgsAnsi[MAX_PATH];
  3509. // copy to local ici
  3510. if (pici->cbSize > sizeof(CMINVOKECOMMANDINFOEX))
  3511. {
  3512. memcpy(&ici, pici, sizeof(CMINVOKECOMMANDINFOEX));
  3513. ici.cbSize = sizeof(CMINVOKECOMMANDINFOEX);
  3514. }
  3515. else
  3516. {
  3517. memset(&ici, 0, sizeof(ici));
  3518. memcpy(&ici, pici, pici->cbSize);
  3519. ici.cbSize = sizeof(ici);
  3520. }
  3521. if (szVerb[0])
  3522. {
  3523. ici.lpVerb = szVerb;
  3524. SHAnsiToUnicode(szVerb, szVerbW, ARRAYSIZE(szVerbW));
  3525. ici.lpVerbW = szVerbW;
  3526. }
  3527. // build the args from those passed in cated on the end of the the link args
  3528. lstrcpyn(szArgs, _pszArgs ? _pszArgs : c_szNULL, ARRAYSIZE(szArgs));
  3529. if (ici.lpParameters)
  3530. {
  3531. int nArgLen = lstrlen(szArgs);
  3532. LPCTSTR lpParameters;
  3533. WCHAR szParameters[MAX_PATH];
  3534. if (ici.cbSize < CMICEXSIZE_NT4
  3535. || (ici.fMask & CMIC_MASK_UNICODE) != CMIC_MASK_UNICODE)
  3536. {
  3537. SHAnsiToUnicode(ici.lpParameters, szParameters, ARRAYSIZE(szParameters));
  3538. lpParameters = szParameters;
  3539. }
  3540. else
  3541. {
  3542. lpParameters = ici.lpParametersW;
  3543. }
  3544. lstrcpyn(szArgs + nArgLen, c_szSpace, ARRAYSIZE(szArgs) - nArgLen - 1);
  3545. lstrcpyn(szArgs + nArgLen + 1, lpParameters, ARRAYSIZE(szArgs) - nArgLen - 2);
  3546. }
  3547. // Expand environment strings in szArgs
  3548. SHExpandEnvironmentStrings(szArgs, szExpArgs, ARRAYSIZE(szExpArgs));
  3549. SHTCharToAnsi(szExpArgs, szArgsAnsi, ARRAYSIZE(szArgsAnsi));
  3550. ici.lpParameters = szArgsAnsi;
  3551. ici.lpParametersW = szExpArgs;
  3552. ici.fMask |= CMIC_MASK_UNICODE;
  3553. // if we have a working dir in the link over ride what is passed in
  3554. if (_GetWorkingDir(szWorkingDir))
  3555. {
  3556. LPCTSTR pszDir = PathIsDirectory(szWorkingDir) ? szWorkingDir : NULL;
  3557. if (pszDir)
  3558. {
  3559. SHTCharToAnsi(pszDir, szWorkingDirAnsi, ARRAYSIZE(szWorkingDirAnsi));
  3560. ici.lpDirectory = szWorkingDirAnsi;
  3561. ici.lpDirectoryW = pszDir;
  3562. }
  3563. }
  3564. // set RUN IN SEPARATE VDM if needed
  3565. if (_sld.dwFlags & SLDF_RUN_IN_SEPARATE)
  3566. {
  3567. ici.fMask |= CMIC_MASK_FLAG_SEP_VDM;
  3568. }
  3569. // and of course use our hotkey
  3570. if (_sld.wHotkey)
  3571. {
  3572. ici.dwHotKey = _sld.wHotkey;
  3573. ici.fMask |= CMIC_MASK_HOTKEY;
  3574. }
  3575. // override normal runs, but let special show cmds through
  3576. if (ici.nShow == SW_SHOWNORMAL)
  3577. {
  3578. DebugMsg(DM_TRACE, TEXT("using shorcut show cmd"));
  3579. ici.nShow = _sld.iShowCmd;
  3580. }
  3581. //
  3582. // On NT we want to pass the title to the
  3583. // thing that we are about to start.
  3584. //
  3585. // CMIC_MASK_HASLINKNAME means that the lpTitle is really
  3586. // the full path to the shortcut. The console subsystem
  3587. // sees the bit and reads all his properties directly from
  3588. // the LNK file.
  3589. //
  3590. // ShellExecuteEx also uses the path to the shortcut so it knows
  3591. // what to set in the SHCNEE_SHORTCUTINVOKE notification.
  3592. //
  3593. if (!(ici.fMask & CMIC_MASK_HASLINKNAME) && !(ici.fMask & CMIC_MASK_HASTITLE))
  3594. {
  3595. if (_pszCurFile)
  3596. {
  3597. ici.lpTitle = NULL; // Title is one or the other...
  3598. ici.lpTitleW = _pszCurFile;
  3599. ici.fMask |= CMIC_MASK_HASLINKNAME | CMIC_MASK_HASTITLE;
  3600. }
  3601. }
  3602. ASSERT((ici.nShow > SW_HIDE) && (ici.nShow <= SW_MAX));
  3603. IBindCtx *pbc;
  3604. hr = _MaybeAddShim(&pbc);
  3605. if (SUCCEEDED(hr))
  3606. {
  3607. hr = _cmTarget.InvokeCommand(this, (LPCMINVOKECOMMANDINFO)&ici);
  3608. if (pbc)
  3609. {
  3610. pbc->Release();
  3611. }
  3612. }
  3613. }
  3614. }
  3615. return hr;
  3616. }
  3617. // Structure which encapsulates the paramters needed for InvokeCommand (so
  3618. // that we can pass both parameters though a single LPARAM in CreateThread)
  3619. typedef struct
  3620. {
  3621. CShellLink *psl;
  3622. CMINVOKECOMMANDINFOEX ici;
  3623. } ICMPARAMS;
  3624. #define ICM_BASE_SIZE (sizeof(ICMPARAMS) - sizeof(CMINVOKECOMMANDINFOEX))
  3625. // Runs as a separate thread, does the actual work of calling the "real"
  3626. // InvokeCommand
  3627. DWORD CALLBACK CShellLink::_InvokeThreadProc(void *pv)
  3628. {
  3629. ICMPARAMS * pParams = (ICMPARAMS *) pv;
  3630. CShellLink *psl = pParams->psl;
  3631. IBindCtx *pbcRelease;
  3632. HRESULT hr = TBCRegisterObjectParam(TBCDIDASYNC, SAFECAST(psl, IShellLink *), &pbcRelease);
  3633. if (SUCCEEDED(hr))
  3634. {
  3635. // since we are ASYNC, this hwnd may now go bad. we just assume it has.
  3636. // we will make sure it doesnt by giving a chance for it to go bad
  3637. if (IsWindow(pParams->ici.hwnd))
  3638. {
  3639. Sleep(100);
  3640. }
  3641. if (!IsWindow(pParams->ici.hwnd))
  3642. pParams->ici.hwnd = NULL;
  3643. hr = psl->_InvokeCommandAsync((LPCMINVOKECOMMANDINFO)&pParams->ici);
  3644. pbcRelease->Release();
  3645. }
  3646. psl->Release();
  3647. LocalFree(pParams);
  3648. return (DWORD) hr;
  3649. }
  3650. // CShellLink::InvokeCommand
  3651. //
  3652. // Function that spins a thread to do the real work, which has been moved into
  3653. // CShellLink::InvokeCommandASync.
  3654. HRESULT CShellLink::InvokeCommand(LPCMINVOKECOMMANDINFO piciIn)
  3655. {
  3656. HRESULT hr = S_OK;
  3657. DWORD cchVerb, cchParameters, cchDirectory;
  3658. DWORD cchVerbW, cchParametersW, cchDirectoryW;
  3659. LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX) piciIn;
  3660. const BOOL fUnicode = pici->cbSize >= CMICEXSIZE_NT4 &&
  3661. (pici->fMask & CMIC_MASK_UNICODE) == CMIC_MASK_UNICODE;
  3662. if (_cmTarget == NULL)
  3663. return E_FAIL;
  3664. if (0 == (piciIn->fMask & CMIC_MASK_ASYNCOK))
  3665. {
  3666. // Caller didn't indicate that Async startup was OK, so we call
  3667. // InvokeCommandAync SYNCHRONOUSLY
  3668. return _InvokeCommandAsync(piciIn);
  3669. }
  3670. // Calc how much space we will need to duplicate the INVOKECOMMANDINFO
  3671. DWORD cbBaseSize = (DWORD)(ICM_BASE_SIZE + max(piciIn->cbSize, sizeof(CMINVOKECOMMANDINFOEX)));
  3672. // One byte slack in case of Unicode roundup for pPosW, below
  3673. DWORD cbSize = cbBaseSize + 1;
  3674. if (HIWORD(pici->lpVerb))
  3675. {
  3676. cbSize += (cchVerb = pici->lpVerb ? (lstrlenA(pici->lpVerb) + 1) : 0) * sizeof(CHAR);
  3677. }
  3678. cbSize += (cchParameters = pici->lpParameters ? (lstrlenA(pici->lpParameters) + 1) : 0) * sizeof(CHAR);
  3679. cbSize += (cchDirectory = pici->lpDirectory ? (lstrlenA(pici->lpDirectory) + 1) : 0) * sizeof(CHAR);
  3680. if (HIWORD(pici->lpVerbW))
  3681. {
  3682. cbSize += (cchVerbW = pici->lpVerbW ? (lstrlenW(pici->lpVerbW) + 1) : 0) * sizeof(WCHAR);
  3683. }
  3684. cbSize += (cchParametersW= pici->lpParametersW? (lstrlenW(pici->lpParametersW) + 1) : 0) * sizeof(WCHAR);
  3685. cbSize += (cchDirectoryW = pici->lpDirectoryW ? (lstrlenW(pici->lpDirectoryW) + 1) : 0) * sizeof(WCHAR);
  3686. ICMPARAMS *pParams = (ICMPARAMS *) LocalAlloc(LPTR, cbSize);
  3687. if (NULL == pParams)
  3688. {
  3689. hr = E_OUTOFMEMORY;
  3690. return hr;
  3691. }
  3692. // Text data will start going in right after the structure
  3693. CHAR *pPos = (CHAR *)((LPBYTE)pParams + cbBaseSize);
  3694. // Start with a copy of the static fields
  3695. CopyMemory(&pParams->ici, pici, pici->cbSize);
  3696. // Walk along and dupe all of the string pointer fields
  3697. if (HIWORD(pici->lpVerb))
  3698. {
  3699. pPos += cchVerb ? lstrcpyA(pPos, pici->lpVerb), pParams->ici.lpVerb = pPos, cchVerb : 0;
  3700. }
  3701. pPos += cchParameters ? lstrcpyA(pPos, pici->lpParameters), pParams->ici.lpParameters = pPos, cchParameters : 0;
  3702. pPos += cchDirectory ? lstrcpyA(pPos, pici->lpDirectory), pParams->ici.lpDirectory = pPos, cchDirectory : 0;
  3703. WCHAR *pPosW = (WCHAR *) ((DWORD_PTR)pPos & 0x1 ? pPos + 1 : pPos); // Ensure Unicode alignment
  3704. if (HIWORD(pici->lpVerbW))
  3705. {
  3706. pPosW += cchVerbW ? lstrcpyW(pPosW, pici->lpVerbW), pParams->ici.lpVerbW = pPosW, cchVerbW : 0;
  3707. }
  3708. pPosW += cchParametersW? lstrcpyW(pPosW, pici->lpParametersW),pParams->ici.lpParametersW= pPosW, cchParametersW : 0;
  3709. pPosW += cchDirectoryW ? lstrcpyW(pPosW, pici->lpDirectoryW), pParams->ici.lpDirectoryW = pPosW, cchDirectoryW : 0;
  3710. // Pass all of the info off to the worker thread that will call the actual
  3711. // InvokeCommand API for us
  3712. //Set the object pointer to this object
  3713. pParams->psl = this;
  3714. pParams->psl->AddRef();
  3715. // need to be able to be refcounted,
  3716. // so that the dataobject we create
  3717. // will stick around as long as needed.
  3718. if (!SHCreateThread(_InvokeThreadProc, pParams, CTF_COINIT | CTF_REF_COUNTED, NULL))
  3719. {
  3720. // Couldn't start the thread, so the onus is on us to clean up
  3721. pParams->psl->Release();
  3722. LocalFree(pParams);
  3723. hr = E_OUTOFMEMORY;
  3724. }
  3725. return hr;
  3726. }
  3727. HRESULT CShellLink::GetCommandString(UINT_PTR idCmd, UINT wFlags, UINT *pmf, LPSTR pszName, UINT cchMax)
  3728. {
  3729. VDATEINPUTBUF(pszName, TCHAR, cchMax);
  3730. if (_cmTarget)
  3731. {
  3732. return _cmTarget.GetCommandString(this, idCmd, wFlags, pmf, pszName, cchMax);
  3733. }
  3734. else
  3735. {
  3736. return E_FAIL;
  3737. }
  3738. }
  3739. //
  3740. // Note that we do not do a SetSite around the call to the inner HandleMenuMsg
  3741. // It isn't necessary (yet)
  3742. //
  3743. HRESULT CShellLink::TargetContextMenu::HandleMenuMsg2(IShellLink *outer, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
  3744. {
  3745. return SHForwardContextMenuMsg(_pcmTarget, uMsg, wParam, lParam, plResult, NULL==plResult);
  3746. }
  3747. STDMETHODIMP CShellLink::HandleMenuMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult)
  3748. {
  3749. if (_cmTarget)
  3750. {
  3751. return _cmTarget.HandleMenuMsg2(this, uMsg, wParam, lParam, plResult);
  3752. }
  3753. return E_NOTIMPL;
  3754. }
  3755. STDMETHODIMP CShellLink::HandleMenuMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
  3756. {
  3757. return HandleMenuMsg2(uMsg, wParam, lParam, NULL);
  3758. }
  3759. HRESULT CShellLink::_InitDropTarget()
  3760. {
  3761. if (_pdtSrc)
  3762. {
  3763. return S_OK;
  3764. }
  3765. HWND hwnd;
  3766. IUnknown_GetWindow(_punkSite, &hwnd);
  3767. return _GetUIObject(hwnd, IID_PPV_ARG(IDropTarget, &_pdtSrc));
  3768. }
  3769. STDMETHODIMP CShellLink::DragEnter(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
  3770. {
  3771. HRESULT hr = _InitDropTarget();
  3772. if (SUCCEEDED(hr))
  3773. {
  3774. _grfKeyStateLast = grfKeyState;
  3775. hr = _pdtSrc->DragEnter(pdtobj, grfKeyState, pt, pdwEffect);
  3776. }
  3777. else
  3778. {
  3779. *pdwEffect = DROPEFFECT_NONE;
  3780. }
  3781. return hr;
  3782. }
  3783. STDMETHODIMP CShellLink::DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
  3784. {
  3785. HRESULT hr = _InitDropTarget();
  3786. if (SUCCEEDED(hr))
  3787. {
  3788. _grfKeyStateLast = grfKeyState;
  3789. hr = _pdtSrc->DragOver(grfKeyState, pt, pdwEffect);
  3790. }
  3791. else
  3792. {
  3793. *pdwEffect = DROPEFFECT_NONE;
  3794. }
  3795. return hr;
  3796. }
  3797. STDMETHODIMP CShellLink::DragLeave()
  3798. {
  3799. HRESULT hr = _InitDropTarget();
  3800. if (SUCCEEDED(hr))
  3801. {
  3802. hr = _pdtSrc->DragLeave();
  3803. }
  3804. return hr;
  3805. }
  3806. STDMETHODIMP CShellLink::Drop(IDataObject *pdtobj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
  3807. {
  3808. HWND hwnd = NULL;
  3809. HRESULT hr = _InitDropTarget();
  3810. if (SUCCEEDED(hr))
  3811. {
  3812. IUnknown_GetWindow(_punkSite, &hwnd);
  3813. _pdtSrc->DragLeave(); // leave from the un-resolved drop target.
  3814. hr = _Resolve(hwnd, 0, 0); // track the target
  3815. if (S_OK == hr)
  3816. {
  3817. IDropTarget *pdtgtResolved;
  3818. if (SUCCEEDED(_GetUIObject(hwnd, IID_PPV_ARG(IDropTarget, &pdtgtResolved))))
  3819. {
  3820. IUnknown_SetSite(pdtgtResolved, SAFECAST(this, IShellLink *));
  3821. SHSimulateDrop(pdtgtResolved, pdtobj, _grfKeyStateLast, &pt, pdwEffect);
  3822. IUnknown_SetSite(pdtgtResolved, NULL);
  3823. pdtgtResolved->Release();
  3824. }
  3825. }
  3826. }
  3827. if (FAILED_AND_NOT_CANCELED(hr))
  3828. {
  3829. TCHAR szLinkSrc[MAX_PATH];
  3830. if (_pidl && SHGetPathFromIDList(_pidl, szLinkSrc))
  3831. {
  3832. ShellMessageBox(HINST_THISDLL, hwnd,
  3833. MAKEINTRESOURCE(IDS_ENUMERR_PATHNOTFOUND),
  3834. MAKEINTRESOURCE(IDS_LINKERROR),
  3835. MB_OK | MB_ICONEXCLAMATION, NULL, szLinkSrc);
  3836. }
  3837. }
  3838. if (hr != S_OK)
  3839. {
  3840. // make sure nothing happens (if we failed)
  3841. *pdwEffect = DROPEFFECT_NONE;
  3842. }
  3843. return hr;
  3844. }
  3845. STDMETHODIMP CShellLink::GetInfoTip(DWORD dwFlags, WCHAR **ppwszTip)
  3846. {
  3847. TCHAR szTip[INFOTIPSIZE];
  3848. TCHAR szDesc[INFOTIPSIZE];
  3849. StrCpyN(szTip, _pszPrefix ? _pszPrefix : TEXT(""), ARRAYSIZE(szTip));
  3850. // QITIPF_USENAME could be replaced with ICustomizeInfoTip::SetPrefixText()
  3851. if ((dwFlags & QITIPF_USENAME) && _pszCurFile)
  3852. {
  3853. SHFILEINFO sfi;
  3854. if (SHGetFileInfo(_pszCurFile, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES))
  3855. {
  3856. if (szTip[0])
  3857. StrCatBuff(szTip, TEXT("\n"), ARRAYSIZE(szTip));
  3858. StrCatBuff(szTip, sfi.szDisplayName, ARRAYSIZE(szTip));
  3859. }
  3860. }
  3861. GetDescription(szDesc, ARRAYSIZE(szDesc));
  3862. // if there is no comment, then we create one based on
  3863. // the target's location. only do this if we are not
  3864. // a darwin link, since the location has no meaning there
  3865. if (!szDesc[0] && !(_sld.dwFlags & SLDF_HAS_DARWINID) && !(dwFlags & QITIPF_LINKNOTARGET))
  3866. {
  3867. if (dwFlags & QITIPF_LINKUSETARGET)
  3868. {
  3869. SHMakeDescription(_pidl, -1, szDesc, ARRAYSIZE(szDesc));
  3870. }
  3871. else
  3872. {
  3873. _MakeDescription(_pidl, szDesc, ARRAYSIZE(szDesc));
  3874. }
  3875. }
  3876. else if (szDesc[0] == TEXT('@'))
  3877. {
  3878. WCHAR sz[INFOTIPSIZE];
  3879. if (SUCCEEDED(SHLoadIndirectString(szDesc, sz, ARRAYSIZE(sz), NULL)))
  3880. {
  3881. StrCpyN(szDesc, sz, ARRAYSIZE(szDesc));
  3882. }
  3883. }
  3884. if (szDesc[0])
  3885. {
  3886. if (szTip[0])
  3887. {
  3888. StrCatBuff(szTip, TEXT("\n"), ARRAYSIZE(szTip));
  3889. }
  3890. StrCatBuff(szTip, szDesc, ARRAYSIZE(szTip));
  3891. }
  3892. if (*szTip)
  3893. {
  3894. return SHStrDup(szTip, ppwszTip);
  3895. }
  3896. else
  3897. {
  3898. *ppwszTip = NULL;
  3899. return S_FALSE;
  3900. }
  3901. }
  3902. STDMETHODIMP CShellLink::GetInfoFlags(DWORD *pdwFlags)
  3903. {
  3904. pdwFlags = 0;
  3905. return E_NOTIMPL;
  3906. }
  3907. HRESULT CShellLink::_GetExtractIcon(REFIID riid, void **ppv)
  3908. {
  3909. HRESULT hr;
  3910. if (_pszIconLocation && _pszIconLocation[0])
  3911. {
  3912. TCHAR szPath[MAX_PATH];
  3913. // update our _pszIconLocation if we have a EXP_SZ_ICON_SIG datablock
  3914. _UpdateIconFromExpIconSz();
  3915. if (_pszIconLocation[0] == TEXT('.'))
  3916. {
  3917. TCHAR szBogusFile[MAX_PATH];
  3918. // We allow people to set ".txt" for an icon path. In this case
  3919. // we cook up a simple pidl and use it to get to the IExtractIcon for
  3920. // whatever extension the user has specified.
  3921. hr = SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, szBogusFile);
  3922. if (SUCCEEDED(hr))
  3923. {
  3924. PathAppend(szBogusFile, TEXT("*"));
  3925. lstrcatn(szBogusFile, _pszIconLocation, ARRAYSIZE(szBogusFile));
  3926. LPITEMIDLIST pidl = SHSimpleIDListFromPath(szBogusFile);
  3927. if (pidl)
  3928. {
  3929. hr = SHGetUIObjectFromFullPIDL(pidl, NULL, riid, ppv);
  3930. ILFree(pidl);
  3931. }
  3932. else
  3933. {
  3934. hr = E_OUTOFMEMORY;
  3935. }
  3936. }
  3937. }
  3938. else if ((_sld.iIcon == 0) &&
  3939. _pidl &&
  3940. SHGetPathFromIDList(_pidl, szPath) &&
  3941. (lstrcmpi(szPath, _pszIconLocation) == 0))
  3942. {
  3943. // IExtractIconA/W
  3944. hr = _GetUIObject(NULL, riid, ppv);
  3945. }
  3946. else
  3947. {
  3948. hr = SHCreateDefExtIcon(_pszIconLocation, _sld.iIcon, _sld.iIcon, GIL_PERINSTANCE, -1, riid, ppv);
  3949. }
  3950. }
  3951. else
  3952. {
  3953. // IExtractIconA/W
  3954. hr = _GetUIObject(NULL, riid, ppv);
  3955. }
  3956. return hr;
  3957. }
  3958. HRESULT CShellLink::_InitExtractIcon()
  3959. {
  3960. if (_pxi || _pxiA)
  3961. return S_OK;
  3962. HRESULT hr = _GetExtractIcon(IID_PPV_ARG(IExtractIconW, &_pxi));
  3963. if (FAILED(hr))
  3964. {
  3965. hr = _GetExtractIcon(IID_PPV_ARG(IExtractIconA, &_pxiA));
  3966. }
  3967. return hr;
  3968. }
  3969. // IExtractIconW::GetIconLocation
  3970. STDMETHODIMP CShellLink::GetIconLocation(UINT uFlags, LPWSTR pszIconFile,
  3971. UINT cchMax, int *piIndex, UINT *pwFlags)
  3972. {
  3973. // If we are in a situation where a shortcut points to itself (or LinkA <--> LinkB), then break the recursion here...
  3974. if (uFlags & GIL_FORSHORTCUT)
  3975. {
  3976. RIPMSG(uFlags & GIL_FORSHORTCUT,"CShellLink::GIL called with GIL_FORSHORTCUT (uFlags=%x)",uFlags);
  3977. return E_INVALIDARG;
  3978. }
  3979. HRESULT hr = _InitExtractIcon();
  3980. if (SUCCEEDED(hr))
  3981. {
  3982. uFlags |= GIL_FORSHORTCUT;
  3983. if (_pxi)
  3984. {
  3985. hr = _pxi->GetIconLocation(uFlags, pszIconFile, cchMax, piIndex, pwFlags);
  3986. }
  3987. else if (_pxiA)
  3988. {
  3989. CHAR sz[MAX_PATH];
  3990. hr = _pxiA->GetIconLocation(uFlags, sz, ARRAYSIZE(sz), piIndex, pwFlags);
  3991. if (SUCCEEDED(hr) && hr != S_FALSE)
  3992. SHAnsiToUnicode(sz, pszIconFile, cchMax);
  3993. }
  3994. if (SUCCEEDED(hr))
  3995. {
  3996. _gilFlags = *pwFlags;
  3997. }
  3998. }
  3999. return hr;
  4000. }
  4001. // IExtractIconA::GetIconLocation
  4002. STDMETHODIMP CShellLink::GetIconLocation(UINT uFlags, LPSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
  4003. {
  4004. WCHAR szFile[MAX_PATH];
  4005. HRESULT hr = GetIconLocation(uFlags, szFile, ARRAYSIZE(szFile), piIndex, pwFlags);
  4006. if (SUCCEEDED(hr))
  4007. {
  4008. SHUnicodeToAnsi(szFile, pszIconFile, cchMax);
  4009. }
  4010. return hr;
  4011. }
  4012. // IExtractIconW::Extract
  4013. STDMETHODIMP CShellLink::Extract(LPCWSTR pszFile, UINT nIconIndex,
  4014. HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize)
  4015. {
  4016. HRESULT hr = _InitExtractIcon();
  4017. if (SUCCEEDED(hr))
  4018. {
  4019. // GIL_PERCLASS, GIL_PERINSTANCE
  4020. if ((_gilFlags & GIL_PERINSTANCE) || !(_gilFlags & GIL_PERCLASS))
  4021. {
  4022. hr = _ShortNetTimeout(); // probe the net path
  4023. }
  4024. if (SUCCEEDED(hr)) // check again for _ShortNetTimeout() above case
  4025. {
  4026. if (_pxi)
  4027. {
  4028. hr = _pxi->Extract(pszFile, nIconIndex, phiconLarge, phiconSmall, nIconSize);
  4029. }
  4030. else if (_pxiA)
  4031. {
  4032. CHAR sz[MAX_PATH];
  4033. SHUnicodeToAnsi(pszFile, sz, ARRAYSIZE(sz));
  4034. hr = _pxiA->Extract(sz, nIconIndex, phiconLarge, phiconSmall, nIconSize);
  4035. }
  4036. }
  4037. }
  4038. return hr;
  4039. }
  4040. // IExtractIconA::Extract
  4041. STDMETHODIMP CShellLink::Extract(LPCSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize)
  4042. {
  4043. WCHAR szFile[MAX_PATH];
  4044. SHAnsiToUnicode(pszFile, szFile, ARRAYSIZE(szFile));
  4045. return Extract(szFile, nIconIndex, phiconLarge, phiconSmall, nIconSize);
  4046. }
  4047. STDMETHODIMP CShellLink::AddDataBlock(void *pdb)
  4048. {
  4049. _AddExtraDataSection((DATABLOCK_HEADER *)pdb);
  4050. return S_OK;
  4051. }
  4052. STDMETHODIMP CShellLink::CopyDataBlock(DWORD dwSig, void **ppdb)
  4053. {
  4054. DATABLOCK_HEADER *peh = (DATABLOCK_HEADER *)SHFindDataBlock(_pExtraData, dwSig);
  4055. if (peh)
  4056. {
  4057. *ppdb = LocalAlloc(LPTR, peh->cbSize);
  4058. if (*ppdb)
  4059. {
  4060. CopyMemory(*ppdb, peh, peh->cbSize);
  4061. return S_OK;
  4062. }
  4063. return E_OUTOFMEMORY;
  4064. }
  4065. *ppdb = NULL;
  4066. return E_FAIL;
  4067. }
  4068. STDMETHODIMP CShellLink::RemoveDataBlock(DWORD dwSig)
  4069. {
  4070. _RemoveExtraDataSection(dwSig);
  4071. return S_OK;
  4072. }
  4073. STDMETHODIMP CShellLink::GetFlags(DWORD *pdwFlags)
  4074. {
  4075. *pdwFlags = _sld.dwFlags;
  4076. return S_OK;
  4077. }
  4078. STDMETHODIMP CShellLink::SetFlags(DWORD dwFlags)
  4079. {
  4080. if (dwFlags != _sld.dwFlags)
  4081. {
  4082. _bDirty = TRUE;
  4083. _sld.dwFlags = dwFlags;
  4084. return S_OK;
  4085. }
  4086. return S_FALSE; // no change made
  4087. }
  4088. STDMETHODIMP CShellLink::GetPath(LPSTR pszFile, int cchFile, WIN32_FIND_DATAA *pfd, DWORD fFlags)
  4089. {
  4090. WCHAR szPath[MAX_PATH];
  4091. WIN32_FIND_DATAW wfd;
  4092. VDATEINPUTBUF(pszFile, CHAR, cchFile);
  4093. //Call the unicode version
  4094. HRESULT hr = GetPath(szPath, ARRAYSIZE(szPath), &wfd, fFlags);
  4095. if (pszFile)
  4096. {
  4097. SHUnicodeToAnsi(szPath, pszFile, cchFile);
  4098. }
  4099. if (pfd)
  4100. {
  4101. if (szPath[0])
  4102. {
  4103. pfd->dwFileAttributes = wfd.dwFileAttributes;
  4104. pfd->ftCreationTime = wfd.ftCreationTime;
  4105. pfd->ftLastAccessTime = wfd.ftLastAccessTime;
  4106. pfd->ftLastWriteTime = wfd.ftLastWriteTime;
  4107. pfd->nFileSizeLow = wfd.nFileSizeLow;
  4108. pfd->nFileSizeHigh = wfd.nFileSizeHigh;
  4109. SHUnicodeToAnsi(wfd.cFileName, pfd->cFileName, ARRAYSIZE(pfd->cFileName));
  4110. }
  4111. else
  4112. {
  4113. ZeroMemory(pfd, sizeof(*pfd));
  4114. }
  4115. }
  4116. return hr;
  4117. }
  4118. STDMETHODIMP CShellLink::SetPath(LPCSTR pszPath)
  4119. {
  4120. WCHAR szPath[MAX_PATH];
  4121. LPWSTR pszPathW;
  4122. if (pszPath)
  4123. {
  4124. SHAnsiToUnicode(pszPath, szPath, ARRAYSIZE(szPath));
  4125. pszPathW = szPath;
  4126. }
  4127. else
  4128. {
  4129. pszPathW = NULL;
  4130. }
  4131. return SetPath(pszPathW);
  4132. }
  4133. STDAPI CShellLink_CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
  4134. {
  4135. *ppv = NULL;
  4136. HRESULT hr;
  4137. CShellLink *pshlink = new CShellLink();
  4138. if (pshlink)
  4139. {
  4140. hr = pshlink->QueryInterface(riid, ppv);
  4141. pshlink->Release();
  4142. }
  4143. else
  4144. {
  4145. hr = E_OUTOFMEMORY;
  4146. }
  4147. return hr;
  4148. }
  4149. STDMETHODIMP CShellLink::Save(IPropertyBag* pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties)
  4150. {
  4151. return E_NOTIMPL;
  4152. }
  4153. STDMETHODIMP CShellLink::InitNew(void)
  4154. {
  4155. _ResetPersistData(); // clear out our state
  4156. return S_OK;
  4157. }
  4158. STDMETHODIMP CShellLink::Load(IPropertyBag* pPropBag, IErrorLog* pErrorLog)
  4159. {
  4160. _ResetPersistData(); // clear out our state
  4161. TCHAR szPath[MAX_PATH];
  4162. // TBD: Shortcut key, Run, Icon, Working Dir, Description
  4163. INT iCSIDL;
  4164. HRESULT hr = SHPropertyBag_ReadInt(pPropBag, L"TargetSpecialFolder", &iCSIDL);
  4165. if (SUCCEEDED(hr))
  4166. {
  4167. hr = SHGetFolderPath(NULL, iCSIDL, NULL, SHGFP_TYPE_CURRENT, szPath);
  4168. }
  4169. else
  4170. {
  4171. szPath[0] = 0;
  4172. hr = S_FALSE;
  4173. }
  4174. if (SUCCEEDED(hr))
  4175. {
  4176. WCHAR wsz[MAX_PATH];
  4177. if (SUCCEEDED(SHPropertyBag_ReadStr(pPropBag, L"Target", wsz, ARRAYSIZE(wsz))))
  4178. {
  4179. TCHAR szTempPath[MAX_PATH];
  4180. SHUnicodeToTChar(wsz, szTempPath, ARRAYSIZE(szTempPath));
  4181. // Do we need to append it to the Special path?
  4182. if (szPath[0])
  4183. {
  4184. // Yes
  4185. if (!PathAppend(szPath, szTempPath))
  4186. {
  4187. hr = E_FAIL;
  4188. }
  4189. }
  4190. else
  4191. {
  4192. // No, there is no special path
  4193. // Maybe we have an Env Var to expand
  4194. if (0 == SHExpandEnvironmentStrings(szTempPath, szPath, ARRAYSIZE(szPath)))
  4195. {
  4196. hr = E_FAIL;
  4197. }
  4198. }
  4199. }
  4200. else if (0 == szPath[0])
  4201. {
  4202. // make sure not empty
  4203. hr = E_FAIL;
  4204. }
  4205. if (SUCCEEDED(hr))
  4206. {
  4207. // FALSE for bUpdateTrackingData as we won't need any tracking data
  4208. // for links loaded via a property bag
  4209. hr = _SetPIDLPath(NULL, szPath, FALSE);
  4210. }
  4211. }
  4212. return hr;
  4213. }
  4214. STDMETHODIMP CShellLink::QueryService(REFGUID guidService, REFIID riid, void **ppv)
  4215. {
  4216. if (guidService == SID_LinkSite)
  4217. return QueryInterface(riid, ppv);
  4218. return IUnknown_QueryService(_punkSite, guidService, riid, ppv);
  4219. }
  4220. const FULLPROPSPEC c_rgProps[] =
  4221. {
  4222. { PSGUID_SUMMARYINFORMATION, { PRSPEC_PROPID, PIDSI_COMMENTS } },
  4223. };
  4224. STDMETHODIMP CShellLink::Init(ULONG grfFlags, ULONG cAttributes,
  4225. const FULLPROPSPEC *rgAttributes, ULONG *pFlags)
  4226. {
  4227. *pFlags = 0;
  4228. if (grfFlags & IFILTER_INIT_APPLY_INDEX_ATTRIBUTES)
  4229. {
  4230. // start at the beginning
  4231. _iChunkIndex = 0;
  4232. }
  4233. else
  4234. {
  4235. // indicate EOF
  4236. _iChunkIndex = ARRAYSIZE(c_rgProps);
  4237. }
  4238. _iValueIndex = 0;
  4239. return S_OK;
  4240. }
  4241. STDMETHODIMP CShellLink::GetChunk(STAT_CHUNK *pStat)
  4242. {
  4243. HRESULT hr = S_OK;
  4244. if (_iChunkIndex < ARRAYSIZE(c_rgProps))
  4245. {
  4246. pStat->idChunk = _iChunkIndex + 1;
  4247. pStat->idChunkSource = _iChunkIndex + 1;
  4248. pStat->breakType = CHUNK_EOP;
  4249. pStat->flags = CHUNK_VALUE;
  4250. pStat->locale = GetSystemDefaultLCID();
  4251. pStat->attribute = c_rgProps[_iChunkIndex];
  4252. pStat->cwcStartSource = 0;
  4253. pStat->cwcLenSource = 0;
  4254. _iValueIndex = 0;
  4255. _iChunkIndex++;
  4256. }
  4257. else
  4258. {
  4259. hr = FILTER_E_END_OF_CHUNKS;
  4260. }
  4261. return hr;
  4262. }
  4263. STDMETHODIMP CShellLink::GetText(ULONG *pcwcBuffer, WCHAR *awcBuffer)
  4264. {
  4265. return FILTER_E_NO_TEXT;
  4266. }
  4267. STDMETHODIMP CShellLink::GetValue(PROPVARIANT **ppPropValue)
  4268. {
  4269. HRESULT hr;
  4270. if ((_iChunkIndex <= ARRAYSIZE(c_rgProps)) && (_iValueIndex < 1))
  4271. {
  4272. *ppPropValue = (PROPVARIANT*)CoTaskMemAlloc(sizeof(PROPVARIANT));
  4273. if (*ppPropValue)
  4274. {
  4275. (*ppPropValue)->vt = VT_BSTR;
  4276. if (_pszName)
  4277. {
  4278. (*ppPropValue)->bstrVal = SysAllocStringT(_pszName);
  4279. }
  4280. else
  4281. {
  4282. // since _pszName is null, return an empty bstr
  4283. (*ppPropValue)->bstrVal = SysAllocStringT(TEXT(""));
  4284. }
  4285. if ((*ppPropValue)->bstrVal)
  4286. {
  4287. hr = S_OK;
  4288. }
  4289. else
  4290. {
  4291. CoTaskMemFree(*ppPropValue);
  4292. *ppPropValue = NULL;
  4293. hr = E_OUTOFMEMORY;
  4294. }
  4295. }
  4296. else
  4297. {
  4298. hr = E_OUTOFMEMORY;
  4299. }
  4300. _iValueIndex++;
  4301. }
  4302. else
  4303. {
  4304. hr = FILTER_E_NO_MORE_VALUES;
  4305. }
  4306. return hr;
  4307. }
  4308. STDMETHODIMP CShellLink::BindRegion(FILTERREGION origPos, REFIID riid, void **ppunk)
  4309. {
  4310. *ppunk = NULL;
  4311. return E_NOTIMPL;
  4312. }
  4313. // ICustomizeInfoTip
  4314. STDMETHODIMP CShellLink::SetPrefixText(LPCWSTR pszPrefix)
  4315. {
  4316. Str_SetPtrW(&_pszPrefix, pszPrefix);
  4317. return S_OK;
  4318. }
  4319. STDMETHODIMP CShellLink::SetExtraProperties(const SHCOLUMNID *pscid, UINT cscid)
  4320. {
  4321. return S_OK;
  4322. }
  4323. HRESULT CShellLink::_MaybeAddShim(IBindCtx **ppbcRelease)
  4324. {
  4325. // set the __COMPAT_LAYER environment variable if necessary
  4326. HRESULT hr = S_FALSE;
  4327. *ppbcRelease = 0;
  4328. if ((_sld.dwFlags & SLDF_RUN_WITH_SHIMLAYER))
  4329. {
  4330. EXP_SHIMLAYER* pShimData = (EXP_SHIMLAYER*)SHFindDataBlock(_pExtraData, EXP_SHIMLAYER_SIG);
  4331. if (pShimData && pShimData->wszLayerEnvName[0])
  4332. {
  4333. // we shouldnt recurse
  4334. ASSERT(FAILED(TBCGetEnvironmentVariable(TEXT("__COMPAT_LAYER"), NULL, 0)));
  4335. hr = TBCSetEnvironmentVariable(L"__COMPAT_LAYER", pShimData->wszLayerEnvName, ppbcRelease);
  4336. }
  4337. }
  4338. return hr;
  4339. }
  4340. DWORD CALLBACK CLinkResolver::_ThreadStartCallBack(void *pv)
  4341. {
  4342. CLinkResolver *prs = (CLinkResolver *)pv;
  4343. prs->_hThread = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId());
  4344. prs->AddRef();
  4345. return 0;
  4346. }
  4347. DWORD CLinkResolver::_Search()
  4348. {
  4349. // Attempt to find the link using the CTracker
  4350. // object (which uses NTFS object IDs and persisted information
  4351. // about link-source moves).
  4352. if (_ptracker)
  4353. {
  4354. HRESULT hr = _ptracker->Search(_dwTimeLimit, // GetTickCount()-relative timeout
  4355. &_ofd, // Original WIN32_FIND_DATA
  4356. &_fdFound, // WIN32_FIND_DATA of new location
  4357. _dwResolveFlags, // SLR_ flags
  4358. _TrackerRestrictions); // TrkMendRestriction flags
  4359. if (SUCCEEDED(hr))
  4360. {
  4361. // We've found the link source, and we're certain it's correct.
  4362. // So set the score to the highest possible value, and
  4363. // return.
  4364. _iScore = MIN_NO_UI_SCORE;
  4365. _bContinue = FALSE;
  4366. }
  4367. else if (HRESULT_FROM_WIN32(ERROR_POTENTIAL_FILE_FOUND) == hr)
  4368. {
  4369. // We've found "a" link source, but we're not certain it's correct.
  4370. // Allow the search algorithm below to run and see if it finds
  4371. // a better match.
  4372. _iScore = MIN_NO_UI_SCORE - 1;
  4373. }
  4374. else if (HRESULT_FROM_WIN32(ERROR_SERVICE_REQUEST_TIMEOUT) == hr)
  4375. {
  4376. // The CTracker search stopped because we've timed out.
  4377. _bContinue = FALSE;
  4378. }
  4379. }
  4380. // Attempt to find the link source using an enumerative search
  4381. // (unless the downlevel search has been suppressed by the caller)
  4382. if (_bContinue && !(_fifFlags & FIF_NODRIVE))
  4383. {
  4384. _HeuristicSearch();
  4385. }
  4386. if (_hDlg)
  4387. {
  4388. PostMessage(_hDlg, WM_COMMAND, IDOK, 0);
  4389. }
  4390. return _iScore;
  4391. }
  4392. DWORD CALLBACK CLinkResolver::_SearchThreadProc(void *pv)
  4393. {
  4394. // Sleep(45 * 1000); // test the network long time out case
  4395. CLinkResolver *prs = (CLinkResolver *)pv;
  4396. DWORD dwRet = prs->_Search();
  4397. prs->Release(); // AddRef in the CallBack while thread creation.
  4398. return dwRet;
  4399. }
  4400. DWORD CLinkResolver::_GetTimeOut()
  4401. {
  4402. if (0 == _dwTimeOutDelta)
  4403. {
  4404. _dwTimeOutDelta = TimeoutDeltaFromResolveFlags(_dwResolveFlags);
  4405. }
  4406. return _dwTimeOutDelta;
  4407. }
  4408. #define IDT_SHOWME 1
  4409. #define IDT_NO_UI_TIMEOUT 2
  4410. void CLinkResolver::_InitDlg(HWND hDlg)
  4411. {
  4412. _hDlg = hDlg;
  4413. if (SHCreateThread(_SearchThreadProc, this, CTF_COINIT | CTF_FREELIBANDEXIT, _ThreadStartCallBack))
  4414. {
  4415. CloseHandle(_hThread);
  4416. _hThread = NULL;
  4417. if (_dwResolveFlags & SLR_NO_UI)
  4418. {
  4419. SetTimer(hDlg, IDT_NO_UI_TIMEOUT, _GetTimeOut(), 0);
  4420. }
  4421. else
  4422. {
  4423. TCHAR szFmt[128], szTemp[MAX_PATH + ARRAYSIZE(szFmt)];
  4424. GetDlgItemText(hDlg, IDD_NAME, szFmt, ARRAYSIZE(szFmt));
  4425. wsprintf(szTemp, szFmt, _ofd.cFileName);
  4426. SetDlgItemText(hDlg, IDD_NAME, szTemp);
  4427. HWND hwndAni = GetDlgItem(hDlg, IDD_STATUS);
  4428. Animate_Open(hwndAni, MAKEINTRESOURCE(IDA_SEARCH)); // open the resource
  4429. Animate_Play(hwndAni, 0, -1, -1); // play from start to finish and repeat
  4430. // delay showing the dialog for the common case where we quickly
  4431. // find the target (in less than 1/2 a sec)
  4432. _idtDelayedShow = SetTimer(hDlg, IDT_SHOWME, 500, 0);
  4433. }
  4434. }
  4435. else
  4436. {
  4437. EndDialog(hDlg, IDCANCEL);
  4438. }
  4439. }
  4440. BOOL_PTR CALLBACK CLinkResolver::_DlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  4441. {
  4442. CLinkResolver *prs = (CLinkResolver *)GetWindowLongPtr(hDlg, DWLP_USER);
  4443. switch (wMsg)
  4444. {
  4445. case WM_INITDIALOG:
  4446. // This Dialog is created in Synchronous to the Worker thread who already has Addref'd prs, so
  4447. // no need to Addref it here.
  4448. SetWindowLongPtr(hDlg, DWLP_USER, lParam);
  4449. prs = (CLinkResolver *)lParam;
  4450. prs->_InitDlg(hDlg);
  4451. break;
  4452. case WM_COMMAND:
  4453. switch (GET_WM_COMMAND_ID(wParam, lParam))
  4454. {
  4455. case IDD_BROWSE:
  4456. prs->_hDlg = NULL; // don't let the thread close us
  4457. prs->_bContinue = FALSE; // cancel thread
  4458. Animate_Stop(GetDlgItem(hDlg, IDD_STATUS));
  4459. if (GetFileNameFromBrowse(hDlg, prs->_sfd.cFileName, ARRAYSIZE(prs->_sfd.cFileName), prs->_pszSearchOriginFirst, prs->_ofd.cFileName, NULL, NULL))
  4460. {
  4461. HANDLE hfind = FindFirstFile(prs->_sfd.cFileName, &prs->_fdFound);
  4462. ASSERT(hfind != INVALID_HANDLE_VALUE);
  4463. FindClose(hfind);
  4464. lstrcpy(prs->_fdFound.cFileName, prs->_sfd.cFileName);
  4465. prs->_iScore = MIN_NO_UI_SCORE;
  4466. wParam = IDOK;
  4467. }
  4468. else
  4469. {
  4470. wParam = IDCANCEL;
  4471. }
  4472. // Fall through...
  4473. case IDCANCEL:
  4474. // tell searching thread to stop
  4475. prs->_bContinue = FALSE;
  4476. // if the searching thread is currently in the tracker
  4477. // waiting for results, wake it up and tell it to abort
  4478. if (prs->_ptracker)
  4479. prs->_ptracker->CancelSearch();
  4480. // Fall through...
  4481. case IDOK:
  4482. // thread posts this to us
  4483. EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
  4484. break;
  4485. }
  4486. break;
  4487. case WM_TIMER:
  4488. KillTimer(hDlg, wParam); // both are one shots
  4489. switch (wParam)
  4490. {
  4491. case IDT_NO_UI_TIMEOUT:
  4492. PostMessage(prs->_hDlg, WM_COMMAND, IDCANCEL, 0);
  4493. break;
  4494. case IDT_SHOWME:
  4495. prs->_idtDelayedShow = 0;
  4496. ShowWindow(hDlg, SW_SHOW);
  4497. break;
  4498. }
  4499. break;
  4500. case WM_WINDOWPOSCHANGING:
  4501. if ((prs->_dwResolveFlags & SLR_NO_UI) || prs->_idtDelayedShow)
  4502. {
  4503. WINDOWPOS *pwp = (WINDOWPOS *)lParam;
  4504. pwp->flags &= ~SWP_SHOWWINDOW;
  4505. }
  4506. break;
  4507. default:
  4508. return FALSE;
  4509. }
  4510. return TRUE;
  4511. }
  4512. typedef struct
  4513. {
  4514. LPCTSTR pszLinkName;
  4515. LPCTSTR pszNewTarget;
  4516. LPCTSTR pszCurFile;
  4517. } DEADLINKDATA;
  4518. BOOL_PTR DeadLinkProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  4519. {
  4520. DEADLINKDATA *pdld = (DEADLINKDATA *)GetWindowPtr(hwnd, DWLP_USER);
  4521. switch (uMsg)
  4522. {
  4523. case WM_INITDIALOG:
  4524. pdld = (DEADLINKDATA*)lParam;
  4525. SetWindowPtr(hwnd, DWLP_USER, pdld);
  4526. HWNDWSPrintf(GetDlgItem(hwnd, IDC_DEADTEXT1), PathFindFileName(pdld->pszLinkName));
  4527. if (GetDlgItem(hwnd, IDC_DEADTEXT2))
  4528. PathSetDlgItemPath(hwnd, IDC_DEADTEXT2, pdld->pszNewTarget);
  4529. return TRUE;
  4530. case WM_COMMAND:
  4531. switch (GET_WM_COMMAND_ID(wParam, lParam))
  4532. {
  4533. case IDC_DELETE:
  4534. {
  4535. TCHAR szName[MAX_PATH + 1] = {0};
  4536. SHFILEOPSTRUCT fo = {
  4537. hwnd,
  4538. FO_DELETE,
  4539. szName,
  4540. NULL,
  4541. FOF_NOCONFIRMATION
  4542. };
  4543. lstrcpy(szName, pdld->pszCurFile);
  4544. SHFileOperation(&fo);
  4545. }
  4546. // fall through...
  4547. case IDCANCEL:
  4548. case IDOK:
  4549. EndDialog(hwnd, GET_WM_COMMAND_ID(wParam, lParam));
  4550. break;
  4551. }
  4552. break;
  4553. }
  4554. return FALSE;
  4555. }
  4556. // in:
  4557. // hwnd for UI if needed
  4558. //
  4559. // returns:
  4560. // IDOK found something
  4561. // IDNO didn't find it
  4562. // IDCANCEL user canceled the operation
  4563. int CLinkResolver::Resolve(HWND hwnd, LPCTSTR pszPath, LPCTSTR pszCurFile)
  4564. {
  4565. lstrcpyn(_szSearchStart, pszPath, ARRAYSIZE(_szSearchStart));
  4566. PathRemoveFileSpec(_szSearchStart);
  4567. _dwTimeLimit = GetTickCount() + _GetTimeOut();
  4568. int id = IDCANCEL;
  4569. if (SLR_NO_UI == (SLR_NO_UI_WITH_MSG_PUMP & _dwResolveFlags))
  4570. {
  4571. if (SHCreateThread(_SearchThreadProc, this, CTF_COINIT | CTF_FREELIBANDEXIT, _ThreadStartCallBack))
  4572. {
  4573. // don't care if it completes or times out. as long as it has a result
  4574. WaitForSingleObject(_hThread, _GetTimeOut());
  4575. CloseHandle(_hThread);
  4576. _hThread = NULL;
  4577. _bContinue = FALSE; // cancel that thread if it is still running
  4578. id = IDOK;
  4579. }
  4580. }
  4581. else
  4582. {
  4583. id = (int)DialogBoxParam(HINST_THISDLL,
  4584. MAKEINTRESOURCE(DLG_LINK_SEARCH),
  4585. hwnd,
  4586. _DlgProc,
  4587. (LPARAM)this);
  4588. }
  4589. if (IDOK == id)
  4590. {
  4591. if (_iScore < MIN_NO_UI_SCORE)
  4592. {
  4593. if (_dwResolveFlags & SLR_NO_UI)
  4594. {
  4595. id = IDCANCEL;
  4596. }
  4597. else
  4598. {
  4599. // we must display UI since this file is questionable
  4600. if (_fifFlags & FIF_NODRIVE)
  4601. {
  4602. LPCTSTR pszName = pszCurFile ? (LPCTSTR)PathFindFileName(pszCurFile) : c_szNULL;
  4603. ShellMessageBox(HINST_THISDLL,
  4604. hwnd,
  4605. MAKEINTRESOURCE(IDS_LINKUNAVAILABLE),
  4606. MAKEINTRESOURCE(IDS_LINKERROR),
  4607. MB_OK | MB_ICONEXCLAMATION,
  4608. pszName);
  4609. id = IDCANCEL;
  4610. }
  4611. else if (pszCurFile)
  4612. {
  4613. DEADLINKDATA dld;
  4614. dld.pszLinkName = pszPath;
  4615. dld.pszNewTarget = _fdFound.cFileName;
  4616. dld.pszCurFile = pszCurFile;
  4617. int idDlg = _iScore <= MIN_SHOW_USER_SCORE ? DLG_DEADSHORTCUT : DLG_DEADSHORTCUT_MATCH;
  4618. id = (int)DialogBoxParam(HINST_THISDLL,
  4619. MAKEINTRESOURCE(idDlg),
  4620. hwnd,
  4621. DeadLinkProc,
  4622. (LPARAM)&dld);
  4623. }
  4624. else if (_iScore <= MIN_SHOW_USER_SCORE)
  4625. {
  4626. ShellMessageBox(HINST_THISDLL,
  4627. hwnd,
  4628. MAKEINTRESOURCE(IDS_LINKNOTFOUND),
  4629. MAKEINTRESOURCE(IDS_LINKERROR),
  4630. MB_OK | MB_ICONEXCLAMATION,
  4631. PathFindFileName(pszPath));
  4632. id = IDCANCEL;
  4633. }
  4634. else
  4635. {
  4636. if (IDYES == ShellMessageBox(HINST_THISDLL,
  4637. hwnd,
  4638. MAKEINTRESOURCE(IDS_LINKCHANGED),
  4639. MAKEINTRESOURCE(IDS_LINKERROR),
  4640. MB_YESNO | MB_ICONEXCLAMATION,
  4641. PathFindFileName(pszPath),
  4642. _fdFound.cFileName))
  4643. {
  4644. id = IDOK;
  4645. }
  4646. else
  4647. {
  4648. id = IDCANCEL;
  4649. }
  4650. }
  4651. }
  4652. }
  4653. }
  4654. _ofd = _fdFound;
  4655. return id;
  4656. }
  4657. void CLinkResolver::GetResult(LPTSTR psz, UINT cch)
  4658. {
  4659. // _ofd.cFileName is a fully qualified name (strange for win32_find_data usage)
  4660. StrCpyN(psz, _ofd.cFileName, cch);
  4661. }
  4662. CLinkResolver::CLinkResolver(CTracker *ptrackerobject, const WIN32_FIND_DATA *pofd, UINT dwResolveFlags, DWORD TrackerRestrictions, DWORD fifFlags) :
  4663. _dwTimeOutDelta(0), _bContinue(TRUE), _hThread(NULL), _pstw(NULL),
  4664. _ptracker(ptrackerobject), _dwResolveFlags(dwResolveFlags), _TrackerRestrictions(TrackerRestrictions), _fifFlags(fifFlags)
  4665. {
  4666. if (_ptracker)
  4667. {
  4668. _ptracker->AddRef();
  4669. }
  4670. _ofd = *pofd; // original find data
  4671. _pszSearchOriginFirst = _szSearchStart;
  4672. _pszSearchOrigin = _szSearchStart;
  4673. _dwMatch = _ofd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; // must match bits
  4674. }
  4675. CLinkResolver::~CLinkResolver()
  4676. {
  4677. if (_ptracker)
  4678. {
  4679. _ptracker->Release();
  4680. }
  4681. ATOMICRELEASE(_pstw);
  4682. ASSERT(NULL == _hThread);
  4683. }
  4684. HRESULT CLinkResolver::_InitWalkObject()
  4685. {
  4686. HRESULT hr = _pstw ? S_OK : CoCreateInstance(CLSID_CShellTreeWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARG(IShellTreeWalker, &_pstw));
  4687. if (SUCCEEDED(hr))
  4688. {
  4689. ASSERT(_pwszSearchSpec == NULL);
  4690. // Note: We only search files with the same extension, this saves us a lot
  4691. // of useless work and from the humiliation of coming up with a ridiculous answer
  4692. _dwSearchFlags = WT_NOTIFYFOLDERENTER | WT_EXCLUDEWALKROOT;
  4693. if (_dwMatch & FILE_ATTRIBUTE_DIRECTORY)
  4694. {
  4695. _dwSearchFlags |= WT_FOLDERONLY;
  4696. }
  4697. else
  4698. {
  4699. // Note that this does the right thing if the file has no extension
  4700. LPTSTR pszExt = PathFindExtension(_ofd.cFileName);
  4701. _wszSearchSpec[0] = L'*';
  4702. SHTCharToUnicode(pszExt, &_wszSearchSpec[1], ARRAYSIZE(_wszSearchSpec) - 1);
  4703. _pwszSearchSpec = _wszSearchSpec;
  4704. // Shortcuts to shortcuts are generally not allowed, but the
  4705. // Personal Start Menu uses them for link tracking purposes...
  4706. _fFindLnk = PathIsLnk(_ofd.cFileName);
  4707. }
  4708. }
  4709. return hr;
  4710. }
  4711. //
  4712. // Compare two FileTime structures. First, see if they're really equal
  4713. // (using CompareFileTime). If not, see if one has 10ms granularity,
  4714. // and if the other rounds down to the same value. This is done
  4715. // to handle the case where a file is moved from NTFS to FAT;
  4716. // FAT file tiems are 10ms granularity, while NTFS is 100ns. When
  4717. // an NTFS file is moved to FAT, its time is rounded down.
  4718. //
  4719. #define NTFS_UNITS_PER_FAT_UNIT 100000
  4720. BOOL IsEqualFileTimesWithTruncation(const FILETIME *pft1, const FILETIME *pft2)
  4721. {
  4722. ULARGE_INTEGER uli1, uli2;
  4723. ULARGE_INTEGER *puliFAT, *puliNTFS;
  4724. FILETIME ftFAT, ftNTFS;
  4725. if (0 == CompareFileTime(pft1, pft2))
  4726. return TRUE;
  4727. uli1.LowPart = pft1->dwLowDateTime;
  4728. uli1.HighPart = pft1->dwHighDateTime;
  4729. uli2.LowPart = pft2->dwLowDateTime;
  4730. uli2.HighPart = pft2->dwHighDateTime;
  4731. // Is one of the times 10ms granular?
  4732. if (0 == (uli1.QuadPart % NTFS_UNITS_PER_FAT_UNIT))
  4733. {
  4734. puliFAT = &uli1;
  4735. puliNTFS = &uli2;
  4736. }
  4737. else if (0 == (uli2.QuadPart % NTFS_UNITS_PER_FAT_UNIT))
  4738. {
  4739. puliFAT = &uli2;
  4740. puliNTFS = &uli1;
  4741. }
  4742. else
  4743. {
  4744. // Neither time appears to be FAT, so they're
  4745. // really different.
  4746. return FALSE;
  4747. }
  4748. // If uliNTFS is already 10ms granular, then again the two times
  4749. // are really different.
  4750. if (0 == (puliNTFS->QuadPart % NTFS_UNITS_PER_FAT_UNIT))
  4751. {
  4752. return FALSE;
  4753. }
  4754. // Now see if the FAT time is the same as the NTFS time
  4755. // when the latter is rounded down to the nearest 10ms.
  4756. puliNTFS->QuadPart = (puliNTFS->QuadPart / NTFS_UNITS_PER_FAT_UNIT) * NTFS_UNITS_PER_FAT_UNIT;
  4757. ftNTFS.dwLowDateTime = puliNTFS->LowPart;
  4758. ftNTFS.dwHighDateTime = puliNTFS->HighPart;
  4759. ftFAT.dwLowDateTime = puliFAT->LowPart;
  4760. ftFAT.dwHighDateTime = puliFAT->HighPart;
  4761. return (0 == CompareFileTime(&ftFAT, &ftNTFS));
  4762. }
  4763. //
  4764. // compute a weighted score for a given find
  4765. //
  4766. int CLinkResolver::_ScoreFindData(const WIN32_FIND_DATA *pfd)
  4767. {
  4768. int iScore = 0;
  4769. BOOL bSameName = lstrcmpi(_ofd.cFileName, pfd->cFileName) == 0;
  4770. BOOL bSameExt = lstrcmpi(PathFindExtension(_ofd.cFileName), PathFindExtension(pfd->cFileName)) == 0;
  4771. BOOL bHasCreateDate = !IsNullTime(&pfd->ftCreationTime);
  4772. BOOL bSameCreateDate = bHasCreateDate &&
  4773. IsEqualFileTimesWithTruncation(&pfd->ftCreationTime, &_ofd.ftCreationTime);
  4774. BOOL bSameWriteTime = !IsNullTime(&pfd->ftLastWriteTime) &&
  4775. IsEqualFileTimesWithTruncation(&pfd->ftLastWriteTime, &_ofd.ftLastWriteTime);
  4776. if (bSameName || bSameCreateDate)
  4777. {
  4778. if (bSameName)
  4779. iScore += bHasCreateDate ? 16 : 32;
  4780. if (bSameCreateDate)
  4781. {
  4782. iScore += 32;
  4783. if (bSameExt)
  4784. iScore += 8;
  4785. }
  4786. if (bSameWriteTime)
  4787. iScore += 8;
  4788. if (pfd->nFileSizeLow == _ofd.nFileSizeLow)
  4789. iScore += 4;
  4790. // if it is in the same folder as the original give it a slight bonus
  4791. iScore += _iFolderBonus;
  4792. }
  4793. else
  4794. {
  4795. // doesn't have create date, apply different rules
  4796. if (bSameExt)
  4797. iScore += 8;
  4798. if (bSameWriteTime)
  4799. iScore += 8;
  4800. if (pfd->nFileSizeLow == _ofd.nFileSizeLow)
  4801. iScore += 4;
  4802. }
  4803. return iScore;
  4804. }
  4805. //
  4806. // Helper function for both EnterFolder and FoundFile
  4807. //
  4808. HRESULT CLinkResolver::_ProcessFoundFile(LPCTSTR pszPath, WIN32_FIND_DATAW * pwfdw)
  4809. {
  4810. HRESULT hr = S_OK;
  4811. if (_fFindLnk || !PathIsLnk(pwfdw->cFileName))
  4812. {
  4813. // both are files or folders, see how it scores
  4814. int iScore = _ScoreFindData(pwfdw);
  4815. if (iScore > _iScore)
  4816. {
  4817. _fdFound = *pwfdw;
  4818. // store the score and fully qualified path
  4819. _iScore = iScore;
  4820. lstrcpyn(_fdFound.cFileName, pszPath, ARRAYSIZE(_fdFound.cFileName));
  4821. }
  4822. }
  4823. if ((_iScore >= MIN_NO_UI_SCORE) || (GetTickCount() >= _dwTimeLimit))
  4824. {
  4825. _bContinue = FALSE;
  4826. hr = E_FAIL;
  4827. }
  4828. return hr;
  4829. }
  4830. // IShellTreeWalkerCallBack::FoundFile
  4831. HRESULT CLinkResolver::FoundFile(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd)
  4832. {
  4833. if (!_bContinue)
  4834. {
  4835. return E_FAIL;
  4836. }
  4837. // We should've excluded files if we're looking for a folder
  4838. ASSERT(!(_dwMatch & FILE_ATTRIBUTE_DIRECTORY));
  4839. return _ProcessFoundFile(pwszPath, pwfd);
  4840. }
  4841. //
  4842. // IShellTreeWalkerCallBack::EnterFolder
  4843. //
  4844. HRESULT CLinkResolver::EnterFolder(LPCWSTR pwszPath, TREEWALKERSTATS *ptws, WIN32_FIND_DATAW * pwfd)
  4845. {
  4846. HRESULT hr = S_OK;
  4847. // Respond quickly to the Cancel button.
  4848. if (!_bContinue)
  4849. {
  4850. return E_FAIL;
  4851. }
  4852. // Once we enter a directory, we lose the "we are still in the starting
  4853. // folder" bonus.
  4854. _iFolderBonus = 0;
  4855. if (PathIsPrefix(pwszPath, _pszSearchOrigin) || IS_SYSTEM_HIDDEN(pwfd->dwFileAttributes))
  4856. {
  4857. // If we're about to enter a directory we've already looked in,
  4858. // or if this is superhidden (implies recycle bin dirs), then skip it.
  4859. return S_FALSE;
  4860. }
  4861. // If our target was a folder, treat this folder as a file found
  4862. if (_dwMatch & FILE_ATTRIBUTE_DIRECTORY)
  4863. {
  4864. hr = _ProcessFoundFile(pwszPath, pwfd);
  4865. }
  4866. return hr;
  4867. }
  4868. BOOL CLinkResolver::_SearchInFolder(LPCTSTR pszFolder, int cLevels)
  4869. {
  4870. int iMaxDepth = 0;
  4871. // cLevels == -1 means inifinite depth
  4872. if (cLevels != -1)
  4873. {
  4874. _dwSearchFlags |= WT_MAXDEPTH;
  4875. iMaxDepth = cLevels;
  4876. }
  4877. else
  4878. {
  4879. _dwSearchFlags &= ~WT_MAXDEPTH;
  4880. }
  4881. // Our folder bonus code lies on the fact that files in the
  4882. // starting folder come before anything else.
  4883. ASSERT(!(_dwSearchFlags & WT_FOLDERFIRST));
  4884. _pstw->WalkTree(_dwSearchFlags, pszFolder, _pwszSearchSpec, iMaxDepth, SAFECAST(this, IShellTreeWalkerCallBack *));
  4885. _iFolderBonus = 0; // You only get one chance at the folder bonus
  4886. return _bContinue;
  4887. }
  4888. //
  4889. // search function for heuristic based link resolution
  4890. // the result will be in _fdFound.cFileName
  4891. //
  4892. void CLinkResolver::_HeuristicSearch()
  4893. {
  4894. if (!SHRestricted(REST_NORESOLVESEARCH) &&
  4895. !(SLR_NOSEARCH & _dwResolveFlags) &&
  4896. SUCCEEDED(_InitWalkObject()))
  4897. {
  4898. int cUp = LNKTRACK_HINTED_UPLEVELS;
  4899. BOOL bSearchOrigin = TRUE;
  4900. TCHAR szRealSearchOrigin[MAX_PATH], szFolderPath[MAX_PATH];
  4901. // search up from old location
  4902. // In the olden days pszSearchOriginFirst was verified to be a valid directory
  4903. // (ie it returned TRUE to PathIsDirectory) and _HeuristicSearch was never called
  4904. // if this was not true. Alas, this is no more. Why not search the desktop and
  4905. // fixed drives anyway? In the interest of saving some time the check that used
  4906. // to be in FindInFolder which caused an early out is now here instead. The rub
  4907. // is that we only skip the downlevel search of the original volume instead of
  4908. // skipping the entire link resolution phase.
  4909. lstrcpy(szRealSearchOrigin, _pszSearchOriginFirst);
  4910. while (!PathIsDirectory(szRealSearchOrigin))
  4911. {
  4912. if (PathIsRoot(szRealSearchOrigin) || !PathRemoveFileSpec(szRealSearchOrigin))
  4913. {
  4914. DebugMsg(DM_TRACE, TEXT("root path does not exists %s"), szRealSearchOrigin);
  4915. bSearchOrigin = FALSE;
  4916. break;
  4917. }
  4918. }
  4919. if (bSearchOrigin)
  4920. {
  4921. lstrcpy(szFolderPath, szRealSearchOrigin);
  4922. _pszSearchOrigin = szRealSearchOrigin;
  4923. // Files found in the starting folder get a slight bonus.
  4924. // _iFolderBonus is set to zero by
  4925. // CLinkResolver::EnterFolder when we leave
  4926. // the starting folder and enter a new one.
  4927. _iFolderBonus = 2;
  4928. while (cUp-- != 0 && _SearchInFolder(szFolderPath, LNKTRACK_HINTED_DOWNLEVELS))
  4929. {
  4930. if (PathIsRoot(szFolderPath) || !PathRemoveFileSpec(szFolderPath))
  4931. break;
  4932. }
  4933. }
  4934. if (_bContinue)
  4935. {
  4936. // search down from desktop
  4937. if (S_OK == SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, szFolderPath))
  4938. {
  4939. _pszSearchOrigin = szFolderPath;
  4940. _SearchInFolder(szFolderPath, LNKTRACK_DESKTOP_DOWNLEVELS);
  4941. }
  4942. }
  4943. if (_bContinue)
  4944. {
  4945. // search down from root of fixed drives
  4946. TCHAR szRoot[4];
  4947. _pszSearchOrigin = szRoot;
  4948. for (int i = 0; _bContinue && (i < 26); i++)
  4949. {
  4950. if (GetDriveType(PathBuildRoot(szRoot, i)) == DRIVE_FIXED)
  4951. {
  4952. lstrcpy(szFolderPath, szRoot);
  4953. _SearchInFolder(szFolderPath, LNKTRACK_ROOT_DOWNLEVELS);
  4954. }
  4955. }
  4956. }
  4957. if (_bContinue && bSearchOrigin)
  4958. {
  4959. // resume search of last volume (should do an exclude list)
  4960. lstrcpy(szFolderPath, szRealSearchOrigin);
  4961. _pszSearchOrigin = szRealSearchOrigin;
  4962. while (_SearchInFolder(szFolderPath, -1))
  4963. {
  4964. if (PathIsRoot(szFolderPath) || !PathRemoveFileSpec(szFolderPath))
  4965. break;
  4966. }
  4967. }
  4968. }
  4969. }