#include "private.h"
#include "chanmgr.h"
#include "chanmgrp.h"
#include "shguidp.h"
#include "resource.h"
#define DECL_CRTFREE
#include <crtfree.h>


#include <mluisupp.h>

#define TF_DUMPTRIGGER              0x80000000

#define PtrDifference(x,y)          ((LPBYTE)(x)-(LPBYTE)(y))

// Invoke Command verb strings
const CHAR c_szOpen[]          = "open";
const CHAR c_szDelete[]        = "delete";
const CHAR c_szProperties[]    = "properties";
const CHAR c_szCopy[]          = "copy";
const CHAR c_szRename[]        = "rename";
const CHAR c_szPaste[]         = "paste";

static TCHAR szNone[40] = {0};
static TCHAR szUnknown[40] = {0};

// For each notification handler CLSID in the registry, send a single CommandId and Cookie to each handler.
void FireSubscriptionEvent(int nCmdID, const SUBSCRIPTIONCOOKIE UNALIGNED *pCookie_ua)
{
    HKEY hkey;
    SUBSCRIPTIONCOOKIE cookie_buf;
    SUBSCRIPTIONCOOKIE *pCookie;

    ASSERT( pCookie_ua );

    if ( ! pCookie_ua )
    {
        TraceMsg(TF_ERROR, "FireSubscriptionEvent() - pCookie_ua is NULL!");
        return;
    }

    //
    // Make an aligned copy of pCookie_ua and set a pointer to it.
    //

    cookie_buf = *pCookie_ua;
    pCookie = &cookie_buf;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WEBCHECK_REGKEY_NOTF, 0, KEY_READ, &hkey) == ERROR_SUCCESS)
    {
        LPOLESTR pszCookie;

        if (SUCCEEDED(StringFromCLSID(*pCookie, &pszCookie)))
        {
            VARIANT varCookie;

            varCookie.vt = VT_BSTR;
            varCookie.bstrVal = SysAllocString(pszCookie);

            if (varCookie.bstrVal)
            {
                for (int i = 0; ; i++)
                {
                    TCHAR szClsid[GUIDSTR_MAX];
                    DWORD cchClsid = ARRAYSIZE(szClsid);
                    DWORD dwType;
                    DWORD dwData;
                    DWORD cbData = sizeof(dwData);

                    int result = RegEnumValue(hkey, i, szClsid, &cchClsid, NULL, &dwType, (LPBYTE)&dwData, &cbData);

                    if (ERROR_NO_MORE_ITEMS == result)
                    {
                        break;
                    }

                    if ((ERROR_SUCCESS == result) && (dwData & nCmdID))
                    {
                        WCHAR wszClsid[GUIDSTR_MAX];
                        CLSID clsid;

                        SHTCharToUnicode(szClsid, wszClsid, ARRAYSIZE(wszClsid));

                        HRESULT hr = CLSIDFromString(wszClsid, &clsid);

                        if (SUCCEEDED(hr))
                        {
                            IOleCommandTarget *pCmdTarget;

                            hr = CoCreateInstance(*(&clsid), NULL, CLSCTX_ALL, IID_IOleCommandTarget, (void **)&pCmdTarget);
                            if (SUCCEEDED(hr))
                            {
                                pCmdTarget->Exec(&CLSID_SubscriptionMgr, nCmdID, 0, &varCookie, NULL);
                                pCmdTarget->Release();
                            }
                        }
                    }
                }

                VariantClear(&varCookie);
            }

            CoTaskMemFree(pszCookie);
        }

        RegCloseKey( hkey );
    }
}

