#include "shellprv.h"

#include "intshcut.h"
#include "ids.h"
#include <ntquery.h>    // defines some values used for fmtid and pid
#include <sddl.h>       // For ConvertSidToStringSid()
#include "prop.h"       // SCID_ stuff
#include "netview.h"    // SHWNetGetConnection
#include "clsobj.h"

HRESULT ReadProperty(IPropertySetStorage *ppss, REFFMTID fmtid, PROPID pid, VARIANT *pVar)
{
    VariantInit(pVar);

    IPropertyStorage *pps;
    HRESULT hr = ppss->Open(fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, &pps);
    if (SUCCEEDED(hr))
    {
        PROPSPEC PropSpec;
        PROPVARIANT PropVar = {0};

        PropSpec.ulKind = PRSPEC_PROPID;
        PropSpec.propid = pid;

        hr = SHPropStgReadMultiple( pps, 0, 1, &PropSpec, &PropVar );
        if (SUCCEEDED(hr))
        {
            hr = PropVariantToVariant(&PropVar, pVar);
            PropVariantClear(&PropVar);
        }
        pps->Release();
    }
    return hr;
}

BOOL IsSlowProperty(IPropertySetStorage *ppss, REFFMTID fmtid, PROPID pid)
{
    IPropertyStorage *pps;
    BOOL bRet = FALSE;

    if (SUCCEEDED(ppss->Open(fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, &pps)))
    {
        IQueryPropertyFlags *pqsp;
        if (SUCCEEDED(pps->QueryInterface(IID_PPV_ARG(IQueryPropertyFlags, &pqsp))))
        {
            PROPSPEC PropSpec;
            PROPVARIANT PropVar = {0};

            PropSpec.ulKind = PRSPEC_PROPID;
            PropSpec.propid = pid;

            SHCOLSTATEF csFlags;
            if (SUCCEEDED(pqsp->GetFlags(&PropSpec, &csFlags)))
            {
                bRet = ((csFlags & SHCOLSTATE_SLOW) == SHCOLSTATE_SLOW);
            }

            // If the property isn't part of this property set, IsSlowProperty will return fairlure,
            // which we'll treat as a fast property.

            pqsp->Release();
        }

        pps->Release();
    }
    return bRet;  
}

class CBaseColumnProvider : public IPersist, public IColumnProvider
{
    // IUnknown methods
public:
    STDMETHODIMP QueryInterface(REFIID riid, void ** ppv)
    {
        static const QITAB qit[] = {
            QITABENT(CBaseColumnProvider, IColumnProvider),     // IID_IColumnProvider
            QITABENT(CBaseColumnProvider, IPersist),            // IID_IPersist
            { 0 },
        };
        return QISearch(this, qit, riid, ppv);
    };
    
    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&_cRef);
    };

    STDMETHODIMP_(ULONG) Release()
    {
        if (InterlockedDecrement(&_cRef))
            return _cRef;

        delete this;
        return 0;
    };

    // IPersist
    STDMETHODIMP GetClassID(CLSID *pClassID) { *pClassID = *_pclsid; return S_OK; };

    // IColumnProvider
    STDMETHODIMP Initialize(LPCSHCOLUMNINIT psci)    { return S_OK ; }
    STDMETHODIMP GetColumnInfo(DWORD dwIndex, LPSHCOLUMNINFO psci);

    CBaseColumnProvider(const CLSID *pclsid, const COLUMN_INFO rgColMap[], int iCount, const LPCWSTR rgExts[]) : 
       _cRef(1), _pclsid(pclsid), _rgColumns(rgColMap), _iCount(iCount), _rgExts(rgExts)
    {
        DllAddRef();
    }

protected:
    virtual ~CBaseColumnProvider()
    {
        DllRelease();
    }

    BOOL _IsHandled(LPCWSTR pszExt);
    int _iCount;
    const COLUMN_INFO *_rgColumns;

private:
    long _cRef;
    const CLSID * _pclsid;
    const LPCWSTR *_rgExts;
};

// the index is an arbitrary zero based index used for enumeration

STDMETHODIMP CBaseColumnProvider::GetColumnInfo(DWORD dwIndex, SHCOLUMNINFO *psci)
{
    ZeroMemory(psci, sizeof(*psci));

    if (dwIndex < (UINT) _iCount)
    {
        psci->scid = *_rgColumns[dwIndex].pscid;
        psci->cChars = _rgColumns[dwIndex].cChars;
        psci->vt = _rgColumns[dwIndex].vt;
        psci->fmt = _rgColumns[dwIndex].fmt;
        psci->csFlags = _rgColumns[dwIndex].csFlags;

        TCHAR szTemp[MAX_COLUMN_NAME_LEN];
        LoadString(HINST_THISDLL, _rgColumns[dwIndex].idTitle, szTemp, ARRAYSIZE(szTemp));
        SHTCharToUnicode(szTemp, psci->wszTitle, ARRAYSIZE(psci->wszTitle));      

        return S_OK;
    }
    return S_FALSE;
}

