windows-nt/Source/XPSP1/NT/shell/ext/cscui/enum.cpp
2020-09-26 16:20:57 +08:00

654 lines
18 KiB
C++

//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 1997 - 1999
//
// File: enum.cpp
//
//--------------------------------------------------------------------------
#include "pch.h"
#pragma hdrstop
#include <shlwapip.h> // QITAB, QISearch
#include <shsemip.h> // ILFree(), etc
#include "folder.h"
#include "security.h"
//
// Create a single entry in the server status cache.
//
CServerStatusCache::CEntry::CEntry(
LPCTSTR pszServer,
DWORD dwStatus
) : m_pszServer(StrDup(pszServer)),
m_dwStatus(dwStatus)
{
}
//
// Destroy a single entry in the server status cache.
//
CServerStatusCache::CEntry::~CEntry(
void
)
{
if (NULL != m_pszServer)
{
LocalFree(m_pszServer);
}
}
//
// Destroy the server status cache.
//
CServerStatusCache::~CServerStatusCache(
void
)
{
if (NULL != m_hdpa)
{
//
// Delete each entry in the DPA then destroy the
// DPA itself.
//
int cEntries = DPA_GetPtrCount(m_hdpa);
for (int i = 0; i < cEntries; i++)
{
delete (CEntry *)DPA_GetPtr(m_hdpa, i);
}
DPA_Destroy(m_hdpa);
}
}
//
// Add a share's status to the cache. We strip the UNC path to it's
// bare server name then add the status to the cache. If there's
// no existing entry we just add it. If there is an existing entry,
// we bitwise OR the status bits in with the existing entry. This way
// the status of the server is the summation of the status of all
// it's shares.
//
bool
CServerStatusCache::AddShareStatus(
LPCTSTR pszShare,
DWORD dwShareStatus
)
{
bool bResult = true;
TCHAR szServer[MAX_PATH];
CEntry *pEntry = FindEntry(ServerFromUNC(pszShare, szServer, ARRAYSIZE(szServer)));
if (NULL != pEntry)
{
//
// Found existing server entry for this share. Merge in the
// status bits for this share.
//
pEntry->AddStatus(dwShareStatus);
}
else
{
//
// No existing entry for this share's server.
//
if (NULL == m_hdpa)
{
//
// No DPA exists yet. Create one.
// We delay creation of the DPA until we really need one.
//
m_hdpa = DPA_Create(8);
}
if (NULL != m_hdpa)
{
//
// We have a DPA. Create a new entry for this share's server
// and add it to the DPA.
//
pEntry = new CEntry(szServer, dwShareStatus);
if (NULL != pEntry)
{
if (!pEntry->IsValid() || -1 == DPA_AppendPtr(m_hdpa, pEntry))
{
//
// One of the following happened:
// 1. Failure allocating server name in CEntry obj.
// 2. Failure adding CEntry obj ptr to DPA.
//
delete pEntry;
bResult = false;
}
}
}
else
{
bResult = false; // DPA creation failed.
}
}
return bResult;
}
//
// Obtain the CSC status bits for a given server.
// This function assumes the pszUNC arg is a valid UNC path.
//
DWORD
CServerStatusCache::GetServerStatus(
LPCTSTR pszUNC
)
{
TCHAR szServer[MAX_PATH];
CEntry *pEntry = FindEntry(ServerFromUNC(pszUNC, szServer, ARRAYSIZE(szServer)));
if (NULL == pEntry)
{
//
// No entry for this server. Scan the CSC cache and pick up any new
// servers added. Since the lifetime of this server cache is only for a single
// enumeration, we should have to do this only once. However, if for some
// reason, something gets added to the CSC cache while we're opening the viewer,
// this code path will pick up the new server entry.
//
WIN32_FIND_DATA fd;
DWORD dwStatus = 0;
CCscFindHandle hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL);
if (hFind.IsValid())
{
do
{
AddShareStatus(fd.cFileName, dwStatus);
}
while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL));
}
//
// Now that we have rescanned the CSC cache, try it again.
//
pEntry = FindEntry(szServer);
}
return pEntry ? pEntry->GetStatus() : 0;
}
//
// Find a single entry in the server cache.
// Assumes pszServer is a raw server name (not UNC).
// Returns NULL if no match found.
//
CServerStatusCache::CEntry *
CServerStatusCache::FindEntry(
LPCTSTR pszServer
)
{
CEntry *pEntry = NULL;
if (NULL != m_hdpa)
{
int cEntries = DPA_GetPtrCount(m_hdpa);
for (int i = 0; i < cEntries; i++)
{
CEntry *pe = (CEntry *)DPA_GetPtr(m_hdpa, i);
if (0 == lstrcmpi(pe->GetServer(), pszServer))
{
pEntry = pe;
break;
}
}
}
return pEntry;
}
LPTSTR
CServerStatusCache::ServerFromUNC(
LPCTSTR pszShare,
LPTSTR pszServer,
UINT cchServer
)
{
LPTSTR pszReturn = pszServer; // Remember for return.
cchServer--; // Leave room for terminating nul.
while(*pszShare && TEXT('\\') == *pszShare)
pszShare++;
while(*pszShare && TEXT('\\') != *pszShare && cchServer--)
*pszServer++ = *pszShare++;
*pszServer = TEXT('\0');
return pszReturn;
}
STDMETHODIMP COfflineFilesEnum::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENT(COfflineFilesEnum, IEnumIDList),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_ (ULONG) COfflineFilesEnum::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_ (ULONG) COfflineFilesEnum::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
COfflineFilesEnum::COfflineFilesEnum(DWORD grfFlags, COfflineFilesFolder *pfolder)
{
_cRef = 1;
//
// The minimum size of the buffer must be MAX_PATH.
// The enumeration code is designed to grow it as needed.
//
_cchPathBuf = MAX_PATH;
_pszPath = (LPTSTR)LocalAlloc(LMEM_FIXED, sizeof(TCHAR) * _cchPathBuf);
if (NULL != _pszPath)
*_pszPath = TEXT('\0');
else
_cchPathBuf = 0;
_grfFlags = grfFlags,
_pfolder = pfolder;
_pfolder->AddRef();
_dwServerStatus = 0;
_hdsaFolderPathInfo = DSA_Create(sizeof(FolderPathInfo), 10);
//
// Determine if we should be showing system and/or hidden files.
//
_bShowHiddenFiles = boolify(ShowHidden());
_bShowSuperHiddenFiles = boolify(ShowSuperHidden());
_bUserIsAdmin = boolify(IsCurrentUserAnAdminMember());
DllAddRef();
}
COfflineFilesEnum::~COfflineFilesEnum()
{
if (_pfolder)
_pfolder->Release();
Reset();
if (_hdsaFolderPathInfo)
{
int cPaths = DSA_GetItemCount(_hdsaFolderPathInfo);
FolderPathInfo fpi;
for (int i = 0; i < cPaths; i++)
{
if (DSA_GetItem(_hdsaFolderPathInfo, i, &fpi) && NULL != fpi.pszPath)
LocalFree(fpi.pszPath);
}
DSA_Destroy(_hdsaFolderPathInfo);
}
if (NULL != _pszPath)
LocalFree(_pszPath);
DllRelease();
}
//
// Since we're not throwing exceptions, clients must call this after ctor
// to verify allocations succeeded.
//
bool
COfflineFilesEnum::IsValid(
void
) const
{
return (NULL != _hdsaFolderPathInfo) && (NULL != _pszPath);
}
bool
COfflineFilesEnum::PopFolderPathInfo(
FolderPathInfo *pfpi
)
{
bool bResult = false;
TraceAssert(NULL != _hdsaFolderPathInfo);
int iItem = DSA_GetItemCount(_hdsaFolderPathInfo) - 1;
if ((0 <= iItem) && DSA_GetItem(_hdsaFolderPathInfo, iItem, pfpi))
{
DSA_DeleteItem(_hdsaFolderPathInfo, iItem);
bResult = true;
}
return bResult;
}
//
// Build complete path to folder in a heap allocation and push it onto
// stack of saved folder paths.
// Returns false if memory can't be allocated for path.
//
bool
COfflineFilesEnum::SaveFolderPath(
LPCTSTR pszRoot,
LPCTSTR pszFolder
)
{
bool bResult = false;
FolderPathInfo fpi;
//
// Length is "root" + '\' + "folder" + <nul>
//
fpi.cchPath = lstrlen(pszRoot) + lstrlen(pszFolder) + 2;
fpi.pszPath = (LPTSTR)LocalAlloc(LPTR, MAX(fpi.cchPath, DWORD(MAX_PATH)) * sizeof(TCHAR));
if (NULL != fpi.pszPath)
{
PathCombine(fpi.pszPath, pszRoot, pszFolder);
if (PushFolderPathInfo(fpi))
bResult = true;
else
LocalFree(fpi.pszPath);
}
return bResult;
}
//
// Increases the size of the _pszPath buffer by a specified amount.
// Original contents of buffer ARE NOT preserved.
// Returns:
// S_FALSE - _pszPath buffer was large enough. Not modified.
// S_OK - _pszPath points to new bigger buffer.
// E_OUTOFMEMORY - _pszPath points to original unmodified buffer.
//
HRESULT
COfflineFilesEnum::GrowPathBuffer(
INT cchRequired,
INT cchExtra
)
{
HRESULT hres = S_FALSE;
if (_cchPathBuf <= cchRequired)
{
LPTSTR pszNewBuf = (LPTSTR)LocalAlloc(LMEM_FIXED, sizeof(TCHAR) * (cchRequired + cchExtra));
if (NULL != pszNewBuf)
{
if (NULL != _pszPath)
LocalFree(_pszPath);
_pszPath = pszNewBuf;
_cchPathBuf = cchRequired + cchExtra;
hres = S_OK;
}
else
{
hres = E_OUTOFMEMORY; // Failure. Orig buffer is left intact.
}
}
return hres;
}
//
// Determine if user has access to view this file.
//
bool
COfflineFilesEnum::UserHasAccess(
const CscFindData& cscfd
)
{
return _bUserIsAdmin ||
CscAccessUser(cscfd.dwStatus) ||
CscAccessGuest(cscfd.dwStatus);
}
//
// Centralize any item-exclusion logic in a single function.
//
bool
COfflineFilesEnum::Exclude(
const CscFindData& cscfd
)
{
return ((FILE_ATTRIBUTE_DIRECTORY & cscfd.fd.dwFileAttributes) ||
(FLAG_CSC_COPY_STATUS_LOCALLY_DELETED & cscfd.dwStatus) ||
((FILE_ATTRIBUTE_HIDDEN & cscfd.fd.dwFileAttributes) && !_bShowHiddenFiles) ||
(IsHiddenSystem(cscfd.fd.dwFileAttributes) && !_bShowSuperHiddenFiles) ||
!UserHasAccess(cscfd));
}
//
// If a folder is hidden and the current shell setting says to not show hidden files,
// don't enumerate any children of a folder. Likewise for super hidden files and the
// "show super hidden files" setting.
//
bool
COfflineFilesEnum::OkToEnumFolder(
const CscFindData& cscfd
)
{
return (_bShowHiddenFiles || (0 == (FILE_ATTRIBUTE_HIDDEN & cscfd.fd.dwFileAttributes))) &&
(_bShowSuperHiddenFiles || !IsHiddenSystem(cscfd.fd.dwFileAttributes));
}
HRESULT COfflineFilesEnum::Next(ULONG celt, LPITEMIDLIST *rgelt,
ULONG *pceltFetched)
{
HRESULT hres;
CscFindData cscfd;
ULONG celtEnumed;
//
// If you've hit one of these asserts, you didn't call IsValid()
// before using the enumerator.
//
TraceAssert(NULL != _pszPath);
TraceAssert(NULL != _hdsaFolderPathInfo);
//
// This label is used to restart the enum if an item is excluded.
//
enum_start:
hres = S_FALSE;
celtEnumed = 0;
ZeroMemory(&cscfd, sizeof(cscfd));
if (!_hEnumShares.IsValid())
{
//
// First time through.
// Enumerate shares and files until we find a folder or file.
//
_hEnumShares = CacheFindFirst(NULL, &cscfd);
if (_hEnumShares.IsValid())
{
_dwServerStatus = _ServerStatusCache.GetServerStatus(cscfd.fd.cFileName);
do
{
//
// Buffer attached to _pszPath is guaranteed to be at least
// MAX_PATH so it's safe to copy cFileName[].
//
lstrcpy(_pszPath, cscfd.fd.cFileName);
_hEnum = CacheFindFirst(_pszPath, &cscfd);
if (_hEnum.IsValid())
{
celtEnumed = 1;
}
}
while(0 == celtEnumed && CacheFindNext(_hEnumShares, &cscfd));
}
}
else
{
if (_hEnum.IsValid())
{
if (CacheFindNext(_hEnum, &cscfd))
{
//
// Most common case. Got next file in current folder.
//
celtEnumed = 1;
}
else
{
//
// Enumeration exhausted for this folder. If we have folder paths
// saved on the stack, keep popping them until we find one containing
// at least one file or folder.
//
FolderPathInfo fpi;
while(SUCCEEDED(hres) && 0 == celtEnumed && PopFolderPathInfo(&fpi) && NULL != fpi.pszPath)
{
_hEnum = CacheFindFirst(fpi.pszPath, &cscfd);
if (_hEnum.IsValid())
{
//
// The popped folder path is the only opportunity we have
// where a string could overflow the temp _pszPath buffer.
// If necesary, grow the buffer to hold the path. Add
// room for an extra 100 chars to minimize re-growth.
// Buffer is not altered if required path length is
// less than _cchPathBuf.
//
if (FAILED(GrowPathBuffer(fpi.cchPath, 100)))
hres = E_OUTOFMEMORY;
if (SUCCEEDED(hres))
{
lstrcpy(_pszPath, fpi.pszPath);
celtEnumed = 1;
}
}
LocalFree(fpi.pszPath);
}
if (SUCCEEDED(hres))
{
while(0 == celtEnumed && CacheFindNext(_hEnumShares, &cscfd))
{
//
// No more saved folder paths. This share is exhausted.
// Enumerate next share. If next is empty, keep enumerating
// shares until we find one with content. The buffer
// attached to _pszPath is guaranteed to be at least MAX_PATH
// so it's always safe to copy cFileName[].
//
_dwServerStatus = _ServerStatusCache.GetServerStatus(cscfd.fd.cFileName);
lstrcpy(_pszPath, cscfd.fd.cFileName);
_hEnum = CacheFindFirst(_pszPath, &cscfd);
if (_hEnum.IsValid())
{
celtEnumed = 1;
}
}
}
}
}
}
if (celtEnumed)
{
if (FILE_ATTRIBUTE_DIRECTORY & cscfd.fd.dwFileAttributes)
{
if (OkToEnumFolder(cscfd))
{
//
// Save the folder path on a stack. This is how we enumerate
// the cache item hierarcy as a flat list. We'll pop these off
// the stack on future calls to Next() when all children of the
// current folder have been enumerated.
//
if (!SaveFolderPath(_pszPath, cscfd.fd.cFileName))
{
//
// Path not saved. Insufficient heap memory.
// Abort the enumeration.
//
hres = E_OUTOFMEMORY;
}
}
}
if (SUCCEEDED(hres))
{
if (!Exclude(cscfd))
{
//
// An IDList is composed of a fixed-length part and a variable-length
// path+name buffer.
// The path+name variable-length buffer is formatted as follows:
//
// dir1\dir2\dir3<nul>name<nul>
//
TCHAR szUNC[MAX_PATH];
if (PathCombine(szUNC, _pszPath, cscfd.fd.cFileName))
{
hres = COfflineFilesFolder::OLID_CreateFromUNCPath(szUNC,
&cscfd.fd,
cscfd.dwStatus,
cscfd.dwPinCount,
cscfd.dwHintFlags,
_dwServerStatus,
(LPOLID *)&rgelt[0]);
}
}
else
{
//
// This item is excluded from the enumeration. Restart.
// I normally don't like goto's but doing this with a loop
// is just plain harder to understand. The goto is quite
// appropriate in this circumstance.
//
goto enum_start;
}
}
}
if (pceltFetched)
*pceltFetched = celtEnumed;
return hres;
}
HRESULT COfflineFilesEnum::Skip(ULONG celt)
{
return E_NOTIMPL;
}
HRESULT COfflineFilesEnum::Reset()
{
_hEnum.Close();
_hEnumShares.Close();
return S_OK;
}
HRESULT COfflineFilesEnum::Clone(IEnumIDList **ppenum)
{
return E_NOTIMPL;
}