#ifdef UNICODE
HRESULT IExtractIcon_GetIconLocationThunk(IExtractIconW *peiw, UINT uFlags, LPSTR szIconFile, UINT cchMax, int * piIndex, UINT * pwFlags)
{
    HRESULT hr;
    WCHAR *pwszIconFile = new WCHAR[cchMax];

    if (NULL != pwszIconFile)
    {
        hr = peiw->GetIconLocation(uFlags, pwszIconFile, cchMax, piIndex, pwFlags);

        if (SUCCEEDED(hr))
        {
            WideCharToMultiByte(CP_ACP, 0, pwszIconFile, -1, szIconFile, cchMax, NULL, NULL);
        }

        delete [] pwszIconFile;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}

HRESULT IExtractIcon_ExtractThunk(IExtractIconW *peiw, LPCSTR pszFile, UINT nIconIndex, HICON * phiconLarge, HICON * phiconSmall, UINT nIconSize)
{
    HRESULT hr;
    int len = lstrlenA(pszFile) + 1;
    WCHAR *pwszFile = new WCHAR[len];

    if (NULL != pwszFile)
    {
        MultiByteToWideChar(CP_ACP, 0, pszFile, len, pwszFile, len);

        hr = peiw->Extract(pwszFile, nIconIndex, phiconLarge, phiconSmall, nIconSize);

        delete [] pwszFile;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}
#endif

DWORD Random(DWORD nMax)
{
    static DWORD dwSeed = GetTickCount();

    if (nMax)
    {
        return dwSeed = (dwSeed * 214013L + 2531011L) % nMax;
    }
    else
    {
        return 0;
    }
}

void CreateCookie(GUID UNALIGNED *pCookie_ua)
{
    static DWORD dwCount = 0;

    union CUCookie
    {
        GUID guidCookie;
        struct XCookie {
            FILETIME ft;
            DWORD    dwCount;
            DWORD    dwRand;
        } x;
    };

    CUCookie uc;
    GetSystemTimeAsFileTime(&uc.x.ft);
    uc.x.dwCount = dwCount++;
    uc.x.dwRand = Random(0xffffffff);

    *pCookie_ua = uc.guidCookie;
}

void VariantTimeToFileTime(double dt, FILETIME& ft)
{
    SYSTEMTIME st;

    VariantTimeToSystemTime(dt, &st);
    SystemTimeToFileTime(&st, &ft);
}

void FileTimeToVariantTime(FILETIME& ft, double *pdt)
{
    SYSTEMTIME st;

    FileTimeToSystemTime(&ft, &st);
    SystemTimeToVariantTime(&st, pdt);
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// Cache helper functions
//

// Caller should MemFree *lpCacheConfigInfo when done. Should pass *lpCacheConfigInfo
//  into SetCacheSize
HRESULT GetCacheInfo(
    LPINTERNET_CACHE_CONFIG_INFOA *lplpCacheConfigInfo,
    DWORD                        *pdwSizeInKB,
    DWORD                        *pdwPercent)
{
    HRESULT hr = S_OK;
    LPINTERNET_CACHE_CONFIG_INFOA lpCCI = NULL;
    DWORD dwSize;

    dwSize = sizeof(INTERNET_CACHE_CONFIG_INFOA);

    lpCCI = (LPINTERNET_CACHE_CONFIG_INFOA)MemAlloc(LPTR, dwSize);

    if (!lpCCI)
    {
        hr = E_OUTOFMEMORY;
        goto cleanup;
    }

    lpCCI->dwStructSize = sizeof(INTERNET_CACHE_CONFIG_INFOA);

    if (!GetUrlCacheConfigInfoA(lpCCI, &dwSize, CACHE_CONFIG_CONTENT_PATHS_FC))
    {
        hr = E_FAIL; // HRESULT_FROM_WIN32(GetLastError());
        goto cleanup;
    }

    // there should be at least one cache path structure
    if (dwSize < sizeof(INTERNET_CACHE_CONFIG_INFOA) ||
        lpCCI->dwNumCachePaths != 1)
    {
        // something is messed up
        hr = E_FAIL;
        goto cleanup;
    }

    *lplpCacheConfigInfo = lpCCI;
    *pdwSizeInKB = lpCCI->dwQuota;
    *pdwPercent = 10; // good faith estimate

    ASSERT(*pdwSizeInKB);   // Better not be 0...

cleanup:

    if (FAILED(hr))
    {
        SAFELOCALFREE(lpCCI);
    }

    return hr;
}

HRESULT SetCacheSize(
            LPINTERNET_CACHE_CONFIG_INFOA lpCacheConfigInfo,
            DWORD                        dwSizeInKB)
{
//  lpCacheConfigInfo->dwNumCachePaths = 1;
//  lpCacheConfigInfo->CachePaths[0].dwCacheSize = dwSizeInKB;
    lpCacheConfigInfo->dwContainer = 0; // CONTENT;
    lpCacheConfigInfo->dwQuota = dwSizeInKB;

    if (!SetUrlCacheConfigInfoA(lpCacheConfigInfo, CACHE_CONFIG_QUOTA_FC))
    {
        return E_FAIL; // HRESULT_FROM_WIN32(GetLastError());
    }

    return S_OK;
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
// Registry helper functions
//
BOOL ReadRegValue(HKEY hkeyRoot, const TCHAR *pszKey, const TCHAR *pszValue,
                   void *pData, DWORD dwBytes)
{
    long    lResult;
    HKEY    hkey;
    DWORD   dwType;

    lResult = RegOpenKey(hkeyRoot, pszKey, &hkey);
    if (lResult != ERROR_SUCCESS) {
        return FALSE;
    }

    lResult = RegQueryValueEx(hkey, pszValue, NULL, &dwType, (BYTE *)pData,
        &dwBytes);
    RegCloseKey(hkey);

    if (lResult != ERROR_SUCCESS)
        return FALSE;

    if(dwType == REG_SZ) {
        // null terminate string
        ((TCHAR *)pData)[dwBytes] = 0;
    }

    return TRUE;
}

BOOL WriteRegValue(HKEY hkeyRoot, const TCHAR *pszKey, const TCHAR *pszValue,
                    void *pData, DWORD dwBytes, DWORD dwType)
{
    HKEY    hkey;
    long    lResult;
    DWORD   dwStatus;

    lResult = RegCreateKeyEx(hkeyRoot, pszKey, 0, NULL,
            REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hkey, &dwStatus);
    if (lResult != ERROR_SUCCESS) {
        return FALSE;
    }

    lResult = RegSetValueEx(hkey, pszValue, 0, dwType, (BYTE *)pData, dwBytes);
    RegCloseKey(hkey);

    return (lResult == ERROR_SUCCESS) ? TRUE : FALSE;
}

DWORD ReadRegDWORD(HKEY hkeyRoot, const TCHAR *pszKey, const TCHAR *pszValue)
{
    DWORD dwData;
    if (ReadRegValue(hkeyRoot, pszKey, pszValue, &dwData, sizeof(dwData)))
        return dwData;
    else
        return 0;
}

HRESULT CreateShellFolderPath(LPCTSTR pszPath, LPCTSTR pszGUID, BOOL bUICLSID)
{
    if (!PathFileExists(pszPath))
    CreateDirectory(pszPath, NULL);

    // Mark the folder as a system directory
    if (SetFileAttributes(pszPath, FILE_ATTRIBUTE_READONLY))
    {
        TCHAR szDesktopIni[MAX_PATH];
        // Write in the desktop.ini the cache folder class ID
        PathCombine(szDesktopIni, pszPath, TEXT("desktop.ini"));

        // If the desktop.ini already exists, make sure it is writable
        if (PathFileExists(szDesktopIni))
            SetFileAttributes(szDesktopIni, FILE_ATTRIBUTE_NORMAL);

        // (First, flush the cache to make sure the desktop.ini
        // file is really created.)
        WritePrivateProfileString(NULL, NULL, NULL, szDesktopIni);
        WritePrivateProfileString(TEXT(".ShellClassInfo"), bUICLSID ? TEXT("UICLSID") : TEXT("CLSID"), pszGUID, szDesktopIni);
        WritePrivateProfileString(NULL, NULL, NULL, szDesktopIni);

        // Hide the desktop.ini since the shell does not selectively
        // hide it.
        SetFileAttributes(szDesktopIni, FILE_ATTRIBUTE_HIDDEN);

        return NOERROR;
    }
    else
    {
        DebugMsg(DM_TRACE, TEXT("Cannot make %s a system folder"), pszPath);
        return E_FAIL;
    }
}

void CleanupShellFolder(LPCTSTR pszPath)
{
    if (PathFileExists(pszPath))
    {
        TCHAR szDesktopIni[MAX_PATH];

        // make the history a normal folder
        SetFileAttributes(pszPath, FILE_ATTRIBUTE_NORMAL);
        PathCombine(szDesktopIni, pszPath, TEXT("desktop.ini"));

        // If the desktop.ini already exists, make sure it is writable
        if (PathFileExists(szDesktopIni))
        {
            SetFileAttributes(szDesktopIni, FILE_ATTRIBUTE_NORMAL);
            DeleteFile(szDesktopIni);
        }

        // remove the history directory
        RemoveDirectory(pszPath);
    }
}

BOOL GetSubscriptionFolderPath(LPTSTR pszPath)
{
    DWORD dwDummy;
    HKEY hk;

    if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE,
                                        REGSTR_PATH_SUBSCRIPTION,
                                        0, TEXT(""),
                                        REG_OPTION_NON_VOLATILE,
                                        KEY_READ|KEY_WRITE, NULL, &hk, &dwDummy))
    {
        DWORD cbData = MAX_PATH * sizeof(TCHAR);
        if (ERROR_SUCCESS != RegQueryValueEx(hk, REGSTR_VAL_DIRECTORY , NULL, NULL, (LPBYTE)pszPath, &cbData))
        {
            TCHAR szWindows[MAX_PATH];
            GetWindowsDirectory(szWindows, ARRAYSIZE(szWindows));
            PathCombine(pszPath, szWindows, TEXT("Offline Web Pages"));
        }
        RegCloseKey(hk);

        return TRUE;
    }
    return FALSE;
}

HRESULT GetChannelPath(LPCTSTR pszURL, LPTSTR pszPath, int cch,
                       IChannelMgrPriv** ppIChannelMgrPriv)
{
    ASSERT(pszURL);
    ASSERT(pszPath || 0 == cch);
    ASSERT(ppIChannelMgrPriv);

    HRESULT hr;
    BOOL    bCoinit = FALSE;

    hr = CoCreateInstance(CLSID_ChannelMgr, NULL, CLSCTX_INPROC_SERVER,
                          IID_IChannelMgrPriv, (void**)ppIChannelMgrPriv);

    if ((hr == CO_E_NOTINITIALIZED || hr == REGDB_E_IIDNOTREG) &&
        SUCCEEDED(CoInitialize(NULL)))
    {
        bCoinit = TRUE;
        hr = CoCreateInstance(CLSID_ChannelMgr, NULL, CLSCTX_INPROC_SERVER,
                          IID_IChannelMgrPriv, (void**)ppIChannelMgrPriv);
    }

    if (SUCCEEDED(hr))
    {
        ASSERT(*ppIChannelMgrPriv);

        IChannelMgr* pIChannelMgr;

        hr = (*ppIChannelMgrPriv)->QueryInterface(IID_IChannelMgr,
                                                (void**)&pIChannelMgr);

        if (SUCCEEDED(hr))
        {
            ASSERT(pIChannelMgr);

            WCHAR wszURL[INTERNET_MAX_URL_LENGTH];
            MyStrToOleStrN(wszURL, ARRAYSIZE(wszURL), pszURL);

            IEnumChannels* pIEnumChannels;

            hr = pIChannelMgr->EnumChannels(CHANENUM_ALLFOLDERS | CHANENUM_PATH,
                                            wszURL, &pIEnumChannels);

            if (SUCCEEDED(hr))
            {
                ASSERT(pIEnumChannels);

                CHANNELENUMINFO ci;

                if (S_OK == pIEnumChannels->Next(1, &ci, NULL))
                {
                    MyOleStrToStrN(pszPath, cch, ci.pszPath);

                    CoTaskMemFree(ci.pszPath);
                }
                else
                {
                    hr = E_FAIL;
                }

                pIEnumChannels->Release();
            }

            pIChannelMgr->Release();
        }

    }

    if (bCoinit)
        CoUninitialize();

    ASSERT((SUCCEEDED(hr) && *ppIChannelMgrPriv) || FAILED(hr));

    return hr;
}


//  Caller is responsible for calling ILFree on *ppidl.
HRESULT ConvertPathToPidl(LPCTSTR path, LPITEMIDLIST * ppidl)
{
    WCHAR wszPath[MAX_PATH];
    IShellFolder * pDesktopFolder;
    HRESULT hr;

    ASSERT(path && ppidl);
    * ppidl = NULL;

    MyStrToOleStrN(wszPath, MAX_PATH, path);
    hr = SHGetDesktopFolder(&pDesktopFolder);
    if (hr != NOERROR)
        return hr;

    ULONG uChEaten;

    hr = pDesktopFolder->ParseDisplayName(NULL, NULL, wszPath,
                                            &uChEaten, ppidl, NULL);
    SAFERELEASE(pDesktopFolder);

    return hr;
}

LPITEMIDLIST    GetSubscriptionFolderPidl(void)
{
    TCHAR szPath[MAX_PATH];
    static LPITEMIDLIST pidlFolder = NULL;  //  We leak here.

    if (!pidlFolder)  {
        if (!(GetSubscriptionFolderPath(szPath)))
            return NULL;
        if (FAILED(ConvertPathToPidl(szPath, &pidlFolder)))
            return NULL;
        ASSERT(pidlFolder);
    }
    return (LPITEMIDLIST)pidlFolder;
}

STDAPI OfflineFolderRegisterServer(void)
{
    TCHAR szOldSubscriptionPath[MAX_PATH];

    GetWindowsDirectory(szOldSubscriptionPath, ARRAYSIZE(szOldSubscriptionPath));
    PathCombine(szOldSubscriptionPath, szOldSubscriptionPath, TEXT("Subscriptions"));
    CleanupShellFolder(szOldSubscriptionPath);

    TCHAR szPath[MAX_PATH];

    if (!(GetSubscriptionFolderPath(szPath)))
        goto CleanUp;

    // we pass FALSE because history folder uses CLSID
    if (FAILED(CreateShellFolderPath(szPath, TEXT("{F5175861-2688-11d0-9C5E-00AA00A45957}"), FALSE)))
        goto CleanUp;

    return NOERROR;

CleanUp:        // cleanup stuff if any of our reg stuff fails

    return E_FAIL;
}

STDAPI OfflineFolderUnregisterServer(void)
{
    TCHAR szPath[MAX_PATH];

    if (!(GetSubscriptionFolderPath(szPath)))
        goto CleanUp;

    // we pass FALSE because history folder uses CLSID
    CleanupShellFolder(szPath);

    return NOERROR;

CleanUp:        // cleanup stuff if any of our reg stuff fails

    return E_FAIL;
}



HMENU LoadPopupMenu(UINT id, UINT uSubOffset)
{
    HMENU hmParent, hmPopup;

    hmParent = LoadMenu(MLGetHinst(), MAKEINTRESOURCE(id));
    if (!hmParent)
        return NULL;

    hmPopup = GetSubMenu(hmParent, uSubOffset);
    RemoveMenu(hmParent, uSubOffset, MF_BYPOSITION);
    DestroyMenu(hmParent);

    return hmPopup;
}

UINT MergePopupMenu(HMENU *phMenu, UINT idResource, UINT uSubOffset, UINT indexMenu,  UINT idCmdFirst, UINT idCmdLast)
{
    HMENU hmMerge;

    if (*phMenu == NULL)
    {
        *phMenu = CreatePopupMenu();
        if (*phMenu == NULL)
            return 0;

        indexMenu = 0;    // at the bottom
    }

    hmMerge = LoadPopupMenu(idResource, uSubOffset);
    if (!hmMerge)
        return 0;

    idCmdLast = Shell_MergeMenus(*phMenu, hmMerge, indexMenu, idCmdFirst, idCmdLast, MM_ADDSEPARATOR);

    DestroyMenu(hmMerge);
    return idCmdLast;
}

HMENU GetMenuFromID(HMENU hmenu, UINT idm)
{
    MENUITEMINFO mii = { sizeof(mii), MIIM_SUBMENU, 0, 0, 0, NULL, NULL, NULL, 0, NULL, 0 };
    GetMenuItemInfo(hmenu, idm, FALSE, &mii);
    return mii.hSubMenu;
}

UINT MergeMenuHierarchy(HMENU hmenuDst, HMENU hmenuSrc, UINT idcMin, UINT idcMax, BOOL bTop)
{
    UINT idcMaxUsed = idcMin;
    int imi = GetMenuItemCount(hmenuSrc);

    while (--imi >= 0)
    {
        MENUITEMINFO mii = {
                sizeof(MENUITEMINFO),
                MIIM_ID | MIIM_SUBMENU,
                0,/* fType */ 0,/* fState */ 0,/*wId*/ NULL,
                NULL, NULL, 0,
                NULL, 0 };

        if (GetMenuItemInfo(hmenuSrc, imi, TRUE, &mii))
        {
            UINT idcT = Shell_MergeMenus(
                            GetMenuFromID(hmenuDst, mii.wID),
                            mii.hSubMenu, (bTop)?0:1024, idcMin, idcMax,
                            MM_ADDSEPARATOR | MM_SUBMENUSHAVEIDS);
            idcMaxUsed = max(idcMaxUsed, idcT);
        }
    }
    return idcMaxUsed;
}

///////////////////////////////////////////////////////////////////////////////
//
// Helper Fuctions for item.cpp and folder.cpp
//
///////////////////////////////////////////////////////////////////////////////

int _CompareURL(LPMYPIDL pooi1, LPMYPIDL pooi2)
{
    return UrlCompare(URL(&(pooi1->ooe)), URL(&(pooi2->ooe)), TRUE);
}

int _CompareShortName(LPMYPIDL pooi1, LPMYPIDL pooi2)
{
    PCTSTR pszNameLocal1;
    PCTSTR pszNameLocal2;

    LPTSTR szNameUnaligned1 = NAME(&(pooi1->ooe));
    LPTSTR szNameUnaligned2 = NAME(&(pooi2->ooe));

    TSTR_ALIGNED_STACK_COPY( &pszNameLocal1, szNameUnaligned1 );
    TSTR_ALIGNED_STACK_COPY( &pszNameLocal2, szNameUnaligned2 );

    return StrCmp( pszNameLocal1, pszNameLocal2 );
}

int _CompareLastUpdate(LPMYPIDL pooi1, LPMYPIDL pooi2)
{
    if (pooi1->ooe.m_LastUpdated - pooi2->ooe.m_LastUpdated > 0)
        return 1;
    return -1;
}

int _CompareCookie(REFCLSID cookie1, REFCLSID cookie2)
{
    return memcmp(&cookie1, &cookie2, sizeof(CLSID));
}

int _CompareStatus(LPMYPIDL pooi1, LPMYPIDL pooi2)
{
    return StrCmp(STATUS(&(pooi1->ooe)), STATUS(&(pooi2->ooe)));
}

int _CompareIdentities(LPMYPIDL pooi1, LPMYPIDL pooi2)
{
    if (pooi1->ooe.clsidDest != pooi2->ooe.clsidDest)
        return -1;

    if (!IsNativeAgent(pooi1->ooe.clsidDest))
        return _CompareCookie(pooi1->ooe.m_Cookie, pooi2->ooe.m_Cookie);

    return _CompareURL(pooi1, pooi2);
}

BOOL _ValidateIDListArray(UINT cidl, LPCITEMIDLIST *ppidl)
{
    UINT i;

    for (i = 0; i < cidl; i++)
    {
        if (!IS_VALID_MYPIDL(ppidl[i]))
            return FALSE;
    }

    return TRUE;
}

int _LaunchApp(HWND hwnd, LPCTSTR pszPath)
{
    SHELLEXECUTEINFO ei = { 0 };

    ei.cbSize           = sizeof(SHELLEXECUTEINFO);
    ei.hwnd             = hwnd;
    ei.lpFile           = pszPath;
    ei.nShow            = SW_SHOWNORMAL;

    return ShellExecuteEx(&ei);
}

void _GenerateEvent(LONG lEventId, LPITEMIDLIST pidlIn, LPITEMIDLIST pidlNewIn, BOOL bRefresh)
{
    LPITEMIDLIST pidlFolder = GetSubscriptionFolderPidl();
    if (!pidlFolder)
        return;

    LPITEMIDLIST pidl = ILCombine(pidlFolder, pidlIn);
    if (pidl)
    {
        if (pidlNewIn)
        {
            LPITEMIDLIST pidlNew = ILCombine(pidlFolder, pidlNewIn);
            if (pidlNew)
            {
                SHChangeNotify(lEventId, SHCNF_IDLIST, pidl, pidlNew);
                ILFree(pidlNew);
            }
        }
        else
        {
            SHChangeNotify(lEventId, SHCNF_IDLIST, pidl, NULL);
        }
        if (bRefresh)
            SHChangeNotifyHandleEvents();
        ILFree(pidl);
    }
}

BOOL _InitComCtl32()
{
    static BOOL fInitialized = FALSE;

    if (!fInitialized)
    {
        INITCOMMONCONTROLSEX icc;

        icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
        icc.dwICC = ICC_NATIVEFNTCTL_CLASS | ICC_DATE_CLASSES;
        fInitialized = InitCommonControlsEx(&icc);
    }
    return fInitialized;
}

const struct {
    LPCSTR pszVerb;
    UINT idCmd;
} rgcmds[] = {
    { c_szOpen,         RSVIDM_OPEN },
    { c_szCopy,         RSVIDM_COPY },
    { c_szRename,       RSVIDM_RENAME},
    { c_szPaste,        RSVIDM_PASTE},
    { c_szDelete,       RSVIDM_DELETE },
    { c_szProperties,   RSVIDM_PROPERTIES }
};

int _GetCmdID(LPCSTR pszCmd)
{
    if (HIWORD(pszCmd))
    {
        int i;
        for (i = 0; i < ARRAYSIZE(rgcmds); i++)
        {
            if (lstrcmpiA(rgcmds[i].pszVerb, pszCmd) == 0)
            {
                return rgcmds[i].idCmd;
            }
        }

        return -1;  // unknown
    }
    return (int)LOWORD(pszCmd);
}

BOOL CALLBACK _AddOnePropSheetPage(HPROPSHEETPAGE hpage, LPARAM lParam)
{
    PROPSHEETHEADER * ppsh = (PROPSHEETHEADER *) lParam;

    if (ppsh->nPages < MAX_PROP_PAGES)
    {
        ppsh->phpage[ppsh->nPages++] = hpage;
        return TRUE;
    }
    return FALSE;
}

HRESULT _CreatePropSheet(HWND hwnd, POOEBuf pBuf)
{
    ASSERT(pBuf);

    ISubscriptionMgr    * pSub= NULL;
    HRESULT hr = CoInitialize(NULL);
    RETURN_ON_FAILURE(hr);

    hr = CoCreateInstance(CLSID_SubscriptionMgr, NULL, CLSCTX_INPROC_SERVER,
                IID_ISubscriptionMgr, (void **)&pSub);
    CoUninitialize();
    RETURN_ON_FAILURE(hr);
    ASSERT(pSub);

    BSTR bstrURL = NULL;
    hr = CreateBSTRFromTSTR(&bstrURL, pBuf->m_URL);
    if (S_OK == hr)
        hr = pSub->ShowSubscriptionProperties(bstrURL, hwnd);
    SAFERELEASE(pSub);
    SAFEFREEBSTR(bstrURL);
    return hr;
}

//  Note:
//      We return FALSE on illegal DATE data.

BOOL DATE2DateTimeString(CFileTime& ft, LPTSTR pszText)
{
    SYSTEMTIME st;

    if (ft == 0)    {
        if (szUnknown[0] == 0)
            MLLoadString(IDS_UNKNOWN, szUnknown, ARRAYSIZE(szUnknown));

        StrCpy(pszText, szUnknown);
        return FALSE;
    }

    if (!FileTimeToSystemTime(&ft, &st))
    {
        if (szNone[0] == 0)
            MLLoadString(IDS_NONE, szNone, ARRAYSIZE(szNone));

        StrCpy(pszText, szNone);
        return FALSE;
    }
    GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, pszText, 64);
    pszText += lstrlen(pszText);
    *pszText++ = ' ';
    GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, NULL, pszText, 64);
    return TRUE;
}