// see if this file type is one we are interested in
BOOL CBaseColumnProvider::_IsHandled(LPCWSTR pszExt)
{
    if (_rgExts)
    {
        for (int i = 0; _rgExts[i]; i++)
        {
            if (0 == StrCmpIW(pszExt, _rgExts[i]))
                return TRUE;
        }
        return FALSE;
    }
    return TRUE;
}

// col handler that works over IPropertySetStorage handlers

const COLUMN_INFO c_rgDocObjColumns[] = 
{
    DEFINE_COL_STR_ENTRY(SCID_Author,           20, IDS_EXCOL_AUTHOR),
    DEFINE_COL_STR_ENTRY(SCID_Title,            20, IDS_EXCOL_TITLE),
    DEFINE_COL_STR_DLG_ENTRY(SCID_Subject,      20, IDS_EXCOL_SUBJECT),
    DEFINE_COL_STR_DLG_ENTRY(SCID_Category,     20, IDS_EXCOL_CATEGORY),
    DEFINE_COL_INT_DLG_ENTRY(SCID_PageCount,    10, IDS_EXCOL_PAGECOUNT),
    DEFINE_COL_STR_ENTRY(SCID_Comment,          30, IDS_EXCOL_COMMENT),
    DEFINE_COL_STR_DLG_ENTRY(SCID_Copyright,    30, IDS_EXCOL_COPYRIGHT),
    DEFINE_COL_STR_ENTRY(SCID_MUSIC_Artist,     15, IDS_EXCOL_ARTIST),
    DEFINE_COL_STR_ENTRY(SCID_MUSIC_Album,      15, IDS_EXCOL_ALBUM),
    DEFINE_COL_STR_ENTRY(SCID_MUSIC_Year,       10, IDS_EXCOL_YEAR),
    DEFINE_COL_INT_ENTRY(SCID_MUSIC_Track,      5,  IDS_EXCOL_TRACK),
    DEFINE_COL_STR_ENTRY(SCID_MUSIC_Genre,      20, IDS_EXCOL_GENRE),
    DEFINE_COL_STR_ENTRY(SCID_AUDIO_Duration,   15, IDS_EXCOL_DURATION),
    DEFINE_COL_STR_ENTRY(SCID_AUDIO_Bitrate,    15, IDS_EXCOL_BITRATE),
    DEFINE_COL_STR_ENTRY(SCID_DRM_Protected,    10, IDS_EXCOL_PROTECTED),
    DEFINE_COL_STR_ENTRY(SCID_CameraModel,      20, IDS_EXCOL_CAMERAMODEL),
    DEFINE_COL_STR_ENTRY(SCID_WhenTaken,        20, IDS_EXCOL_WHENTAKEN),

    DEFINE_COL_STR_ENTRY(SCID_ImageDimensions,  20, IDS_EXCOL_DIMENSIONS),
    DEFINE_COL_INT_HIDDEN_ENTRY(SCID_ImageCX),
    DEFINE_COL_INT_HIDDEN_ENTRY(SCID_ImageCY),

    DEFINE_COL_DATE_HIDDEN_ENTRY(SCID_DocCreated),
};

class CPropStgColumns : public CBaseColumnProvider
{
    STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData);

private:
    // help on initializing base classes: mk:@ivt:vclang/FB/DD/S44B5E.HTM
    CPropStgColumns() : 
       CBaseColumnProvider(&CLSID_DocFileColumnProvider, c_rgDocObjColumns, ARRAYSIZE(c_rgDocObjColumns), NULL)
    {
        ASSERT(_wszLastFile[0] == 0);
        ASSERT(_bSlowPropertiesCached == FALSE);
    };
    
    ~CPropStgColumns()
    {
        _FreeCache();
    }
    
    // for the cache
    VARIANT _rgvCache[ARRAYSIZE(c_rgDocObjColumns)]; // zero'ing allocator will fill with VT_EMPTY
    BOOL _rgbSlow[ARRAYSIZE(c_rgDocObjColumns)]; // Store if each property is "slow".
    WCHAR _wszLastFile[MAX_PATH];
    HRESULT _hrCache;
    BOOL _bSlowPropertiesCached;

