//+-------------------------------------------------------------------------
//
//  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;
}