BOOL Date2LocalDateString(SYSTEMTIME * st, LPTSTR dtStr, int size)
{
    ASSERT(dtStr);

    return GetDateFormat(LOCALE_USER_DEFAULT, 0, st, NULL, dtStr, size);
}

void CopyToOOEBuf(POOEntry pooe, POOEBuf pBuf)
{
    ASSERT(pooe);
    ASSERT(pBuf);

    pBuf->dwFlags           = pooe->dwFlags;
    pBuf->m_LastUpdated     = pooe->m_LastUpdated;
    pBuf->m_NextUpdate      = pooe->m_NextUpdate;
    pBuf->m_SizeLimit       = pooe->m_SizeLimit;
    pBuf->m_ActualSize      = pooe->m_ActualSize;
    pBuf->m_RecurseLevels   = pooe->m_RecurseLevels;
    pBuf->m_RecurseFlags    = pooe->m_RecurseFlags;
    pBuf->m_Priority        = pooe->m_Priority;
    pBuf->bDesktop          = pooe->bDesktop;
    pBuf->bChannel          = pooe->bChannel;
    pBuf->bMail             = pooe->bMail;
    pBuf->bGleam            = pooe->bGleam;
    pBuf->bChangesOnly      = pooe->bChangesOnly;
    pBuf->fChannelFlags     = pooe->fChannelFlags;
    pBuf->bNeedPassword     = pooe->bNeedPassword;
    pBuf->m_Cookie          = pooe->m_Cookie;
    pBuf->groupCookie       = pooe->groupCookie;
    pBuf->grfTaskTrigger    = pooe->grfTaskTrigger;
    pBuf->m_Trigger         = pooe->m_Trigger;
    pBuf->clsidDest         = pooe->clsidDest;
    pBuf->status            = pooe->status;

    StrCpyN(pBuf->m_URL,       URL(pooe),      MAX_URL);
    StrCpyN(pBuf->m_Name,      NAME(pooe),     MAX_NAME);
    StrCpyN(pBuf->username,    UNAME(pooe),    MAX_USERNAME);
    StrCpyN(pBuf->password,    PASSWD(pooe),   MAX_PASSWORD);
    StrCpyN(pBuf->statusStr,   STATUS(pooe),   MAX_STATUS);
}