#ifdef DEBUG
    int deb_dwTotal, deb_dwMiss;
#endif
    
    void _FreeCache();

    friend HRESULT CDocFileColumns_CreateInstance(IUnknown *punk, REFIID riid, void **ppv);
};

void CPropStgColumns::_FreeCache()
{
    for (int i = 0; i < ARRAYSIZE(_rgvCache); i++)
        VariantClear(&_rgvCache[i]);

    _hrCache = S_OK;
}

STDMETHODIMP CPropStgColumns::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData)
{
    HRESULT hr;

    // VariantCopy requires input to be initialized, and we handle failure case
    VariantInit(pvarData);

    // is this even a property we support?
    for (int iProp = 0; iProp < _iCount; iProp++)
    {
        if (IsEqualSCID(*_rgColumns[iProp].pscid, *pscid))
        {
            goto found;
        }
    }

    // Unknown property
    return S_FALSE;

found:

#ifdef DEBUG
    deb_dwTotal++;
#endif

    // Three cases here:
    // 1) We need to update the cache. Fetch the properties again (and only get fast props if we asked for a fast prop)
    // 2) We've only cached fast properties so far, and we asked for a slow property, so now we need to get slow props.
    // 3) The property we want is cached.

    if ((pscd->dwFlags & SHCDF_UPDATEITEM) || (StrCmpW(_wszLastFile, pscd->wszFile) != 0))
    {
        // 1) Cache is no good - item has been updated, or this is a different file.

        // SHCDF_UPDATEITEM flag is a hint
        // that the file for which we are getting data has changed since the last call.  This flag
        // is only passed once per filename, not once per column per filename so update the entire
        // cache if this flag is set.

        // sanity check our caching.  If the shell thread pool is > 1, we will thrash like mad, and should change this
#ifdef DEBUG
        deb_dwMiss++;
        if ((deb_dwTotal > 3) && (deb_dwTotal / deb_dwMiss <= 3))
            TraceMsg(TF_DEFVIEW, "Column data caching is ineffective (%d misses for %d access)", deb_dwMiss, deb_dwTotal);
#endif
        _FreeCache();

        StrCpyW(_wszLastFile, pscd->wszFile);

        IPropertySetStorage *ppss;
        hr = SHFileSysBindToStorage(pscd->wszFile, pscd->dwFileAttributes, STGM_READ | STGM_SHARE_DENY_WRITE, 0, 
                                    IID_PPV_ARG(IPropertySetStorage, &ppss));

        _hrCache = hr;

        if (SUCCEEDED(hr))
        {
            // Did we ask for a slow property?
            BOOL bSlowProperty = IsSlowProperty(ppss, _rgColumns[iProp].pscid->fmtid, _rgColumns[iProp].pscid->pid);

            hr = E_INVALIDARG; // normally overwritten by hrT below
            for (int i = 0; i < _iCount; i++)
            {
                // For every property, take note if it is "slow"
                _rgbSlow[i] = IsSlowProperty(ppss, _rgColumns[i].pscid->fmtid, _rgColumns[i].pscid->pid);

                // Only retrieve a value right now if we asked for a slow property, or this is not a slow property.
                if (bSlowProperty || (!_rgbSlow[i]))
                {
                    // it would be slightly more efficient, but more code, to set up the propid array to call ReadMultiple
                    HRESULT hrT = ReadProperty(ppss, _rgColumns[i].pscid->fmtid, _rgColumns[i].pscid->pid, &_rgvCache[i]);
                    if (i == iProp)
                    {
                        hr = (SUCCEEDED(hrT) ? VariantCopy(pvarData, &_rgvCache[i]) : hrT);
                    }
                }
            }

            ppss->Release();
            _bSlowPropertiesCached = bSlowProperty;
        }        
    }
    else if (_rgbSlow[iProp] && !_bSlowPropertiesCached)
    {
        // 2) We asked for a slow property, but slow properties haven't been cached yet.

        // Bind to the storage a second time.  This is a perf hit, but should be
        // minor compared to getting slow properties.
        IPropertySetStorage *ppss;
        hr = SHFileSysBindToStorage(pscd->wszFile, pscd->dwFileAttributes, STGM_READ | STGM_SHARE_DENY_WRITE, 0, 
                                    IID_PPV_ARG(IPropertySetStorage, &ppss));

        _hrCache = hr;

        if (SUCCEEDED(hr))
        {
            hr = E_INVALIDARG; // normally overwritten by hrT below
            for (int i = 0; i < _iCount; i++)
            {
                if (_rgbSlow[i]) // If it's slow, get it.
                {
                    ASSERT(_rgvCache[i].vt == VT_EMPTY); // Because we haven't retrieved it yet.

                    HRESULT hrT = ReadProperty(ppss, _rgColumns[i].pscid->fmtid, _rgColumns[i].pscid->pid, &_rgvCache[i]);
                    if (i == iProp)
                    {
                        hr = (SUCCEEDED(hrT) ? VariantCopy(pvarData, &_rgvCache[i]) : hrT);
                    }
                }
            }
            ppss->Release();

            _bSlowPropertiesCached = TRUE;
        }

    }
    else 
    {
        // 3) It's not a slow property, or slow properties are already cached.
        ASSERT(!_rgbSlow[iProp] || _bSlowPropertiesCached);

        hr = S_FALSE;       // assume we don't have it

        if (SUCCEEDED(_hrCache))
        {
            if (_rgvCache[iProp].vt != VT_EMPTY)
            {
                hr = VariantCopy(pvarData, &_rgvCache[iProp]);
            }
        }
    }

    return hr;
}


