Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1546 lines
49 KiB

  1. // Authors;
  2. // Jeff Saathoff (jeffreys)
  3. //
  4. // Notes;
  5. // Context Menu and Property Sheet shell extensions
  6. #include "pch.h"
  7. #include "options.h" // ..\viewer\options.h
  8. #include "firstpin.h"
  9. #include "uihooks.h" // Self-host notifications
  10. #include "msgbox.h"
  11. #include "strings.h"
  12. #include "nopin.h"
  13. #include <ccstock.h>
  14. #define CSC_PROP_NO_CSC 0x00000001L
  15. #define CSC_PROP_MULTISEL 0x00000002L
  16. #define CSC_PROP_PINNED 0x00000004L
  17. #define CSC_PROP_SYNCABLE 0x00000008L
  18. #define CSC_PROP_ADMIN_PINNED 0x00000010L
  19. #define CSC_PROP_INHERIT_PIN 0x00000020L
  20. #define CSC_PROP_DCON_MODE 0x00000040L
  21. // Thread data for unpinning files
  22. typedef struct
  23. {
  24. CscFilenameList *pNamelist;
  25. DWORD dwUpdateFlags;
  26. HWND hwndOwner;
  27. BOOL bOffline;
  28. } CSC_UNPIN_DATA;
  29. ///////////////////////////////////////////////////////////////////////////////
  30. // //
  31. // Shell extension object implementation //
  32. // //
  33. ///////////////////////////////////////////////////////////////////////////////
  34. STDAPI CCscShellExt::CreateInstance(REFIID riid, LPVOID *ppv)
  35. {
  36. HRESULT hr;
  37. CCscShellExt *pThis = new CCscShellExt;
  38. if (pThis)
  39. {
  40. hr = pThis->QueryInterface(riid, ppv);
  41. pThis->Release(); // release initial ref
  42. }
  43. else
  44. hr = E_OUTOFMEMORY;
  45. return hr;
  46. }
  47. ///////////////////////////////////////////////////////////////////////////////
  48. // //
  49. // Shell extension object implementation (IUnknown) //
  50. // //
  51. ///////////////////////////////////////////////////////////////////////////////
  52. STDMETHODIMP CCscShellExt::QueryInterface(REFIID riid, void **ppv)
  53. {
  54. static const QITAB qit[] =
  55. {
  56. QITABENT(CCscShellExt, IShellExtInit),
  57. QITABENT(CCscShellExt, IContextMenu),
  58. QITABENT(CCscShellExt, IShellIconOverlayIdentifier),
  59. { 0 },
  60. };
  61. return QISearch(this, qit, riid, ppv);
  62. }
  63. STDMETHODIMP_(ULONG) CCscShellExt::AddRef()
  64. {
  65. return InterlockedIncrement(&m_cRef);
  66. }
  67. STDMETHODIMP_(ULONG) CCscShellExt::Release()
  68. {
  69. if (InterlockedDecrement(&m_cRef))
  70. return m_cRef;
  71. delete this;
  72. return 0;
  73. }
  74. // IShellExtInit
  75. STDMETHODIMP CCscShellExt::Initialize(LPCITEMIDLIST /*pidlFolder*/, IDataObject *pdobj, HKEY /*hKeyProgID*/)
  76. {
  77. IUnknown_Set((IUnknown **)&m_lpdobj, pdobj);
  78. return S_OK;
  79. }
  80. // IContextMenu
  81. //
  82. // PURPOSE: Called by the shell just before the context menu is displayed.
  83. // This is where you add your specific menu items.
  84. //
  85. // PARAMETERS:
  86. // hMenu - Handle to the context menu
  87. // iMenu - Index of where to begin inserting menu items
  88. // idCmdFirst - Lowest value for new menu ID's
  89. // idCmtLast - Highest value for new menu ID's
  90. // uFlags - Specifies the context of the menu event
  91. //
  92. // RETURN VALUE:
  93. // HRESULT signifying success or failure.
  94. //
  95. STDMETHODIMP CCscShellExt::QueryContextMenu(HMENU hMenu, UINT iMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
  96. {
  97. HRESULT hr = ResultFromShort(0);
  98. UINT idCmd = idCmdFirst;
  99. TCHAR szMenu[MAX_PATH];
  100. MENUITEMINFO mii;
  101. CConfig& config = CConfig::GetSingleton();
  102. if ((uFlags & (CMF_DEFAULTONLY | CMF_VERBSONLY)) || !m_lpdobj)
  103. return hr;
  104. TraceEnter(TRACE_SHELLEX, "CCscShellExt::QueryContextMenu");
  105. TraceAssert(IsCSCEnabled());
  106. //
  107. // Check the pin status and CSC-ability of the current selection
  108. //
  109. m_dwUIStatus = 0;
  110. if (FAILED(CheckFileStatus(m_lpdobj, &m_dwUIStatus)))
  111. m_dwUIStatus = CSC_PROP_NO_CSC;
  112. if (m_dwUIStatus & CSC_PROP_NO_CSC)
  113. TraceLeaveResult(hr);
  114. //
  115. // Add a menu separator
  116. //
  117. mii.cbSize = sizeof(mii);
  118. mii.fMask = MIIM_TYPE;
  119. mii.fType = MFT_SEPARATOR;
  120. InsertMenuItem(hMenu, iMenu++, TRUE, &mii);
  121. if (!config.NoMakeAvailableOffline())
  122. {
  123. if (SUCCEEDED(hr = CanAllFilesBePinned(m_lpdobj)))
  124. {
  125. if (S_OK == hr)
  126. {
  127. mii.fState = MFS_ENABLED; // All files in selection can be pinned.
  128. }
  129. else
  130. {
  131. mii.fState = MFS_DISABLED; // 1+ files in selection cannot be pinned.
  132. }
  133. //
  134. // Add the "Make Available Offline" menu item
  135. //
  136. LoadString(g_hInstance, IDS_MENU_PIN, szMenu, ARRAYSIZE(szMenu));
  137. mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
  138. mii.fType = MFT_STRING;
  139. if (m_dwUIStatus & (CSC_PROP_ADMIN_PINNED | CSC_PROP_PINNED))
  140. {
  141. mii.fState = MFS_CHECKED;
  142. if (m_dwUIStatus & (CSC_PROP_ADMIN_PINNED | CSC_PROP_INHERIT_PIN))
  143. mii.fState |= MFS_DISABLED;
  144. }
  145. mii.wID = idCmd++;
  146. mii.dwTypeData = szMenu;
  147. InsertMenuItem(hMenu, iMenu++, TRUE, &mii);
  148. }
  149. }
  150. if (m_dwUIStatus & (CSC_PROP_SYNCABLE | CSC_PROP_PINNED | CSC_PROP_ADMIN_PINNED))
  151. {
  152. //
  153. // Add the "Synchronize" menu item
  154. //
  155. LoadString(g_hInstance, IDS_MENU_SYNCHRONIZE, szMenu, ARRAYSIZE(szMenu));
  156. mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
  157. mii.fType = MFT_STRING;
  158. mii.fState = MFS_ENABLED;
  159. mii.wID = idCmd++;
  160. mii.dwTypeData = szMenu;
  161. InsertMenuItem(hMenu, iMenu++, TRUE, &mii);
  162. }
  163. //
  164. // Return the number of menu items we added.
  165. //
  166. hr = ResultFromShort(idCmd - idCmdFirst);
  167. TraceLeaveResult(hr);
  168. }
  169. //
  170. // FUNCTION: IContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO)
  171. //
  172. // PURPOSE: Called by the shell after the user has selected on of the
  173. // menu items that was added in QueryContextMenu().
  174. //
  175. // PARAMETERS:
  176. // lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
  177. //
  178. // RETURN VALUE:
  179. // HRESULT signifying success or failure.
  180. //
  181. // COMMENTS:
  182. //
  183. STDMETHODIMP
  184. CCscShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
  185. {
  186. HRESULT hr = S_OK;
  187. UINT iCmd = 0;
  188. CscFilenameList *pfnl = NULL; // Namelist object.
  189. BOOL fPin;
  190. BOOL bSubFolders = FALSE;
  191. DWORD dwUpdateFlags = 0;
  192. TraceEnter(TRACE_SHELLEX, "CCscShellExt::InvokeCommand");
  193. TraceAssert(IsCSCEnabled());
  194. TraceAssert(!(m_dwUIStatus & CSC_PROP_NO_CSC));
  195. if (HIWORD(lpcmi->lpVerb))
  196. {
  197. if (!lstrcmpiA(lpcmi->lpVerb, STR_PIN_VERB))
  198. {
  199. iCmd = 0;
  200. m_dwUIStatus &= ~CSC_PROP_PINNED;
  201. }
  202. else if (!lstrcmpiA(lpcmi->lpVerb, STR_UNPIN_VERB))
  203. {
  204. iCmd = 0;
  205. m_dwUIStatus |= CSC_PROP_PINNED;
  206. }
  207. else if (!lstrcmpiA(lpcmi->lpVerb, STR_SYNC_VERB))
  208. {
  209. iCmd = 1;
  210. }
  211. else
  212. {
  213. Trace((TEXT("Unknown command \"%S\""), lpcmi->lpVerb));
  214. ExitGracefully(hr, E_INVALIDARG, "Invalid command");
  215. }
  216. }
  217. else
  218. {
  219. iCmd = LOWORD(lpcmi->lpVerb);
  220. // If we didn't add the "Make Available Offline" verb, adjust the index
  221. if (CConfig::GetSingleton().NoMakeAvailableOffline())
  222. iCmd++;
  223. }
  224. if (iCmd >= 2)
  225. ExitGracefully(hr, E_INVALIDARG, "Invalid command");
  226. pfnl = new CscFilenameList;
  227. if (!pfnl)
  228. ExitGracefully(hr, E_OUTOFMEMORY, "Unable to create CscFilenameList object");
  229. hr = BuildFileList(m_lpdobj, lpcmi->hwnd, pfnl, &bSubFolders);
  230. FailGracefully(hr, "Unable to build file list");
  231. switch (iCmd)
  232. {
  233. case 0: // "Make available offline" menu choice - Pin files
  234. if (!FirstPinWizardCompleted())
  235. {
  236. //
  237. // User has never seen the "first pin" wizard.
  238. //
  239. if (S_FALSE == ShowFirstPinWizard(lpcmi->hwnd))
  240. {
  241. //
  242. // User cancelled wizard. Abort pinning operation.
  243. //
  244. ExitGracefully(hr, S_OK, "User cancelled first-pin wizard");
  245. }
  246. }
  247. fPin = !(m_dwUIStatus & CSC_PROP_PINNED);
  248. if (!fPin && (m_dwUIStatus & CSC_PROP_DCON_MODE))
  249. {
  250. // Unpin while disconnected causes things to disappear.
  251. // Warn the user.
  252. if (IDCANCEL == CscMessageBox(lpcmi->hwnd,
  253. MB_OKCANCEL | MB_ICONWARNING,
  254. g_hInstance,
  255. IDS_CONFIRM_UNPIN_OFFLINE))
  256. {
  257. ExitGracefully(hr, E_FAIL, "User cancelled disconnected unpin operation");
  258. }
  259. }
  260. // If there is a directory in the list AND we're pinning AND
  261. // the "AlwaysPinSubFolders" policy is NOT set, ask the user
  262. // whether to go deep or not.
  263. // If the policy IS set we automatically do a recursive pin.
  264. if (bSubFolders && (!fPin || !CConfig::GetSingleton().AlwaysPinSubFolders()))
  265. {
  266. switch (DialogBox(g_hInstance,
  267. MAKEINTRESOURCE(fPin ? IDD_CONFIRM_PIN : IDD_CONFIRM_UNPIN),
  268. lpcmi->hwnd,
  269. _ConfirmPinDlgProc))
  270. {
  271. case IDYES:
  272. // nothing
  273. break;
  274. case IDNO:
  275. bSubFolders = FALSE; // no subfolders
  276. break;
  277. case IDCANCEL:
  278. ExitGracefully(hr, E_FAIL, "User cancelled (un)pin operation");
  279. break;
  280. }
  281. }
  282. if (bSubFolders)
  283. dwUpdateFlags |= CSC_UPDATE_PIN_RECURSE;
  284. if (fPin)
  285. {
  286. // Self-host notification callback
  287. CSCUI_NOTIFYHOOK((CSCH_Pin, TEXT("Pinning %1!d! selected items"), pfnl->GetFileCount()));
  288. // Set the flags for pin + quick sync
  289. dwUpdateFlags |= CSC_UPDATE_SELECTION | CSC_UPDATE_STARTNOW
  290. | CSC_UPDATE_PINFILES | CSC_UPDATE_FILL_QUICK;
  291. }
  292. else
  293. {
  294. HANDLE hThread;
  295. DWORD dwThreadID;
  296. CSC_UNPIN_DATA *pUnpinData = (CSC_UNPIN_DATA *)LocalAlloc(LPTR, sizeof(*pUnpinData));
  297. // Self-host notification callback
  298. CSCUI_NOTIFYHOOK((CSCH_Unpin, TEXT("Unpinning %1!d! selected items"), pfnl->GetFileCount()));
  299. //
  300. // No sync is required to unpin files, so let's do it in this
  301. // process rather than starting SyncMgr. However, let's do
  302. // it in the background in case there's a lot to unpin.
  303. //
  304. if (pUnpinData)
  305. {
  306. pUnpinData->pNamelist = pfnl;
  307. pUnpinData->dwUpdateFlags = dwUpdateFlags;
  308. pUnpinData->hwndOwner = lpcmi->hwnd;
  309. pUnpinData->bOffline = !!(m_dwUIStatus & CSC_PROP_DCON_MODE);
  310. hThread = CreateThread(NULL,
  311. 0,
  312. _UnpinFilesThread,
  313. pUnpinData,
  314. 0,
  315. &dwThreadID);
  316. if (hThread)
  317. {
  318. // The thread will delete pUnpinData and pUnpinData->pNamelist
  319. pfnl = NULL;
  320. // We give the async thread a little time to complete, during which we
  321. // put up the busy cursor. This is solely to let the user see that
  322. // some work is being done...
  323. HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
  324. WaitForSingleObject(hThread, 750);
  325. CloseHandle(hThread);
  326. SetCursor(hCur);
  327. }
  328. else
  329. {
  330. LocalFree(pUnpinData);
  331. }
  332. }
  333. // Clear the flags to prevent sync below
  334. dwUpdateFlags = 0;
  335. }
  336. break;
  337. case 1: // Synchronize
  338. // Set the flags for a full sync
  339. dwUpdateFlags = CSC_UPDATE_SELECTION | CSC_UPDATE_STARTNOW
  340. | CSC_UPDATE_REINT | CSC_UPDATE_FILL_ALL
  341. | CSC_UPDATE_SHOWUI_ALWAYS | CSC_UPDATE_NOTIFY_DONE;
  342. break;
  343. }
  344. //
  345. // Update the files we are pinning or synchronizing.
  346. // Setting the "ignore access" flag will cause us to ignore the
  347. // user/guest/other access info and sync all selected files. We want
  348. // this behavior as the operation was initiated by a user's explicit
  349. // selection of files/folders in explorer.
  350. //
  351. if (dwUpdateFlags && pfnl->GetFileCount())
  352. {
  353. if (!::IsSyncInProgress())
  354. {
  355. hr = CscUpdateCache(dwUpdateFlags | CSC_UPDATE_IGNORE_ACCESS, pfnl);
  356. }
  357. else
  358. {
  359. //
  360. // A sync is in progress. Tell user why they can't currently
  361. // pin or sync.
  362. //
  363. const UINT rgidsMsg[] = { IDS_CANTPIN_SYNCINPROGRESS,
  364. IDS_CANTSYNC_SYNCINPROGRESS };
  365. CscMessageBox(lpcmi->hwnd,
  366. MB_OK | MB_ICONINFORMATION,
  367. g_hInstance,
  368. rgidsMsg[iCmd]);
  369. }
  370. }
  371. exit_gracefully:
  372. delete pfnl;
  373. TraceLeaveResult(hr);
  374. }
  375. //
  376. // FUNCTION: IContextMenu::GetCommandString(UINT, UINT, UINT, LPSTR, UINT)
  377. //
  378. // PURPOSE: Called by the shell after the user has selected on of the
  379. // menu items that was added in QueryContextMenu().
  380. //
  381. // PARAMETERS:
  382. // lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
  383. //
  384. // RETURN VALUE:
  385. // HRESULT signifying success or failure.
  386. //
  387. // COMMENTS:
  388. //
  389. STDMETHODIMP
  390. CCscShellExt::GetCommandString(UINT_PTR iCmd,
  391. UINT uFlags,
  392. LPUINT /*reserved*/,
  393. LPSTR pszString,
  394. UINT cchMax)
  395. {
  396. HRESULT hr = E_UNEXPECTED;
  397. if (uFlags == GCS_VALIDATE)
  398. hr = S_FALSE;
  399. if (iCmd > 1)
  400. return hr;
  401. hr = S_OK;
  402. if (uFlags == GCS_HELPTEXT)
  403. {
  404. LoadString(g_hInstance, iCmd ? IDS_HELP_UPDATE_SEL : IDS_HELP_PIN, (LPTSTR)pszString, cchMax);
  405. }
  406. else if (uFlags == GCS_VERB)
  407. {
  408. lstrcpyn((LPTSTR)pszString, iCmd ? TEXT(STR_SYNC_VERB) : ((m_dwUIStatus & CSC_PROP_PINNED) ? TEXT(STR_UNPIN_VERB) : TEXT(STR_PIN_VERB)), cchMax);
  409. }
  410. else if (uFlags != GCS_VALIDATE)
  411. {
  412. // Must be some other flag that we don't handle
  413. hr = E_NOTIMPL;
  414. }
  415. return hr;
  416. }
  417. ///////////////////////////////////////////////////////////////////////////////
  418. // //
  419. // Shell extension object implementation (IShellIconOverlayIdentifier) //
  420. // //
  421. ///////////////////////////////////////////////////////////////////////////////
  422. STDMETHODIMP CCscShellExt::IsMemberOf(LPCWSTR pwszPath, DWORD dwAttrib)
  423. {
  424. HRESULT hr = S_FALSE; // assume not pinned
  425. DWORD dwHintFlags;
  426. DWORD dwErr;
  427. LPTSTR pszUNC = NULL;
  428. LPTSTR pszSlash;
  429. USES_CONVERSION;
  430. //
  431. // Make sure we have a UNC path
  432. //
  433. GetRemotePath(W2CT(pwszPath), &pszUNC);
  434. if (!pszUNC)
  435. return S_FALSE;
  436. //
  437. // Ask CSC if this is a pinned file
  438. //
  439. dwHintFlags = 0;
  440. if (CSCQueryFileStatus(pszUNC, NULL, NULL, &dwHintFlags))
  441. {
  442. if (dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN))
  443. hr = S_OK;
  444. }
  445. else
  446. {
  447. dwErr = GetLastError();
  448. if (ERROR_FILE_NOT_FOUND != dwErr)
  449. {
  450. //
  451. // Need to check for 0 to accomodate GetLastError
  452. // returning 0 on CSCQueryFileStatus failure.
  453. // I'll talk to Shishir about getting this fixed.
  454. // [brianau - 5/13/99]
  455. //
  456. // Most of these were fixed for Windows 2000. If we
  457. // hit the assertion below we should file a bug and
  458. // get Shishir to fix it.
  459. // [jeffreys - 1/24/2000]
  460. //
  461. if (0 == dwErr)
  462. {
  463. ASSERTMSG(FALSE, "CSCQueryFileStatus failed with error = 0");
  464. dwErr = ERROR_GEN_FAILURE;
  465. }
  466. hr = HRESULT_FROM_WIN32(dwErr);
  467. }
  468. }
  469. DWORD dwAttribTest = FILE_ATTRIBUTE_ENCRYPTED;
  470. if (!CConfig::GetSingleton().AlwaysPinSubFolders())
  471. dwAttribTest |= FILE_ATTRIBUTE_DIRECTORY;
  472. if (S_FALSE == hr && !(dwAttrib & dwAttribTest))
  473. {
  474. //
  475. // Check to see if pinning is disallowed by system policy.
  476. //
  477. hr = m_NoPinList.IsPinAllowed(pszUNC);
  478. if (S_OK == hr)
  479. {
  480. hr = S_FALSE; // Reset
  481. //
  482. // If we get here, then either CSCQueryFileStatus succeeded but the file
  483. // isn't pinned, or the file isn't in the cache (ERROR_FILE_NOT_FOUND).
  484. // Also, policy allows pinning of this file/folder.
  485. //
  486. // Check whether the parent folder has the pin-inherit-user or
  487. // admin-pin flag and pin this file if necessary.
  488. //
  489. // Note that we don't pin encrypted files here.
  490. // Also note that pinning of folder is policy-dependent. The default
  491. // behavior is to NOT pin folders (only files). If the
  492. // "AlwaysPinSubFolders" policy is set, we will pin folders.
  493. //
  494. pszSlash = PathFindFileName(pszUNC);
  495. if (pszSlash && pszUNC != pszSlash)
  496. {
  497. --pszSlash;
  498. *pszSlash = TEXT('\0'); // truncate the path
  499. // Check the parent status
  500. if (CSCQueryFileStatus(pszUNC, NULL, NULL, &dwHintFlags) &&
  501. (dwHintFlags & (FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_ADMIN)))
  502. {
  503. // The parent is pinned, so pin this file with the same flags
  504. if (dwHintFlags & FLAG_CSC_HINT_PIN_USER)
  505. dwHintFlags |= FLAG_CSC_HINT_PIN_INHERIT_USER;
  506. // Restore the rest of the path
  507. *pszSlash = TEXT('\\');
  508. //
  509. // To avoid a nasty race condition between purging and auto-pinning we need
  510. // to disable auto-pinning when a purge is in progress. The race condition
  511. // can occur if a shell folder for the files being purged is open. We purge
  512. // a file and send out a change notify. The shell updates the icon overlay
  513. // and calls our overlay handler to remove the overlay. Our handler notices
  514. // that the parent folder is pinned so we re-pin the file which places it
  515. // back in the cache. Ugh... [brianau - 11/01/99]
  516. //
  517. // p.s.: Note that this check calls WaitForSingleObject so we only
  518. // do it AFTER we're sure that we want to pin the file. We don't
  519. // want to do the "wait" and THEN decide the file should not be
  520. // pinned because it's not a UNC path or it's a directory.
  521. //
  522. if (!IsPurgeInProgress())
  523. {
  524. if (CSCPinFile(pszUNC, dwHintFlags, NULL, NULL, NULL))
  525. hr = S_OK;
  526. }
  527. }
  528. }
  529. }
  530. }
  531. LocalFreeString(&pszUNC);
  532. return hr;
  533. }
  534. STDMETHODIMP
  535. CCscShellExt::GetOverlayInfo (LPWSTR pwszIconFile,
  536. int cchMax,
  537. int * pIndex,
  538. DWORD * pdwFlags)
  539. {
  540. if (cchMax < (lstrlen(c_szDllName) + 1))
  541. {
  542. return E_OUTOFMEMORY;
  543. }
  544. #ifdef UNICODE
  545. lstrcpyn (pwszIconFile, c_szDllName, cchMax);
  546. #else
  547. MultiByteToWideChar (CP_ACP, 0, c_szDllName, -1, pwszIconFile, cchMax);
  548. #endif
  549. // Using the ID doesn't work on Win95. We're currently not shipping
  550. // on Win95, but if that changes, we may need to fix this. The overlay icon
  551. // is deliberately early in the DLL (right after the CSC icon) so the index
  552. // should always be one, but using the ID is still preferable.
  553. #ifdef WINNT
  554. // Use positive #'s for indexes, negative for ID's
  555. *pIndex = -IDI_PIN_OVERLAY;
  556. #else
  557. *pIndex = 1;
  558. #endif
  559. *pdwFlags = (ISIOI_ICONFILE | ISIOI_ICONINDEX);
  560. return S_OK;
  561. }
  562. STDMETHODIMP
  563. CCscShellExt::GetPriority (int * pIPriority)
  564. {
  565. *pIPriority = 1;
  566. return S_OK;
  567. }
  568. ///////////////////////////////////////////////////////////////////////////////
  569. // //
  570. // CCscShellExt implementation //
  571. // //
  572. ///////////////////////////////////////////////////////////////////////////////
  573. BOOL
  574. ShareIsCacheable(LPCTSTR pszUNC, BOOL bPathIsFile, LPTSTR *ppszConnectionName, PDWORD pdwShareStatus)
  575. {
  576. TCHAR szShare[MAX_PATH];
  577. DWORD dwShareStatus = 0;
  578. *ppszConnectionName = NULL;
  579. // CSCQueryFileStatus can fail for multiple reasons, one of which is that
  580. // there is no database entry and no existing SMB connection to the share.
  581. // To handle the no-connection part, we try to connect to the share and
  582. // retry CSCQueryFileStatus.
  583. //
  584. // However, there may be a non-SMB connnection which the SMB RDR doesn't
  585. // know about, so we have to check for a connection first. If there is a
  586. // non-SMB connection and we connect again, we would end up disconnecting
  587. // the pre-existing connection later, since we think we made the connection.
  588. //
  589. // If there is a non-SMB connection, then caching is not possible.
  590. //
  591. // Note that we can get here without a connection in at least 3 ways:
  592. // 1. When exploring on \\server and the context menu is for \\server\share.
  593. // 2. When checking a link target, which may live on a different server
  594. // than what we're exploring.
  595. // 3. When checking a folder which is a DFS junction (we need to connect
  596. // to the 'child' share).
  597. // Use a deep path to get correct results in DFS scenarios, but strip the
  598. // filename if it's not a directory.
  599. lstrcpyn(szShare, pszUNC, ARRAYSIZE(szShare));
  600. if (bPathIsFile)
  601. {
  602. PathRemoveFileSpec(szShare);
  603. }
  604. // CSCQueryShareStatus is currently unable to return permissions in
  605. // some cases (e.g. DFS), so don't use the permission parameters.
  606. if (!CSCQueryShareStatus(szShare, &dwShareStatus, NULL, NULL, NULL, NULL))
  607. {
  608. if (!ShareIsConnected(szShare) && ConnectShare(szShare, ppszConnectionName))
  609. {
  610. if (!CSCQueryShareStatus(szShare, &dwShareStatus, NULL, NULL, NULL, NULL))
  611. {
  612. dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
  613. // We're going to return FALSE; kill the connection
  614. if (*ppszConnectionName)
  615. {
  616. WNetCancelConnection2(*ppszConnectionName, 0, FALSE);
  617. LocalFreeString(ppszConnectionName);
  618. }
  619. }
  620. }
  621. else
  622. {
  623. dwShareStatus = FLAG_CSC_SHARE_STATUS_NO_CACHING;
  624. }
  625. }
  626. *pdwShareStatus = dwShareStatus;
  627. return !((dwShareStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) == FLAG_CSC_SHARE_STATUS_NO_CACHING);
  628. }
  629. BOOL
  630. IsSameServer(LPCTSTR pszUNC, LPCTSTR pszServer)
  631. {
  632. ULONG nLen;
  633. LPTSTR pszSlash;
  634. pszUNC += 2; // Skip leading backslashes
  635. pszSlash = StrChr(pszUNC, TEXT('\\'));
  636. if (pszSlash)
  637. nLen = (ULONG)(pszSlash - pszUNC);
  638. else
  639. nLen = lstrlen(pszUNC);
  640. return (CSTR_EQUAL == CompareString(LOCALE_SYSTEM_DEFAULT,
  641. NORM_IGNORECASE,
  642. pszUNC,
  643. nLen,
  644. pszServer,
  645. -1));
  646. }
  647. STDMETHODIMP
  648. CCscShellExt::CheckOneFileStatus(LPCTSTR pszItem,
  649. DWORD dwAttr, // SFGAO_* flags
  650. BOOL bShareChecked,
  651. LPDWORD pdwStatus) // CSC_PROP_* flags
  652. {
  653. HRESULT hr = S_OK;
  654. LPTSTR pszConnectionName = NULL;
  655. DWORD dwHintFlags = 0;
  656. TraceEnter(TRACE_SHELLEX, "CCscShellExt::CheckOneFileStatus");
  657. TraceAssert(pszItem && *pszItem);
  658. TraceAssert(pdwStatus);
  659. if (!PathIsUNC(pszItem))
  660. ExitGracefully(hr, HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME), "Not a network path");
  661. // If server is local machine, fail. Don't allow someone to
  662. // cache a local path via a net share.
  663. if (IsSameServer(pszItem, m_szLocalMachine))
  664. ExitGracefully(hr, HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME), "Locally redirected path");
  665. // Check whether the share is cacheable
  666. // To handle DFS correctly, we need to re-check share status for folders,
  667. // since they may be DFS junction points and have different cache settings.
  668. if (!bShareChecked || (dwAttr & SFGAO_FOLDER))
  669. {
  670. DWORD dwShareStatus = 0;
  671. if (!ShareIsCacheable(pszItem, !(dwAttr & SFGAO_FOLDER), &pszConnectionName, &dwShareStatus))
  672. ExitGracefully(hr, E_FAIL, "Share not cacheable");
  673. if (dwShareStatus & FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP)
  674. *pdwStatus |= CSC_PROP_DCON_MODE;
  675. }
  676. // Check the file status
  677. if (!CSCQueryFileStatus(pszItem, NULL, NULL, &dwHintFlags))
  678. {
  679. DWORD dwErr = GetLastError();
  680. if (dwErr != ERROR_FILE_NOT_FOUND)
  681. {
  682. if (NO_ERROR == dwErr)
  683. dwErr = ERROR_GEN_FAILURE;
  684. ExitGracefully(hr, HRESULT_FROM_WIN32(dwErr), "CSCQueryFileStatus failed");
  685. }
  686. }
  687. else
  688. {
  689. if (dwAttr & SFGAO_FOLDER)
  690. {
  691. // CSCQueryFileStatus succeeded, so this folder is in the cache.
  692. // Enable the sync menu.
  693. if (PathIsRoot(pszItem))
  694. {
  695. // Special note for "\\server\share" items: CSCQueryFileStatus
  696. // can succeed even if nothing on the share is cached. Only
  697. // enable CSC_PROP_SYNCABLE if something on this share is cached.
  698. CSCSHARESTATS shareStats;
  699. CSCGETSTATSINFO si = { SSEF_NONE, // No exclusions
  700. SSUF_TOTAL, // Interested in total only.
  701. false, // No access info reqd (faster).
  702. false };
  703. _GetShareStatisticsForUser(pszItem, &si, &shareStats);
  704. if (shareStats.cTotal)
  705. *pdwStatus |= CSC_PROP_SYNCABLE;
  706. }
  707. else
  708. {
  709. *pdwStatus |= CSC_PROP_SYNCABLE;
  710. }
  711. }
  712. const bool bPinSubFolders = CConfig::GetSingleton().AlwaysPinSubFolders();
  713. if (!(*pdwStatus & CSC_PROP_INHERIT_PIN) &&
  714. (!(dwAttr & SFGAO_FOLDER) || bPinSubFolders))
  715. {
  716. TCHAR szParent[MAX_PATH];
  717. DWORD dwParentHints = 0;
  718. // It's a file OR it's a folder and the "AlwaysPinSubFolders"
  719. // policy is set.. Check whether the parent is pinned.
  720. lstrcpyn(szParent, pszItem, ARRAYSIZE(szParent));
  721. PathRemoveFileSpec(szParent);
  722. if (CSCQueryFileStatus(szParent, NULL, NULL, &dwParentHints)
  723. && (dwParentHints & FLAG_CSC_HINT_PIN_USER))
  724. {
  725. *pdwStatus |= CSC_PROP_INHERIT_PIN;
  726. }
  727. }
  728. }
  729. // If it's not pinned, turn off pinned flag
  730. if (0 == (dwHintFlags & FLAG_CSC_HINT_PIN_USER))
  731. *pdwStatus &= ~CSC_PROP_PINNED;
  732. // If it's not admin pinned, turn off admin pinned flag
  733. if (0 == (dwHintFlags & FLAG_CSC_HINT_PIN_ADMIN))
  734. *pdwStatus &= ~CSC_PROP_ADMIN_PINNED;
  735. exit_gracefully:
  736. if (pszConnectionName)
  737. {
  738. WNetCancelConnection2(pszConnectionName, 0, FALSE);
  739. LocalFreeString(&pszConnectionName);
  740. }
  741. TraceLeaveResult(hr);
  742. }
  743. BOOL
  744. _PathIsUNCServer(LPCTSTR pszPath)
  745. {
  746. int i;
  747. if (!pszPath)
  748. return FALSE;
  749. for (i = 0; *pszPath; pszPath++ )
  750. {
  751. if (pszPath[0]==TEXT('\\') && pszPath[1]) // don't count a trailing slash
  752. {
  753. i++;
  754. }
  755. }
  756. return (i == 2);
  757. }
  758. STDMETHODIMP CCscShellExt::CheckFileStatus(IDataObject *pdobj, DWORD *pdwStatus) // CSC_PROP_* flags
  759. {
  760. LPTSTR pszConnectionName = NULL;
  761. UINT i;
  762. BOOL bShareOK = FALSE;
  763. TCHAR szItem[MAX_PATH];
  764. CIDArray ida;
  765. TraceEnter(TRACE_SHELLEX, "CCscShellExt::CheckFileStatus");
  766. TraceAssert(pdobj != NULL);
  767. TraceAssert(IsCSCEnabled());
  768. if (pdwStatus)
  769. *pdwStatus = 0;
  770. // Assume that everything is both user and system pinned. If anything
  771. // is not pinned, clear the appropriate flag and treat the entire
  772. // selection as non-pinned.
  773. DWORD dwStatus = CSC_PROP_PINNED | CSC_PROP_ADMIN_PINNED;
  774. HRESULT hr = ida.Initialize(pdobj);
  775. FailGracefully(hr, "Can't get ID List format from data object");
  776. if (ida.Count() > 1)
  777. dwStatus |= CSC_PROP_MULTISEL;
  778. // Check the parent path
  779. hr = ida.GetFolderPath(szItem, ARRAYSIZE(szItem));
  780. FailGracefully(hr, "No parent path");
  781. if (ida.Count() > 1 && PathIsUNC(szItem) && !_PathIsUNCServer(szItem))
  782. {
  783. DWORD dwShareStatus = 0;
  784. if (!ShareIsCacheable(szItem, FALSE, &pszConnectionName, &dwShareStatus))
  785. ExitGracefully(hr, E_FAIL, "Share not cacheable");
  786. if (dwShareStatus & FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP)
  787. dwStatus |= CSC_PROP_DCON_MODE;
  788. // No need to check share status again inside CheckOneFileStatus
  789. bShareOK = TRUE;
  790. }
  791. // Loop over each selected item
  792. for (i = 0; i < ida.Count(); i++)
  793. {
  794. // Get the attributes
  795. DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_FOLDER;
  796. hr = ida.GetItemPath(i, szItem, ARRAYSIZE(szItem), &dwAttr);
  797. FailGracefully(hr, "Unable to get item attributes");
  798. if (!(dwAttr & SFGAO_FILESYSTEM))
  799. ExitGracefully(hr, E_FAIL, "Not a filesystem object");
  800. // Is it a shortcut?
  801. if (dwAttr & SFGAO_LINK)
  802. {
  803. LPTSTR pszTarget = NULL;
  804. // Check the target
  805. GetLinkTarget(szItem, &pszTarget);
  806. if (pszTarget)
  807. {
  808. hr = CheckOneFileStatus(pszTarget, 0, FALSE, &dwStatus);
  809. LocalFreeString(&pszTarget);
  810. if (SUCCEEDED(hr) && !PathIsUNC(szItem))
  811. {
  812. // The link is local, but the target is remote, so don't
  813. // bother checking status of the link itself. Just go
  814. // with the target status and move on to the next item.
  815. continue;
  816. }
  817. }
  818. }
  819. hr = CheckOneFileStatus(szItem, dwAttr, bShareOK, &dwStatus);
  820. FailGracefully(hr, "File not cacheable");
  821. }
  822. exit_gracefully:
  823. if (pszConnectionName)
  824. {
  825. WNetCancelConnection2(pszConnectionName, 0, FALSE);
  826. LocalFreeString(&pszConnectionName);
  827. }
  828. if (SUCCEEDED(hr) && pdwStatus != NULL)
  829. *pdwStatus = dwStatus;
  830. TraceLeaveResult(hr);
  831. }
  832. //
  833. // Determines if a folder has subfolders.
  834. // Returns:
  835. // S_OK = Has subfolders.
  836. // S_FALSE = No subfolders.
  837. // E_OUTOFMEMORY = Insufficient memory.
  838. //
  839. HRESULT
  840. CCscShellExt::FolderHasSubFolders(
  841. LPCTSTR pszPath,
  842. CscFilenameList *pfnl
  843. )
  844. {
  845. if (NULL == pszPath || TEXT('\0') == *pszPath)
  846. return E_INVALIDARG;
  847. HRESULT hr = S_FALSE;
  848. const TCHAR szWildcard[] = TEXT("*.*");
  849. UINT cchFolder = lstrlen(pszPath) + 1; // +1 for '\\'
  850. LPTSTR pszTemp = (LPTSTR)LocalAlloc(LPTR, (cchFolder + MAX_PATH + 1) * sizeof(TCHAR));
  851. if (NULL != pszTemp)
  852. {
  853. PathCombine(pszTemp, pszPath, szWildcard);
  854. WIN32_FIND_DATA fd;
  855. HANDLE hFind = FindFirstFile(pszTemp, &fd);
  856. if (INVALID_HANDLE_VALUE != hFind)
  857. {
  858. do
  859. {
  860. if ((FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes) && !PathIsDotOrDotDot(fd.cFileName))
  861. {
  862. if (IsHiddenSystem(fd.dwFileAttributes))
  863. {
  864. // This subfolder is "super hidden". Build the full path
  865. // and silently add it to the file list, but don't set the
  866. // result to S_OK (we don't want superhidden subfolders to
  867. // cause prompts).
  868. lstrcpy(&pszTemp[cchFolder], fd.cFileName);
  869. pfnl->AddFile(pszTemp, true);
  870. }
  871. else
  872. hr = S_OK; // don't break, there may be superhidden folders
  873. }
  874. }
  875. while(FindNextFile(hFind, &fd));
  876. FindClose(hFind);
  877. }
  878. else
  879. {
  880. hr = HRESULT_FROM_WIN32(GetLastError());
  881. }
  882. LocalFree(pszTemp);
  883. }
  884. else
  885. {
  886. hr = E_OUTOFMEMORY;
  887. }
  888. return hr;
  889. }
  890. STDMETHODIMP CCscShellExt::BuildFileList(IDataObject *pdobj, HWND hwndOwner,
  891. CscFilenameList *pfnl, LPBOOL pbSubFolders)
  892. {
  893. UINT i;
  894. TCHAR szItem[MAX_PATH];
  895. CIDArray ida;
  896. BOOL bDirectory;
  897. TraceEnter(TRACE_SHELLEX, "CCscShellExt::BuildFileList");
  898. TraceAssert(pdobj != NULL);
  899. TraceAssert(pfnl != NULL);
  900. HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
  901. HRESULT hr = ida.Initialize(pdobj);
  902. FailGracefully(hr, "Can't get ID List format from data object");
  903. // Loop over each selected item
  904. for (i = 0; i < ida.Count(); i++)
  905. {
  906. // Get the attributes
  907. DWORD dwAttr = SFGAO_FILESYSTEM | SFGAO_LINK | SFGAO_FOLDER;
  908. hr = ida.GetItemPath(i, szItem, ARRAYSIZE(szItem), &dwAttr);
  909. FailGracefully(hr, "Unable to get item attributes");
  910. if (!(dwAttr & SFGAO_FILESYSTEM))
  911. continue;
  912. // Is it a shortcut?
  913. if (dwAttr & SFGAO_LINK)
  914. {
  915. LPTSTR pszTarget = NULL;
  916. // Check the target
  917. GetLinkTarget(szItem, &pszTarget);
  918. if (pszTarget)
  919. {
  920. // Add the target to the file list
  921. if (!pfnl->FileExists(pszTarget, false))
  922. pfnl->AddFile(pszTarget, false);
  923. LocalFreeString(&pszTarget);
  924. }
  925. }
  926. bDirectory = (dwAttr & SFGAO_FOLDER);
  927. if (pbSubFolders && bDirectory && !*pbSubFolders)
  928. *pbSubFolders = (S_OK == FolderHasSubFolders(szItem, pfnl));
  929. // Add the item to the file list
  930. pfnl->AddFile(szItem, !!bDirectory);
  931. // If it's an html file, look for a directory of the same name
  932. // and add it to the file list if necessary.
  933. //
  934. // We're supposed to look for a localized version of "Files"
  935. // tacked on to the root name. For example, given "foo.htm" we
  936. // should look for a directory named "foo Files" where the "Files"
  937. // part comes from a list of localized strings provided by Office.
  938. // We don't bother and just look for a directory named "foo".
  939. //
  940. if (!bDirectory && PathIsHTMLFile(szItem))
  941. {
  942. // Truncate the path
  943. LPTSTR pszExtn = PathFindExtension(szItem);
  944. if (pszExtn)
  945. *pszExtn = NULL;
  946. // Check for existence
  947. dwAttr = GetFileAttributes(szItem);
  948. if ((DWORD)-1 != dwAttr && (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
  949. pfnl->AddFile(szItem, true);
  950. }
  951. }
  952. exit_gracefully:
  953. SetCursor(hCur);
  954. TraceLeaveResult(hr);
  955. }
  956. #define _WNET_ENUM_BUFFER_SIZE 4000
  957. BOOL
  958. ShareIsConnected(LPCTSTR pszUNC)
  959. {
  960. HANDLE hEnum;
  961. PVOID pBuffer;
  962. BOOL fShareIsConnected = FALSE;
  963. pBuffer = (PVOID)LocalAlloc(LMEM_FIXED, _WNET_ENUM_BUFFER_SIZE);
  964. if (NULL != pBuffer)
  965. {
  966. //
  967. // Enumerate all connected disk resources
  968. //
  969. if (NO_ERROR == WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, 0, NULL, &hEnum))
  970. {
  971. //
  972. // Look at each connected share. If we find the share we're looking for,
  973. // we know it's connected so we can quit looking.
  974. //
  975. while (!fShareIsConnected)
  976. {
  977. LPNETRESOURCE pnr;
  978. DWORD cEnum = (DWORD)-1;
  979. DWORD dwBufferSize = _WNET_ENUM_BUFFER_SIZE;
  980. if (NO_ERROR != WNetEnumResource(hEnum, &cEnum, pBuffer, &dwBufferSize))
  981. break;
  982. for (pnr = (LPNETRESOURCE)pBuffer; cEnum > 0; cEnum--, pnr++)
  983. {
  984. if (NULL != pnr->lpRemoteName &&
  985. 0 == lstrcmpi(pnr->lpRemoteName, pszUNC))
  986. {
  987. // Found it
  988. fShareIsConnected = TRUE;
  989. break;
  990. }
  991. }
  992. }
  993. WNetCloseEnum(hEnum);
  994. }
  995. LocalFree(pBuffer);
  996. }
  997. return fShareIsConnected;
  998. }
  999. BOOL
  1000. ConnectShare(LPCTSTR pszUNC, LPTSTR *ppszAccessName)
  1001. {
  1002. NETRESOURCE nr;
  1003. DWORD dwResult;
  1004. DWORD dwErr;
  1005. TCHAR szAccessName[MAX_PATH];
  1006. DWORD cchAccessName = ARRAYSIZE(szAccessName);
  1007. TraceEnter(TRACE_SHELLEX, "CCscShellExt::ConnectShare");
  1008. TraceAssert(pszUNC && *pszUNC);
  1009. nr.dwType = RESOURCETYPE_DISK;
  1010. nr.lpLocalName = NULL;
  1011. nr.lpRemoteName = (LPTSTR)pszUNC;
  1012. nr.lpProvider = NULL;
  1013. szAccessName[0] = TEXT('\0');
  1014. dwErr = WNetUseConnection(NULL,
  1015. &nr,
  1016. NULL,
  1017. NULL,
  1018. 0,
  1019. szAccessName,
  1020. &cchAccessName,
  1021. &dwResult);
  1022. Trace((TEXT("Connecting %s (%d)"), pszUNC, dwErr));
  1023. if (ppszAccessName && NOERROR == dwErr)
  1024. {
  1025. LocalAllocString(ppszAccessName, szAccessName);
  1026. }
  1027. TraceLeaveValue(NOERROR == dwErr);
  1028. }
  1029. DWORD WINAPI
  1030. CCscShellExt::_UnpinFilesThread(LPVOID pvThreadData)
  1031. {
  1032. CSC_UNPIN_DATA *pUnpinData = reinterpret_cast<CSC_UNPIN_DATA *>(pvThreadData);
  1033. if (pUnpinData)
  1034. {
  1035. HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
  1036. if (hInstThisDll)
  1037. {
  1038. CscUnpinFileList(pUnpinData->pNamelist,
  1039. (pUnpinData->dwUpdateFlags & CSC_UPDATE_PIN_RECURSE),
  1040. pUnpinData->bOffline,
  1041. NULL, NULL, 0);
  1042. }
  1043. delete pUnpinData->pNamelist;
  1044. LocalFree(pUnpinData);
  1045. if (hInstThisDll)
  1046. {
  1047. FreeLibraryAndExitThread(hInstThisDll, 0);
  1048. }
  1049. }
  1050. return 0;
  1051. }
  1052. INT_PTR CALLBACK
  1053. CCscShellExt::_ConfirmPinDlgProc(HWND hDlg,
  1054. UINT uMsg,
  1055. WPARAM wParam,
  1056. LPARAM lParam)
  1057. {
  1058. INT_PTR bResult = TRUE;
  1059. switch (uMsg)
  1060. {
  1061. case WM_INITDIALOG:
  1062. CheckRadioButton(hDlg, IDC_PIN_NO_RECURSE, IDC_PIN_RECURSE, IDC_PIN_RECURSE);
  1063. break;
  1064. case WM_COMMAND:
  1065. switch (LOWORD(wParam))
  1066. {
  1067. case IDCANCEL:
  1068. EndDialog(hDlg, IDCANCEL);
  1069. break;
  1070. case IDOK:
  1071. // Return IDYES to indicate that the operation should be recursive.
  1072. // Return IDNO to indicate no recursion.
  1073. EndDialog(hDlg, BST_CHECKED == IsDlgButtonChecked(hDlg, IDC_PIN_RECURSE) ? IDYES : IDNO);
  1074. break;
  1075. }
  1076. break;
  1077. default:
  1078. bResult = FALSE; // message not handled
  1079. }
  1080. return bResult;
  1081. }
  1082. //
  1083. // Given an IDataObject ptr representing a selection of files from
  1084. // within the shell, this function determins if pinning of any of
  1085. // the files and directories is disallowed via system policy.
  1086. //
  1087. // Returns: S_OK - All files in data object can be pinned.
  1088. // S_FALSE - At least one file in data object cannot be pinned.
  1089. //
  1090. HRESULT
  1091. CCscShellExt::CanAllFilesBePinned(
  1092. IDataObject *pdtobj
  1093. )
  1094. {
  1095. TraceEnter(TRACE_SHELLEX, "CCscShellExt::CanAllFilesBePinned");
  1096. //
  1097. // Quick check to see if ANY pin restrictions are in place.
  1098. //
  1099. HRESULT hr = m_NoPinList.IsAnyPinDisallowed();
  1100. if (S_OK == hr)
  1101. {
  1102. //
  1103. // Yes, at least one restriction was read from registry.
  1104. //
  1105. CscFilenameList fnl;
  1106. hr = BuildFileList(m_lpdobj,
  1107. GetDesktopWindow(),
  1108. &fnl,
  1109. NULL);
  1110. if (SUCCEEDED(hr))
  1111. {
  1112. //
  1113. // Iterate over all UNC paths in the data object
  1114. // until we either exhaust the list or find one for which
  1115. // pinning is disallowed.
  1116. //
  1117. CscFilenameList::ShareIter si = fnl.CreateShareIterator();
  1118. CscFilenameList::HSHARE hShare;
  1119. while(si.Next(&hShare))
  1120. {
  1121. TCHAR szUncPath[MAX_PATH];
  1122. wnsprintf(szUncPath, ARRAYSIZE(szUncPath), TEXT("%s\\"), fnl.GetShareName(hShare));
  1123. const int cchShare = lstrlen(szUncPath);
  1124. CscFilenameList::FileIter fi = fnl.CreateFileIterator(hShare);
  1125. LPCTSTR pszFile;
  1126. while(NULL != (pszFile = fi.Next()))
  1127. {
  1128. //
  1129. // Assemble the full UNC path string.
  1130. // If the item is a directory, will need to truncate the trailing
  1131. // "\*" characters.
  1132. //
  1133. lstrcpyn(szUncPath + cchShare, pszFile, ARRAYSIZE(szUncPath) - cchShare);
  1134. LPTSTR pszEnd = szUncPath + lstrlen(szUncPath) - 1;
  1135. while(pszEnd > szUncPath && (TEXT('\\') == *pszEnd || TEXT('*') == *pszEnd))
  1136. {
  1137. *pszEnd-- = TEXT('\0');
  1138. }
  1139. if (S_FALSE == m_NoPinList.IsPinAllowed(szUncPath))
  1140. {
  1141. Trace((TEXT("Policy prevents pinning of \"%s\""), szUncPath));
  1142. TraceLeaveResult(S_FALSE);
  1143. }
  1144. }
  1145. }
  1146. }
  1147. }
  1148. TraceLeaveResult(SUCCEEDED(hr) ? S_OK : hr);
  1149. }
  1150. //
  1151. // Support for recursively unpinning a tree with progress updates
  1152. //
  1153. typedef struct _UNPIN_FILES_DATA
  1154. {
  1155. BOOL bSubfolders;
  1156. BOOL bOffline;
  1157. PFN_UNPINPROGRESSPROC pfnProgressCB;
  1158. LPARAM lpContext;
  1159. } UNPIN_FILES_DATA, *PUNPIN_FILES_DATA;
  1160. DWORD WINAPI
  1161. _UnpinCallback(LPCTSTR pszItem,
  1162. ENUM_REASON eReason,
  1163. DWORD /*dwStatus*/,
  1164. DWORD dwHintFlags,
  1165. DWORD dwPinCount,
  1166. LPWIN32_FIND_DATA pFind32,
  1167. LPARAM lpContext)
  1168. {
  1169. PUNPIN_FILES_DATA pufd = reinterpret_cast<PUNPIN_FILES_DATA>(lpContext);
  1170. // Skip folders if we aren't recursing
  1171. if (eReason == ENUM_REASON_FOLDER_BEGIN && !pufd->bSubfolders)
  1172. return CSCPROC_RETURN_SKIP;
  1173. // Update progress
  1174. if (pufd->pfnProgressCB)
  1175. {
  1176. DWORD dwResult = (*pufd->pfnProgressCB)(pszItem, pufd->lpContext);
  1177. if (CSCPROC_RETURN_CONTINUE != dwResult)
  1178. return dwResult;
  1179. }
  1180. // Unpin the item if it's pinned. For folders,
  1181. // do this before recursing.
  1182. if ((eReason == ENUM_REASON_FILE || eReason == ENUM_REASON_FOLDER_BEGIN)
  1183. && (dwHintFlags & FLAG_CSC_HINT_PIN_USER))
  1184. {
  1185. CSCUnpinFile(pszItem,
  1186. FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER,
  1187. NULL,
  1188. NULL,
  1189. &dwHintFlags);
  1190. ShellChangeNotify(pszItem, pFind32, FALSE);
  1191. }
  1192. // Delete items that are no longer pinned. For folders,
  1193. // do this after recursing.
  1194. if (eReason == ENUM_REASON_FILE || eReason == ENUM_REASON_FOLDER_END)
  1195. {
  1196. if (!dwHintFlags && !dwPinCount)
  1197. {
  1198. if (NOERROR == CscDelete(pszItem) && pufd->bOffline)
  1199. {
  1200. // Removing from the cache while in offline mode means
  1201. // it's no longer available, so remove it from view.
  1202. ShellChangeNotify(pszItem,
  1203. pFind32,
  1204. FALSE,
  1205. (pFind32->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? SHCNE_RMDIR : SHCNE_DELETE);
  1206. }
  1207. }
  1208. }
  1209. return CSCPROC_RETURN_CONTINUE;
  1210. }
  1211. DWORD
  1212. _UnpinOneShare(CscFilenameList *pfnl,
  1213. CscFilenameList::HSHARE hShare,
  1214. PUNPIN_FILES_DATA pufd)
  1215. {
  1216. DWORD dwResult = CSCPROC_RETURN_CONTINUE;
  1217. LPCTSTR pszFile;
  1218. LPCTSTR pszShare = pfnl->GetShareName(hShare);
  1219. CscFilenameList::FileIter fi = pfnl->CreateFileIterator(hShare);
  1220. // Iterate over the filenames associated with the share.
  1221. while (pszFile = fi.Next())
  1222. {
  1223. TCHAR szFullPath[MAX_PATH];
  1224. TCHAR szRelativePath[MAX_PATH];
  1225. WIN32_FIND_DATA fd;
  1226. ULONG cchFile = lstrlen(pszFile) + 1; // include NULL
  1227. DWORD dwPinCount = 0;
  1228. DWORD dwHintFlags = 0;
  1229. ZeroMemory(&fd, sizeof(fd));
  1230. fd.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
  1231. // Directories have a trailing "\*"
  1232. if (StrChr(pszFile, TEXT('*')))
  1233. {
  1234. // It's a directory. Trim off the "\*"
  1235. cchFile -= 2;
  1236. fd.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
  1237. // When unpinning at the share level, pszFile points to "*"
  1238. // and cchFile is now zero.
  1239. }
  1240. szRelativePath[0] = TEXT('\0');
  1241. lstrcpyn(szRelativePath, pszFile, (int)min(cchFile, ARRAYSIZE(szRelativePath)));
  1242. // Build the full path
  1243. PathCombine(szFullPath, pszShare, szRelativePath);
  1244. pszFile = PathFindFileName(szFullPath);
  1245. lstrcpyn(fd.cFileName, pszFile ? pszFile : szFullPath, ARRAYSIZE(fd.cFileName));
  1246. // Update progress
  1247. if (pufd->pfnProgressCB)
  1248. {
  1249. dwResult = (*pufd->pfnProgressCB)(szFullPath, pufd->lpContext);
  1250. switch (dwResult)
  1251. {
  1252. case CSCPROC_RETURN_SKIP:
  1253. continue;
  1254. case CSCPROC_RETURN_ABORT:
  1255. break;
  1256. }
  1257. }
  1258. // Unpin it
  1259. CSCUnpinFile(szFullPath,
  1260. FLAG_CSC_HINT_PIN_USER | FLAG_CSC_HINT_PIN_INHERIT_USER,
  1261. NULL,
  1262. &dwPinCount,
  1263. &dwHintFlags);
  1264. ShellChangeNotify(szFullPath, &fd, FALSE);
  1265. // If it's a directory, unpin its contents
  1266. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  1267. {
  1268. _CSCEnumDatabase(szFullPath,
  1269. pufd->bSubfolders,
  1270. _UnpinCallback,
  1271. (LPARAM)pufd);
  1272. }
  1273. // Is it still pinned?
  1274. if (!dwHintFlags && !dwPinCount)
  1275. {
  1276. // Remove it from the cache (folders may still contain children
  1277. // so we expect this to fail sometimes).
  1278. if (NOERROR == CscDelete(szFullPath) && pufd->bOffline)
  1279. {
  1280. // Removing from the cache while in offline mode means
  1281. // it's no longer available, so remove it from view.
  1282. ShellChangeNotify(szFullPath,
  1283. &fd,
  1284. FALSE,
  1285. (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? SHCNE_RMDIR : SHCNE_DELETE);
  1286. }
  1287. }
  1288. }
  1289. return dwResult;
  1290. }
  1291. void
  1292. CscUnpinFileList(CscFilenameList *pfnl,
  1293. BOOL bSubfolders,
  1294. BOOL bOffline,
  1295. LPCTSTR pszShare,
  1296. PFN_UNPINPROGRESSPROC pfnProgressCB,
  1297. LPARAM lpContext)
  1298. {
  1299. UNPIN_FILES_DATA ufd;
  1300. DWORD dwResult = CSCPROC_RETURN_CONTINUE;
  1301. CscFilenameList::HSHARE hShare;
  1302. if (NULL == pfnl || !pfnl->IsValid() || 0 == pfnl->GetFileCount())
  1303. return;
  1304. ufd.bSubfolders = bSubfolders;
  1305. ufd.bOffline = bOffline;
  1306. ufd.pfnProgressCB = pfnProgressCB;
  1307. ufd.lpContext = lpContext;
  1308. if (pszShare) // enumerate this share only
  1309. {
  1310. if (pfnl->GetShareHandle(pszShare, &hShare))
  1311. _UnpinOneShare(pfnl, hShare, &ufd);
  1312. }
  1313. else // enumerate everything in the list
  1314. {
  1315. CscFilenameList::ShareIter si = pfnl->CreateShareIterator();
  1316. while (si.Next(&hShare) && dwResult != CSCPROC_RETURN_ABORT)
  1317. {
  1318. dwResult = _UnpinOneShare(pfnl, hShare, &ufd);
  1319. }
  1320. }
  1321. // Flush the shell notify queue
  1322. ShellChangeNotify(NULL, TRUE);
  1323. }