void CopyToMyPooe(POOEBuf pBuf, POOEntry pooe)
{
    UINT    offset = sizeof(OOEntry);
    UINT    srcLen = lstrlen(pBuf->m_URL) + 1;

    ASSERT(pooe);
    ASSERT(pBuf);

    pooe->dwFlags           = pBuf->dwFlags;
    pooe->m_LastUpdated     = pBuf->m_LastUpdated;
    pooe->m_NextUpdate      = pBuf->m_NextUpdate;
    pooe->m_SizeLimit       = pBuf->m_SizeLimit;
    pooe->m_ActualSize      = pBuf->m_ActualSize;
    pooe->m_RecurseLevels   = pBuf->m_RecurseLevels;
    pooe->m_Priority        = pBuf->m_Priority;
    pooe->m_RecurseFlags    = pBuf->m_RecurseFlags;
    pooe->bDesktop          = pBuf->bDesktop;
    pooe->bChannel          = pBuf->bChannel;
    pooe->bMail             = pBuf->bMail;
    pooe->bGleam            = pBuf->bGleam;
    pooe->bChangesOnly      = pBuf->bChangesOnly;
    pooe->fChannelFlags     = pBuf->fChannelFlags;
    pooe->bNeedPassword     = pBuf->bNeedPassword;
    pooe->m_Cookie          = pBuf->m_Cookie;
    pooe->groupCookie       = pBuf->groupCookie;
    pooe->m_Trigger         = pBuf->m_Trigger;
    pooe->grfTaskTrigger    = pBuf->grfTaskTrigger;
    pooe->clsidDest         = pBuf->clsidDest;
    pooe->status            = pBuf->status;

    pooe->m_URL = (LPTSTR)((LPBYTE)pooe + offset);
    srcLen = lstrlen(pBuf->m_URL) + 1;
    StrCpyN(pooe->m_URL, pBuf->m_URL, srcLen);
    offset += srcLen * sizeof (TCHAR);
    pooe->m_URL = (LPTSTR) PtrDifference(pooe->m_URL, pooe);

    pooe->m_Name = (LPTSTR)((LPBYTE)pooe + offset);
    srcLen = lstrlen(pBuf->m_Name) + 1;
    StrCpyN(pooe->m_Name, pBuf->m_Name, srcLen);
    offset += srcLen * sizeof (TCHAR);
    pooe->m_Name = (LPTSTR) PtrDifference(pooe->m_Name, pooe);

    pooe->username = (LPTSTR)((LPBYTE)pooe + offset);
    srcLen = lstrlen(pBuf->username) + 1;
    StrCpyN(pooe->username, pBuf->username, srcLen);
    offset += srcLen * sizeof (TCHAR);
    pooe->username = (LPTSTR) PtrDifference(pooe->username, pooe);

    pooe->password = (LPTSTR)((LPBYTE)pooe + offset);
    srcLen = lstrlen(pBuf->password) + 1;
    StrCpyN(pooe->password, pBuf->password, srcLen);
    offset += srcLen * sizeof (TCHAR);
    pooe->password = (LPTSTR) PtrDifference(pooe->password, pooe);

    pooe->statusStr = (LPTSTR)((LPBYTE)pooe + offset);
    srcLen = lstrlen(pBuf->statusStr) + 1;
    StrCpyN(pooe->statusStr, pBuf->statusStr, srcLen);
    offset += srcLen * sizeof (TCHAR);
    pooe->statusStr = (LPTSTR) PtrDifference(pooe->statusStr, pooe);

    pooe->dwSize = offset;
}