STDAPI CDocFileColumns_CreateInstance(IUnknown *punk, REFIID riid, void **ppv)
{
    HRESULT hr;
    CPropStgColumns *pdocp = new CPropStgColumns;
    if (pdocp)
    {
        hr = pdocp->QueryInterface(riid, ppv);
        pdocp->Release();
    }
    else
    {
        *ppv = NULL;
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

// Shortcut handler

// W because pidl is always converted to widechar filename
const LPCWSTR c_szURLExtensions[] = {
    L".URL", 
    L".LNK", 
    NULL
};

const COLUMN_INFO c_rgURLColumns[] = 
{
    DEFINE_COL_STR_ENTRY(SCID_Author,           20, IDS_EXCOL_AUTHOR),
    DEFINE_COL_STR_ENTRY(SCID_Title,            20, IDS_EXCOL_TITLE),
    DEFINE_COL_STR_ENTRY(SCID_Comment,          30, IDS_EXCOL_COMMENT),
};

class CLinkColumnProvider : public CBaseColumnProvider
{
    STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData);

private:
    // help on initializing base classes: mk:@ivt:vclang/FB/DD/S44B5E.HTM
    CLinkColumnProvider() : CBaseColumnProvider(&CLSID_LinkColumnProvider, c_rgURLColumns, ARRAYSIZE(c_rgURLColumns), c_szURLExtensions)
    {};

    // friends
    friend HRESULT CLinkColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv);
};

const struct 
{
    DWORD dwSummaryPid;
    DWORD dwURLPid;
} c_URLMap[] =  {
    { PIDSI_AUTHOR,   PID_INTSITE_AUTHOR },
    { PIDSI_TITLE,    PID_INTSITE_TITLE },
    { PIDSI_COMMENTS, PID_INTSITE_COMMENT },
};

DWORD _MapSummaryToSitePID(DWORD pid)
{
    for (int i = 0; i < ARRAYSIZE(c_URLMap); i++)
    {
        if (c_URLMap[i].dwSummaryPid == pid)
            return c_URLMap[i].dwURLPid;
    }
    return -1;
}

