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.

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