UINT BufferSize(POOEBuf pBuf)
{
    UINT strLen = 0;
    ASSERT(pBuf);

    strLen += lstrlen(pBuf->m_URL)      + 1;
    strLen += lstrlen(pBuf->m_Name)     + 1;
    strLen += lstrlen(pBuf->username)   + 1;
    strLen += lstrlen(pBuf->password)   + 1;
    strLen += lstrlen(pBuf->statusStr)  + 1;

    return strLen * sizeof(TCHAR);
}


typedef struct
{
    int cItems;
    LPCTSTR pszName;
    LPCTSTR pszUrl;
} DELETE_CONFIRM_INFO;

INT_PTR CALLBACK ConfirmDeleteDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch(message) {

        case WM_INITDIALOG:
        {
            DELETE_CONFIRM_INFO* pInfo = (DELETE_CONFIRM_INFO*)lParam;
            ASSERT (pInfo);
            ASSERT(pInfo->cItems == 1);

            SetListViewToString (GetDlgItem (hDlg, IDC_NAME), pInfo->pszName);
            SetListViewToString (GetDlgItem (hDlg, IDC_LOCATION), pInfo->pszUrl);
        }
        break;

        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDYES:
                case IDNO:
                case IDCANCEL:
                    EndDialog(hDlg, wParam);
                    break;
            }
            break;

        case WM_NOTIFY:
            if (LOWORD(wParam) == IDC_LOCATION)
            {
                NM_LISTVIEW * pnmlv = (NM_LISTVIEW *)lParam;
                ASSERT(pnmlv);
                if (pnmlv->hdr.code == LVN_GETINFOTIP)
                {
                    TCHAR szURL[MAX_URL];
                    LV_ITEM lvi = {0};
                    lvi.mask = LVIF_TEXT;
                    lvi.pszText = szURL;
                    lvi.cchTextMax = ARRAYSIZE(szURL);
                    if (!ListView_GetItem (GetDlgItem (hDlg, IDC_LOCATION), &lvi))
                        return FALSE;

                    NMLVGETINFOTIP  * pTip = (NMLVGETINFOTIP *)pnmlv;
                    ASSERT(pTip->pszText);
                    StrCpyN(pTip->pszText, szURL, pTip->cchTextMax);
                    return TRUE;
                }
            }
        return FALSE;

        default:
            return FALSE;

    } // end of switch

    return TRUE;
}