STDMETHODIMP CLinkColumnProvider::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData)
{
    HRESULT hr;
    USES_CONVERSION;
    const CLSID *pclsidLink = &CLSID_ShellLink;

    // Some of the code-paths below assume pvarData is initialized
    VariantInit(pvarData);

    // should we match against a list of known extensions, or always try to open?

    if (FILE_ATTRIBUTE_DIRECTORY & pscd->dwFileAttributes)
    {
        if (PathIsShortcut(W2CT(pscd->wszFile), pscd->dwFileAttributes))
        {
            pclsidLink = &CLSID_FolderShortcut;     // we are dealing with a folder shortcut now
        }
        else
        {
            return S_FALSE;
        }
    }
    else
    {
        if (!_IsHandled(pscd->pwszExt))
        {
            return S_FALSE;
        }
    }

    if (StrCmpIW(pscd->pwszExt, L".URL") == 0)
    {
        //
        // its a .URL so lets handle it by creating the Internet Shortcut object, loading
        // the file and then reading the properties from it.
        //
        IPropertySetStorage *ppss;
        hr = LoadFromFile(CLSID_InternetShortcut, W2CT(pscd->wszFile), IID_PPV_ARG(IPropertySetStorage, &ppss));
        if (SUCCEEDED(hr))
        {
            UINT pid;
            GUID fmtid;

            if (IsEqualGUID(pscid->fmtid, FMTID_SummaryInformation))
            {
                fmtid = FMTID_InternetSite;
                pid = _MapSummaryToSitePID(pscid->pid);
            }
            else
            {
                fmtid = pscid->fmtid;
                pid = pscid->pid;
            }

            hr = ReadProperty(ppss, fmtid, pid, pvarData);
            ppss->Release();
        }
    }
    else
    {
        //
        // open the .LNK file, load it and then read the description for it.  we then
        // return this a the comment for this object.
        //

        if (IsEqualSCID(*pscid, SCID_Comment))
        {
            IShellLink *psl;
            hr = LoadFromFile(*pclsidLink, W2CT(pscd->wszFile), IID_PPV_ARG(IShellLink, &psl));
            if (SUCCEEDED(hr))
            {
                TCHAR szBuffer[MAX_PATH];

                hr = psl->GetDescription(szBuffer, ARRAYSIZE(szBuffer));            
                if (SUCCEEDED(hr) && szBuffer[0])
                {
                    hr = InitVariantFromStr(pvarData, szBuffer);
                }
                else
                {
                    IQueryInfo *pqi;
                    if (SUCCEEDED(psl->QueryInterface(IID_PPV_ARG(IQueryInfo, &pqi))))
                    {
                        WCHAR *pwszTip;

                        if (SUCCEEDED(pqi->GetInfoTip(0, &pwszTip)) && pwszTip)
                        {
                            hr = InitVariantFromStr(pvarData, W2CT(pwszTip));
                            SHFree(pwszTip);
                        }
                        pqi->Release();
                    }
                }

                psl->Release();
            }
        }
        else
            hr = S_FALSE;
    }

    return hr;
}

STDAPI CLinkColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv)
{
    HRESULT hr;
    CLinkColumnProvider *pdocp = new CLinkColumnProvider;
    if (pdocp)
    {
        hr = pdocp->QueryInterface(riid, ppv);
        pdocp->Release();
    }
    else
    {
        *ppv = NULL;
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

const COLUMN_INFO c_rgFileSysColumns[] = 
{
    DEFINE_COL_STR_ENTRY(SCID_OWNER,            20, IDS_EXCOL_OWNER),
};

class COwnerColumnProvider : public CBaseColumnProvider
{
    STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData);

private:
    COwnerColumnProvider() : CBaseColumnProvider(&CLSID_FileSysColumnProvider, c_rgFileSysColumns, ARRAYSIZE(c_rgFileSysColumns), NULL)
    {
        ASSERT(_wszLastFile[0] == 0);
        ASSERT(_psid==NULL && _pwszName==NULL && _psd==NULL);
        LoadString(HINST_THISDLL, IDS_BUILTIN_DOMAIN, _szBuiltin, ARRAYSIZE(_szBuiltin));
    };

    ~COwnerColumnProvider() { _CacheSidName(NULL, NULL, NULL); }

    WCHAR _wszLastFile[MAX_PATH];

    //  Since we typically get pinged for files all in the same folder,
    //  cache the "folder to server" mapping to avoid calling
    //  WNetGetConnection five million times.
    //
    //  Since files in the same directory tend to have the same owner,
    //  we cache the SID/Name mapping.
    //
    //  Column providers do not have to support multithreaded clients,
    //  so we won't take any critical sections.
    //

    HRESULT _LookupOwnerName(LPCTSTR pszFile, VARIANT *pvar);
    void _CacheSidName(PSECURITY_DESCRIPTOR psd, void *psid, LPCWSTR pwszName);

    void                *_psid;
    LPWSTR               _pwszName;
    PSECURITY_DESCRIPTOR _psd;          // _psid points into here

    int                  _iCachedDrive; // What drive letter is cached in _pszServer?
    LPTSTR               _pszServer;    // What server to use (NULL = local machine)
    TCHAR                _szBuiltin[MAX_COMPUTERNAME_LENGTH + 1];

    friend HRESULT CFileSysColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv);
};

//
//  _CacheSidName takes ownership of the psd.  (psid points into the psd)
//
void COwnerColumnProvider::_CacheSidName(PSECURITY_DESCRIPTOR psd, void *psid, LPCWSTR pwszName)
{
    LocalFree(_psd);
    _psd = psd;
    _psid = psid;

    Str_SetPtrW(&_pwszName, pwszName);
}

