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

5745 lines
179 KiB

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