BOOL ConfirmDelete(HWND hwnd, UINT cItems, LPMYPIDL * ppidl)
{
    ASSERT(ppidl);
    INT_PTR iRet;

    // Check if the user is restricted from deleting URLs.
    // If they're deleting multiple, we'll fail if any can fail.
    UINT i;
    for (i = 0; i < cItems; i++)
    {
        if (ppidl[i]->ooe.bChannel)
        {
            if (SHRestricted2(REST_NoRemovingChannels, URL(&(ppidl[i]->ooe)), 0))
            {
                if (IsWindow(hwnd))
                    SGMessageBox(hwnd, IDS_RESTRICTED, MB_OK);
                return FALSE;
            }
        }

        if (!ppidl[i]->ooe.bDesktop)
        {
            // FEATURE: What about desktop components?
            if (SHRestricted2(REST_NoRemovingSubscriptions, URL(&(ppidl[i]->ooe)), 0))
            {
                if (IsWindow(hwnd))
                    SGMessageBox(hwnd, IDS_RESTRICTED, MB_OK);
                return FALSE;
            }
        }
    }

    if (IsWindow(hwnd)) {
        DELETE_CONFIRM_INFO dci = {0};
        dci.cItems = cItems;
        if (cItems == 1)
        {
            dci.pszName = NAME(&(ppidl[0]->ooe));
            dci.pszUrl = URL(&(ppidl[0]->ooe));
            iRet = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_OBJECTDEL_WARNING),
                        hwnd, ConfirmDeleteDlgProc, (LPARAM)&dci);
        }
        else
        {

            TCHAR szFormat[200];
            //  Enough room for format string and int as string
            TCHAR szBuf[ARRAYSIZE(szFormat) + 11];

            MLLoadString(IDS_DEL_MULTIPLE_FMT, szFormat, ARRAYSIZE(szFormat));
            wnsprintf(szBuf, ARRAYSIZE(szBuf), szFormat, cItems);

            MLLoadString(IDS_DELETE_CAPTION, szFormat, ARRAYSIZE(szFormat));

            MSGBOXPARAMS mbp;

            mbp.cbSize = sizeof(MSGBOXPARAMS);
            mbp.hwndOwner = hwnd;
            mbp.hInstance = MLGetHinst();
            mbp.lpszText = szBuf;
            mbp.lpszCaption = szFormat;
            mbp.dwStyle = MB_YESNO | MB_USERICON;
            mbp.lpszIcon = MAKEINTRESOURCE(IDI_OBJECTDELETED);
            iRet = MessageBoxIndirect(&mbp);
        }
        if (iRet == IDYES)
            return TRUE;
        return FALSE;
    } else  {
        return TRUE;
    }
}