//
//  Given a string of the form \\server\share\blah\blah, stomps the
//  inner backslash (if necessary) and returns a pointer to "server".
//
STDAPI_(LPTSTR) PathExtractServer(LPTSTR pszUNC)
{
    if (PathIsUNC(pszUNC))
    {
        pszUNC += 2;            // Skip over the two leading backslashes
        LPTSTR pszEnd = StrChr(pszUNC, TEXT('\\'));
        if (pszEnd) 
            *pszEnd = TEXT('\0'); // nuke the backslash
    }
    else
    {
        pszUNC = NULL;
    }
    return pszUNC;
}

HRESULT COwnerColumnProvider::_LookupOwnerName(LPCTSTR pszFile, VARIANT *pvar)
{
    pvar->vt = VT_BSTR;
    pvar->bstrVal = NULL;

    PSECURITY_DESCRIPTOR psd;
    void *psid;

    DWORD err = GetNamedSecurityInfo(const_cast<LPTSTR>(pszFile),
                               SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION,
                               &psid, NULL, NULL, NULL, &psd);
    if (err == ERROR_SUCCESS)
    {
        if (_psid && EqualSid(psid, _psid) && _pwszName)
        {
            pvar->bstrVal = SysAllocString(_pwszName);
            LocalFree(psd);
            err = ERROR_SUCCESS;
        }
        else
        {
            LPTSTR pszServer;
            TCHAR szServer[MAX_PATH];

            //
            //  Now go figure out which server to resolve the SID against.
            //
            if (PathIsUNC(pszFile))
            {
                lstrcpyn(szServer, pszFile, ARRAYSIZE(szServer));
                pszServer = PathExtractServer(szServer);
            }
            else if (pszFile[0] == _iCachedDrive)
            {
                // Local drive letter already in cache -- use it
                pszServer = _pszServer;
            }
            else
            {
                // Local drive not cached -- cache it
                _iCachedDrive = pszFile[0];
                DWORD cch = ARRAYSIZE(szServer);
                if (SHWNetGetConnection(pszFile, szServer, &cch) == NO_ERROR)
                    pszServer = PathExtractServer(szServer);
                else
                    pszServer = NULL;
                Str_SetPtr(&_pszServer, pszServer);
            }

            TCHAR szName[MAX_PATH];
            DWORD cchName = ARRAYSIZE(szName);
            TCHAR szDomain[MAX_COMPUTERNAME_LENGTH + 1];
            DWORD cchDomain = ARRAYSIZE(szDomain);
            SID_NAME_USE snu;
            LPTSTR pszName;
            BOOL fFreeName = FALSE; // Do we need to LocalFree(pszName)?

            if (LookupAccountSid(pszServer, psid, szName, &cchName,
                                 szDomain, &cchDomain, &snu))
            {
                //
                //  If the domain is the bogus "BUILTIN" or we don't have a domain
                //  at all, then just use the name.  Otherwise, use domain\userid.
                //
                if (!szDomain[0] || StrCmpC(szDomain, _szBuiltin) == 0)
                {
                    pszName = szName;
                }
                else
                {
                    // Borrow szServer as a scratch buffer
                    wnsprintf(szServer, ARRAYSIZE(szServer), TEXT("%s\\%s"), szDomain, szName);
                    pszName = szServer;
                }
                err = ERROR_SUCCESS;
            }
            else
            {
                err = GetLastError();

                // Couldn't map the SID to a name.  Use the horrid raw version
                // if available.
                if (ConvertSidToStringSid(psid, &pszName))
                {
                    fFreeName = TRUE;
                    err = ERROR_SUCCESS;
                }
                else
                    pszName = NULL;
            }

            // Even on error, cache the result so we don't keep trying over and over
            // on the same SID.

            _CacheSidName(psd, psid, pszName);
            pvar->bstrVal = SysAllocString(pszName);

            if (fFreeName)
                LocalFree(pszName);
        }
    }

    if (err == ERROR_SUCCESS && pvar->bstrVal == NULL)
        err = ERROR_OUTOFMEMORY;

    return HRESULT_FROM_WIN32(err);
}

STDMETHODIMP COwnerColumnProvider::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData)
{
    HRESULT hr = S_FALSE;   // return S_FALSE on failure
    VariantInit(pvarData);

    if (IsEqualSCID(SCID_OWNER, *pscid))
    {
        hr = _LookupOwnerName(pscd->wszFile, pvarData);
    }

    return hr;
}

STDAPI CFileSysColumnProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv)
{
    HRESULT hr;
    COwnerColumnProvider *pfcp = new COwnerColumnProvider;
    if (pfcp)
    {
        hr = pfcp->QueryInterface(riid, ppv);
        pfcp->Release();
    }
    else
    {
        *ppv = NULL;
        hr = E_OUTOFMEMORY;
    }
    return hr;
}