BOOL IsHTTPPrefixed(LPCTSTR szURL)
{
    TCHAR szCanonicalURL[MAX_URL];
    DWORD dwSize = MAX_URL;
    URL_COMPONENTS uc;

    memset(&uc, 0, sizeof(URL_COMPONENTS));
    uc.dwStructSize = sizeof(URL_COMPONENTS);

    // Note:  We explicitly check for and allow the "about:home" URL to pass through here.  This allows
    // the Active Desktop "My Current Home Page" component to specify that URL when creating and managing
    // it's subscription which is consistent with it's use of that form in the browser.
    if (!InternetCanonicalizeUrl(szURL, szCanonicalURL, &dwSize, ICU_DECODE) ||
        !InternetCrackUrl(szCanonicalURL, 0, 0, &uc) ||
        ((INTERNET_SCHEME_HTTP != uc.nScheme) && (INTERNET_SCHEME_HTTPS != uc.nScheme) && (0 != StrCmpI(TEXT("about:home"), szURL))))
    {
        return FALSE;
    }
    return TRUE;
}

//   Checks if global state is offline

BOOL IsGlobalOffline(void)
{
    DWORD   dwState = 0, dwSize = sizeof(DWORD);
    BOOL    fRet = FALSE;
    HANDLE hModuleHandle = LoadLibraryA("wininet.dll");

    if(!hModuleHandle)
        return FALSE;

    if(InternetQueryOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &dwState,
        &dwSize))
    {
        if(dwState & INTERNET_STATE_DISCONNECTED_BY_USER)
            fRet = TRUE;
    }

    return fRet;
}

void SetGlobalOffline(BOOL fOffline)
{
    INTERNET_CONNECTED_INFO ci;

    memset(&ci, 0, sizeof(ci));
    if(fOffline) {
        ci.dwConnectedState = INTERNET_STATE_DISCONNECTED_BY_USER;
        ci.dwFlags = ISO_FORCE_DISCONNECTED;
    } else {
        ci.dwConnectedState = INTERNET_STATE_CONNECTED;
    }

    InternetSetOption(NULL, INTERNET_OPTION_CONNECTED_STATE, &ci, sizeof(ci));
}


//helper function to create one column in a ListView control, add one item to that column,
//size the column to the width of the control, and color the control like a static...
//basically, like SetWindowText for a ListView.  Because we use a lot of ListViews to display
//urls that would otherwise be truncated... the ListView gives us automatic ellipsis and ToolTip.
void SetListViewToString (HWND hLV, LPCTSTR pszString)
{
    ASSERT(hLV);

    LV_COLUMN   lvc = {0};
    RECT lvRect;
    GetClientRect (hLV, &lvRect);
    lvc.mask = LVCF_WIDTH;
    lvc.cx = lvRect.right - lvRect.left;
    if (-1 == ListView_InsertColumn(hLV, 0, &lvc))   {
        ASSERT(0);
    }

    SendMessage(hLV, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_INFOTIP, LVS_EX_INFOTIP);

    LV_ITEM lvi = {0};
    lvi.iSubItem = 0;
    lvi.pszText = (LPTSTR)pszString;
    lvi.mask = LVIF_TEXT;
    ListView_InsertItem(hLV, &lvi);
    ListView_EnsureVisible(hLV, 0, TRUE);

    ListView_SetBkColor(hLV, GetSysColor(COLOR_BTNFACE));
    ListView_SetTextBkColor(hLV, GetSysColor(COLOR_BTNFACE));
}

int WCMessageBox(HWND hwnd, UINT idTextFmt, UINT idCaption, UINT uType, ...)
{
    TCHAR szCaption[256];
    TCHAR szTextFmt[512];
    LPTSTR pszText;
    int result;
    va_list va;

    va_start(va, uType);

    szCaption[0] = 0;

    MLLoadString(idTextFmt, szTextFmt, ARRAYSIZE(szTextFmt));

    if (idCaption <= 0)
    {
        if (NULL != hwnd)
        {
            GetWindowText(hwnd, szCaption, ARRAYSIZE(szCaption));
        }

        //  This handles GetWindowText failure and a NULL hwnd
        if (0 == szCaption[0])
        {
            #if IDS_DEFAULT_MSG_CAPTION < 1
            #error IDS_DEFAULT_MSG_CAPTION is defined incorrectly
            #endif

            idCaption = IDS_DEFAULT_MSG_CAPTION;
        }
    }

    if (idCaption > 0)
    {
        MLLoadString(idCaption, szCaption, ARRAYSIZE(szCaption));
    }

    ASSERT(0 != szCaption[0]);

    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                  szTextFmt, 0, 0, (LPTSTR)&pszText, 0, &va);

    result = MessageBox(hwnd, pszText, szCaption, uType);

    LocalFree(pszText);

    return result;
}


/////////////////////////////////////////////////////////////////////////////
// SGMessageBox
/////////////////////////////////////////////////////////////////////////////
int SGMessageBox
(
    HWND    hwndParent,
    UINT    idStringRes,
    UINT    uType
)
{
    ASSERT(hwndParent != NULL);
    ASSERT(IsWindow(hwndParent));

    TCHAR szError[512];
    if (!MLLoadString(idStringRes, szError, ARRAYSIZE(szError)))
        return 0;

    TCHAR szTitle[128];
    szTitle[0] = 0;

    if (hwndParent != NULL)
        GetWindowText(hwndParent, szTitle, ARRAYSIZE(szTitle));

    return MessageBox(  hwndParent,
                        szError,
                        ((hwndParent != NULL) ? szTitle : NULL),
                        uType);
}

#ifdef DEBUG
/////////////////////////////////////////////////////////////////////////////
// DumpTaskTrigger
/////////////////////////////////////////////////////////////////////////////
void DumpTaskTrigger
(
    TASK_TRIGGER * pTT
)
{
    TraceMsg(TF_DUMPTRIGGER, "----- BEGIN DumpTaskTrigger -----");

    TraceMsg(TF_DUMPTRIGGER, "cbTriggerSize = %d", pTT->cbTriggerSize);
    TraceMsg(TF_DUMPTRIGGER, "Reserved1 = %d", pTT->Reserved1);
    TraceMsg(TF_DUMPTRIGGER, "wBeginYear = %d", pTT->wBeginYear);
    TraceMsg(TF_DUMPTRIGGER, "wBeginMonth = %d", pTT->wBeginMonth);
    TraceMsg(TF_DUMPTRIGGER, "wBeginDay = %d", pTT->wBeginDay);
    TraceMsg(TF_DUMPTRIGGER, "wEndYear = %d", pTT->wEndYear);
    TraceMsg(TF_DUMPTRIGGER, "wEndMonth = %d", pTT->wEndMonth);
    TraceMsg(TF_DUMPTRIGGER, "wEndDay = %d", pTT->wEndDay);
    TraceMsg(TF_DUMPTRIGGER, "wStartHour = %d", pTT->wStartHour);
    TraceMsg(TF_DUMPTRIGGER, "wStartMinute = %d", pTT->wStartMinute);
    TraceMsg(TF_DUMPTRIGGER, "MinutesDuration = %d", pTT->MinutesDuration);
    TraceMsg(TF_DUMPTRIGGER, "MinutesInterval = %d", pTT->MinutesInterval);
    TraceMsg(TF_DUMPTRIGGER, "rgFlags = %d", pTT->rgFlags);
    TraceMsg(TF_DUMPTRIGGER, "Reserved2 = %d", pTT->Reserved2);
    TraceMsg(TF_DUMPTRIGGER, "wRandomMinutesInterval = %d", pTT->wRandomMinutesInterval);

    switch (pTT->TriggerType)
    {
        case TASK_TIME_TRIGGER_DAILY:
        {
            TraceMsg(TF_DUMPTRIGGER, "DAILY");
            TraceMsg(TF_DUMPTRIGGER, "DaysInterval = %d", pTT->Type.Daily.DaysInterval);
            break;
        }

        case TASK_TIME_TRIGGER_WEEKLY:
        {
            TraceMsg(TF_DUMPTRIGGER, "WEEKLY");
            TraceMsg(TF_DUMPTRIGGER, "WeeksInterval = %d", pTT->Type.Weekly.WeeksInterval);
            TraceMsg(TF_DUMPTRIGGER, "rgfDaysOfTheWeek = %d", pTT->Type.Weekly.rgfDaysOfTheWeek);
            break;
        }

        case TASK_TIME_TRIGGER_MONTHLYDATE:
        {
            TraceMsg(TF_DUMPTRIGGER, "MONTHLY DATE");
            TraceMsg(TF_DUMPTRIGGER, "rgfDays = %d", pTT->Type.MonthlyDate.rgfDays);
            TraceMsg(TF_DUMPTRIGGER, "rgfMonths = %d", pTT->Type.MonthlyDate.rgfMonths);
            break;
        }

        case TASK_TIME_TRIGGER_MONTHLYDOW:
        {
            TraceMsg(TF_DUMPTRIGGER, "MONTHLY DOW");
            TraceMsg(TF_DUMPTRIGGER, "wWhichWeek = %d", pTT->Type.MonthlyDOW.wWhichWeek);
            TraceMsg(TF_DUMPTRIGGER, "rgfDaysOfTheWeek = %d", pTT->Type.MonthlyDOW.rgfDaysOfTheWeek);
            TraceMsg(TF_DUMPTRIGGER, "rgfMonths = %d", pTT->Type.MonthlyDOW.rgfMonths);
            break;
        }

        default:
        {
            ASSERT(FALSE);
            break;
        }
    }

    TraceMsg(TF_DUMPTRIGGER, "-----  END DumpTaskTrigger  -----");
}
#endif  // DEBUG