STDAPI SHReadProperty(IShellFolder *psf, LPCITEMIDLIST pidl, REFFMTID fmtid, PROPID pid, VARIANT *pvar)
{
    IPropertySetStorage *ppss;
    HRESULT hr = psf->BindToStorage(pidl, NULL, IID_PPV_ARG(IPropertySetStorage, &ppss));
    if (SUCCEEDED(hr))
    {
        hr = ReadProperty(ppss, fmtid, pid, pvar);
        ppss->Release();
    }
    return hr;
}

// 66742402-F9B9-11D1-A202-0000F81FEDEE
// const CLSID CLSID_VersionColProvider = {0x66742402,0xF9B9,0x11D1,0xA2,0x02,0x00,0x00,0xF8,0x1F,0xED,0xEE};

//  FMTID_ExeDllInformation,
//// {0CEF7D53-FA64-11d1-A203-0000F81FEDEE}
#define PSFMTID_VERSION { 0xcef7d53, 0xfa64, 0x11d1, 0xa2, 0x3, 0x0, 0x0, 0xf8, 0x1f, 0xed, 0xee }

#define PIDVSI_FileDescription   0x003
#define PIDVSI_FileVersion       0x004
#define PIDVSI_InternalName      0x005
#define PIDVSI_OriginalFileName  0x006
#define PIDVSI_ProductName       0x007
#define PIDVSI_ProductVersion    0x008

//  Win32 PE (exe, dll) Version Information column identifier defs...
DEFINE_SCID(SCID_FileDescription,   PSFMTID_VERSION, PIDVSI_FileDescription);
DEFINE_SCID(SCID_FileVersion,       PSFMTID_VERSION, PIDVSI_FileVersion);
DEFINE_SCID(SCID_InternalName,      PSFMTID_VERSION, PIDVSI_InternalName);
DEFINE_SCID(SCID_OriginalFileName,  PSFMTID_VERSION, PIDVSI_OriginalFileName);
DEFINE_SCID(SCID_ProductName,       PSFMTID_VERSION, PIDVSI_ProductName);
DEFINE_SCID(SCID_ProductVersion,    PSFMTID_VERSION, PIDVSI_ProductVersion);

const COLUMN_INFO c_rgExeDllColumns[] =
{
    DEFINE_COL_STR_ENTRY(SCID_CompanyName,        30, IDS_VN_COMPANYNAME),
    DEFINE_COL_STR_ENTRY(SCID_FileDescription,    30, IDS_VN_FILEDESCRIPTION),
    DEFINE_COL_STR_ENTRY(SCID_FileVersion,        20, IDS_VN_FILEVERSION),
    DEFINE_COL_STR_MENU_ENTRY(SCID_ProductName,   30, IDS_VN_PRODUCTNAME),
    DEFINE_COL_STR_MENU_ENTRY(SCID_ProductVersion,20, IDS_VN_PRODUCTVERSION),
};


class CVersionColProvider : public CBaseColumnProvider
{
    STDMETHODIMP GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData);

private:
    CVersionColProvider() : 
       CBaseColumnProvider(&CLSID_VersionColProvider, c_rgExeDllColumns, ARRAYSIZE(c_rgExeDllColumns), NULL)
    {
        _pvAllTheInfo = NULL;
        _szFileCache[0] = 0;
    };

    virtual ~CVersionColProvider() 
    {
        _ClearCache();
    }

    FARPROC _GetVerProc(LPCSTR pszName);
    HRESULT _CacheFileVerInfo(LPCWSTR pszFile);
    void _ClearCache();

    WCHAR _szFileCache[MAX_PATH];
    void  *_pvAllTheInfo;
    HRESULT _hrCache;

    friend HRESULT CVerColProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv);
};

void CVersionColProvider::_ClearCache()
{
    if (_pvAllTheInfo)
    {
        delete _pvAllTheInfo;
        _pvAllTheInfo = NULL;
    }
    _szFileCache[0] = 0;
}

HRESULT CVersionColProvider::_CacheFileVerInfo(LPCWSTR pszFile)
{
    if (StrCmpW(_szFileCache, pszFile))
    {
        HRESULT hr;
        _ClearCache();

        DWORD dwVestigial;
        DWORD versionISize = GetFileVersionInfoSizeW((LPWSTR)pszFile, &dwVestigial); // cast for bad API design
        if (versionISize)
        {
            _pvAllTheInfo = new BYTE[versionISize];
            if (_pvAllTheInfo)
            {
                // read the data
                if (GetFileVersionInfoW((LPWSTR)pszFile, dwVestigial, versionISize, _pvAllTheInfo))
                {
                    hr = S_OK;
                }
                else
                {
                    _ClearCache();
                    hr = E_FAIL;
                }
            }
            else
                hr = E_OUTOFMEMORY; // error, out of memory.
        }
        else
            hr = S_FALSE;

        StrCpyNW(_szFileCache, pszFile, ARRAYSIZE(_szFileCache));
        _hrCache = hr;
    }
    return _hrCache;
}

STDMETHODIMP CVersionColProvider::GetItemData(LPCSHCOLUMNID pscid, LPCSHCOLUMNDATA pscd, VARIANT *pvarData)
{
    VariantInit(pvarData);

    if (pscd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        return S_FALSE;

    HRESULT hr = _CacheFileVerInfo(pscd->wszFile);
    if (hr != S_OK)
        return hr;

    TCHAR szString[128], *pszVersionInfo = NULL; //A pointer to the specific version info I am looking for
    LPCTSTR pszVersionField = NULL;

    switch (pscid->pid)
    {
    case PIDVSI_FileVersion:
        {
            VS_FIXEDFILEINFO *pffi;
            UINT uInfoSize;
            if (VerQueryValue(_pvAllTheInfo, TEXT("\\"), (void **)&pffi, &uInfoSize))
            {
                wnsprintf(szString, ARRAYSIZE(szString), TEXT("%d.%d.%d.%d"), 
                    HIWORD(pffi->dwFileVersionMS),
                    LOWORD(pffi->dwFileVersionMS),
                    HIWORD(pffi->dwFileVersionLS),
                    LOWORD(pffi->dwFileVersionLS));

                pszVersionInfo = szString;
            }
            else
                pszVersionField = TEXT("FileVersion");      
        }
        break;

    case PIDDSI_COMPANY:            pszVersionField = TEXT("CompanyName");      break;
    case PIDVSI_FileDescription:    pszVersionField = TEXT("FileDescription");  break;
    case PIDVSI_InternalName:       pszVersionField = TEXT("InternalName");     break;
    case PIDVSI_OriginalFileName:   pszVersionField = TEXT("OriginalFileName"); break;
    case PIDVSI_ProductName:        pszVersionField = TEXT("ProductName");      break;
    case PIDVSI_ProductVersion:     pszVersionField = TEXT("ProductVersion");   break;
    default: 
        return E_FAIL;
    }
    //look for the intended language in the examined object.

    if (pszVersionInfo == NULL)
    {
        struct _VERXLATE
        {
            WORD wLanguage;
            WORD wCodePage;
        } *pxlate;                     /* ptr to translations data */

        //this is a fallthrough set of if statements.
        //on a failure, it just tries the next one, until it runs out of tries.
        UINT uInfoSize;
        if (VerQueryValue(_pvAllTheInfo, TEXT("\\VarFileInfo\\Translation"), (void **)&pxlate, &uInfoSize))
        {
            TCHAR szVersionKey[60];   //a string to hold all the format string for VerQueryValue
            wnsprintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\%04X%04X\\%s"),
                                                pxlate[0].wLanguage, pxlate[0].wCodePage, pszVersionField);
            if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize))
            {
                wnsprintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\040904B0\\%s"), pszVersionField);
                if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize))
                {
                    wnsprintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\040904E4\\%s"), pszVersionField);
                    if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize))
                    {
                        wnsprintf(szVersionKey, ARRAYSIZE(szVersionKey), TEXT("\\StringFileInfo\\04090000\\%s"), pszVersionField);
                        if (!VerQueryValue(_pvAllTheInfo, szVersionKey, (void **) &pszVersionInfo, &uInfoSize))
                        {
                            pszVersionInfo = NULL;
                        }
                    }
                }
            }
        }
    }
    
    if (pszVersionInfo)
    {
        PathRemoveBlanks(pszVersionInfo);
        hr = InitVariantFromStr(pvarData, pszVersionInfo);
    }
    else
    {
        hr = E_FAIL;
    }

    return hr;
}

STDAPI CVerColProvider_CreateInstance(IUnknown *punk, REFIID riid, void **ppv)
{
    HRESULT hr;
    CVersionColProvider *pvcp = new CVersionColProvider;
    if (pvcp)
    {
        hr = pvcp->QueryInterface(riid, ppv);
        pvcp->Release();
    }
    else
    {
        *ppv = NULL;
        hr = E_OUTOFMEMORY;
    }

    return hr;
}