#include "private.h"
#include <exdisp.h>
#include <exdispid.h>
#include <htiface.h>
#include <mshtmdid.h>
#include <mshtmcid.h>
#include <mshtmhst.h>
#include <optary.h>                 // needed for IHtmlLoadOptions

#include "downld.h"

#define TF_THISMODULE   TF_DOWNLD

// CUrlDownload is a single threaded object. We can assume we are always on a single thread.

long g_lRegisteredWnd = 0;
LRESULT UrlDownloadWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

CLIPFORMAT g_cfHTML=CF_NULL;

// User-Agent strings
const WCHAR c_wszUserAgentAppend[] = L"; MSIECrawler)";

// Refresh header for http-equiv (client-pull)
const WCHAR c_wszRefresh[] = L"Refresh";

const int  MAX_CLIENT_PULL_NUM = 4;     // max # redirections
const int  MAX_CLIENT_PULL_TIMEOUT = 6; // max timeout we'll follow

// Function also present in shdocvw\basesb.cpp and in mshtml
BOOL ParseRefreshContent(LPWSTR pwzContent, UINT * puiDelay, LPWSTR pwzUrlBuf, UINT cchUrlBuf);

const WCHAR c_wszHeadVerb[] = L"HEAD";

const WCHAR c_szUserAgentPrefix[] = L"User-Agent: ";
const WCHAR c_szAcceptLanguagePrefix[] = L"Accept-Language: ";

#define WM_URLDL_CLEAN      (WM_USER + 0x1010)
#define WM_URLDL_ONDLCOMPLETE (WM_USER + 0x1012)
#define WM_URLDL_CLIENTPULL (WM_USER+0x1013)

#define SAFE_RELEASE_BSC() \
if (m_pCbsc) { \
m_pCbsc->SetParent(NULL); \
m_pCbsc->Release(); \
m_pCbsc = NULL; \
} else

//---------------------------------------------------------------
// CUrlDownload class
CUrlDownload::CUrlDownload(CUrlDownloadSink *pParent, UINT iID /* =0 */)
{
    DWORD cbData;

    // Maintain global count of objects
    DllAddRef();

    m_iID = iID;
    m_pParent = pParent;

    m_cRef = 1;
    
    ASSERT(m_pDocument==NULL && m_dwConnectionCookie==0 && m_pwszURL == NULL);

    // Get the timeout value (stored in seconds)
    cbData = sizeof(m_nTimeout);
    if (NO_ERROR != SHGetValue(HKEY_CURRENT_USER, c_szRegKey, TEXT("Timeout"), NULL, &m_nTimeout, &cbData))
    {
        // Default to 120 seconds
        m_nTimeout = 120;
    }

    // find the HTML clipboard format
    if (!g_cfHTML)
    {
        g_cfHTML = (CLIPFORMAT) RegisterClipboardFormat(CFSTR_MIME_HTML);
        TraceMsg(TF_THISMODULE, "ClipFormat for HTML = %d", (int)g_cfHTML);
    }

    // find out if we need to set the "RESYNCHRONIZE" flag
    INTERNET_CACHE_CONFIG_INFOA CacheConfigInfo;
    DWORD dwBufSize = sizeof(CacheConfigInfo);
    CacheConfigInfo.dwStructSize = sizeof(CacheConfigInfo);

    if (GetUrlCacheConfigInfoA(&CacheConfigInfo, &dwBufSize, CACHE_CONFIG_SYNC_MODE_FC))
    {
        if ((WININET_SYNC_MODE_ONCE_PER_SESSION == CacheConfigInfo.dwSyncMode) ||
             (WININET_SYNC_MODE_ALWAYS == CacheConfigInfo.dwSyncMode) ||
             (WININET_SYNC_MODE_AUTOMATIC == CacheConfigInfo.dwSyncMode))
        {
            m_fSetResync = FALSE;
        }
        else
        {
            m_fSetResync = TRUE;
            DBG("Browser session update='never', setting RESYNCHRONIZE");
        }
    }
    else
        DBG_WARN("GetUrlCacheConfigInfo failed! Not setting Resync.");

    m_lBindFlags = DLCTL_SILENT | DLCTL_NO_SCRIPTS | DLCTL_NO_BEHAVIORS |
        DLCTL_NO_JAVA | DLCTL_NO_RUNACTIVEXCTLS | DLCTL_NO_DLACTIVEXCTLS;
    if (m_fSetResync)
        m_lBindFlags |= DLCTL_RESYNCHRONIZE;

    // register our window class if necessary
    if (!g_lRegisteredWnd)
    {
        g_lRegisteredWnd++;

        WNDCLASS wc;

        wc.style = 0;
        wc.lpfnWndProc = UrlDownloadWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = g_hInst;
        wc.hIcon = NULL;
        wc.hCursor = NULL;
        wc.hbrBackground = (HBRUSH)NULL;
        wc.lpszMenuName = NULL;
        wc.lpszClassName = URLDL_WNDCLASS;

        RegisterClass(&wc);
    }
}

CUrlDownload::~CUrlDownload()
{
    // Maintain global count of objects
    DllRelease();

    CleanUp();
    DBG("Destroyed CUrlDownload object");
}

void CUrlDownload::CleanUpBrowser()
{
    SAFERELEASE(m_pScript);

    if (m_fAdviseOn)
    {
        UnAdviseMe();
    }
    SAFERELEASE(m_pCP);
    SAFERELEASE(m_pDocument);
    SAFERELEASE(m_pPersistMk);
    SAFERELEASE(m_pOleCmdTarget);
    SAFELOCALFREE(m_pwszClientPullURL);
}

void CUrlDownload::CleanUp()
{
    CleanUpBrowser();
    SAFE_RELEASE_BSC();
    SAFELOCALFREE(m_pwszURL);
    SAFELOCALFREE(m_pstLastModified);
    SAFERELEASE(m_pStm);
    SAFELOCALFREE(m_pwszUserAgent);

    if (m_hwndMe)
    {
        SetWindowLongPtr(m_hwndMe, GWLP_USERDATA, 0);
        DestroyWindow(m_hwndMe);
        m_hwndMe = NULL;
    }
}

LRESULT UrlDownloadWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    CUrlDownload *pThis = (CUrlDownload*) GetWindowLongPtr(hWnd, GWLP_USERDATA);

    // Validate pThis
#ifdef DEBUG
    if (pThis && IsBadWritePtr(pThis, sizeof(*pThis)))
    {
        TraceMsg(TF_THISMODULE,
            "Invalid 'this' in UrlDownloadWndProc (0x%08x) - already destroyed?", pThis);
    }
#endif

    switch (Msg)
    {
    case WM_CREATE :
        {
            LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;

            if (!pcs || !(pcs->lpCreateParams))
            {
                DBG_WARN("Invalid param UrlDownloadWndProc Create");
                return -1;
            }
            SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) pcs->lpCreateParams);
            return 0;
        }

    case WM_URLDL_CLIENTPULL :
    case WM_URLDL_ONDLCOMPLETE :
    case WM_TIMER :
        if (pThis)
            pThis->HandleMessage(hWnd, Msg, wParam, lParam);
        break;

    default:
        return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    return 0;
}

HRESULT CUrlDownload::CreateMyWindow()
{
    // Create our callback window
    if (NULL == m_hwndMe)
    {
//      TraceMsg(TF_THISMODULE, "Creating MeWnd, this=0x%08x", (DWORD)this);
        m_hwndMe = CreateWindow(URLDL_WNDCLASS, TEXT("YO"), WS_OVERLAPPED,
                    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                    NULL, NULL, g_hInst, (LPVOID)this);

        if (NULL == m_hwndMe)
        {
            DBG_WARN("CUrlDownload CreateWindow(UrlDl WndClass) failed");
            return E_FAIL;
        }
    }
    return S_OK;
}

HRESULT CUrlDownload::BeginDownloadURL2(
    LPCWSTR     pwszURL,        // URL
    BDUMethod   iMethod,        // download method
    BDUOptions  iOptions,       // download options
    LPTSTR      pszLocalFile,   // Local file to download to instead of cache
    DWORD       dwMaxSize       // Max size in bytes; will abort if exceeded
)
{
    HRESULT hr = S_OK;

    // Param validation
    ASSERT(pwszURL);
    ASSERT(!(iOptions & BDU2_NEEDSTREAM) || (iMethod == BDU2_URLMON));
    ASSERT(!pszLocalFile || (iMethod == BDU2_URLMON));

    if (pszLocalFile && iMethod != BDU2_URLMON)
    {
        hr = E_INVALIDARG;
    }
    else
    {
        CreateMyWindow();

        // Clean up some old stuff
        if (m_pCbsc)
        {
            if (m_fbscValid)
                m_pCbsc->Abort();
            SAFE_RELEASE_BSC();
        }
        SAFERELEASE(m_pScript);
        SAFERELEASE(m_pStm);

        m_fbscValid = m_fBrowserValid = FALSE;

        m_iMethod = iMethod;
        m_iOptions = iOptions;

        m_dwMaxSize = dwMaxSize;

        SAFELOCALFREE(m_pwszClientPullURL);
        m_iNumClientPull = 0;

        // Save URL
        SAFELOCALFREE(m_pwszURL);
        m_pwszURL = StrDupW(pwszURL);

        SAFELOCALFREE(m_pstLastModified);
        m_dwResponseCode = 0;

        if ((iOptions & BDU2_FAIL_IF_NOT_HTML) && IsNonHtmlUrl(pwszURL))
        {
            // Hey, this isn't an HTML url! Don't even try to download it.
            OnDownloadComplete(BDU2_ERROR_NOT_HTML);
        }
        else
        {
            // Determine how to download this URL
            if ((iMethod == BDU2_BROWSER) ||
                ((iMethod == BDU2_SMART) && IsHtmlUrl(pwszURL)))
            {
                hr = BeginDownloadWithBrowser(pwszURL);
            }
            else
            {
                hr = BeginDownloadWithUrlMon(pwszURL, pszLocalFile, NULL);
            }
        }
    }

    if (FAILED(hr))
    {
        DBG("BeginDownloadURL2 : error HRESULT - calling OnDownloadComplete w/Error");
        OnDownloadComplete(BDU2_ERROR_GENERAL);
    }

    return hr;
}

//
// Looks up the Url in the url history object and if its not CP_ACP
// inserts an IHTMLLoadOptions object that contains the codepage
// into the bind context
//
HRESULT InsertHistoricalCodepageIntoBindCtx(LPCWSTR pwszURL, IBindCtx * pbc)
{
    HRESULT hr = S_OK;

    if (pwszURL == NULL || pbc == NULL)
    {
        hr = E_INVALIDARG;
    }
    else
    {
        //
        // Get the codepage from the intsite database. This is the codepage
        // the user set when last visiting this url.
        //
        PROPVARIANT propCodepage = {0};
        propCodepage.vt = VT_UI4;

        TCHAR szURL[INTERNET_MAX_URL_LENGTH];
        MyOleStrToStrN(szURL, INTERNET_MAX_URL_LENGTH, pwszURL);
        hr = IntSiteHelper(szURL, &c_rgPropRead[PROP_CODEPAGE], 
            &propCodepage, 1, FALSE);

        if (SUCCEEDED(hr) && propCodepage.lVal != CP_ACP)
        {
            //
            // We got a codepage that wasn't the ansi one create an
            // HTMLLoadOptions object and set the code page in it.
            //
            IHtmlLoadOptions *phlo = NULL;
            hr = CoCreateInstance(CLSID_HTMLLoadOptions, NULL, 
                CLSCTX_INPROC_SERVER, IID_IHtmlLoadOptions, (void**)&phlo);

            if (SUCCEEDED(hr) && phlo)
            {
                hr = phlo->SetOption(HTMLLOADOPTION_CODEPAGE, &propCodepage.lVal,
                    sizeof(propCodepage.lVal));

                if (SUCCEEDED(hr))
                {
                    //
                    // Insert the option into the bindctx
                    //
                    pbc->RegisterObjectParam(L"__HTMLLOADOPTIONS", phlo);
                    TraceMsg(TF_THISMODULE,
                        "InsertHistoricalCodepageIntoBindCtx codepage=%d",
                        propCodepage.lVal);
                }
                phlo->Release();
            }
        }
    }
    return hr;
}

LPCWSTR CUrlDownload::GetUserAgent()
{
    if (m_pwszUserAgent)
    {
        return m_pwszUserAgent;
    }

    // Get default User-Agent string from urlmon
    CHAR chUA[1024];
    DWORD dwBufLen;

    // Assume that UrlMkGetSessionOption always succeeds (82160).
    chUA[0] = 0;
    UrlMkGetSessionOption(URLMON_OPTION_USERAGENT, chUA, sizeof(chUA), &dwBufLen, 0);
    
    // Append "MSIECrawler"
    int iLenUA, iLenNew;

    iLenUA = lstrlenA(chUA);
    iLenNew = iLenUA + ARRAYSIZE(c_wszUserAgentAppend);

    ASSERT(iLenUA == (int)(dwBufLen-1));

    if (iLenUA > 0)
    {
        m_pwszUserAgent = (LPWSTR) LocalAlloc(LMEM_FIXED, sizeof(WCHAR)*iLenNew);

        if (m_pwszUserAgent)
        {
            LPWSTR pwszAppend = m_pwszUserAgent+iLenUA-1;
            m_pwszUserAgent[0] = L'\0';
            SHAnsiToUnicode(chUA, m_pwszUserAgent, iLenNew);
            // find the closing parenthesis and append string there
            if (*pwszAppend != L')')
            {
                DBG("GetUserAgent: Last Char in UA isn't closing paren");
                pwszAppend = StrRChrW(m_pwszUserAgent, m_pwszUserAgent+iLenUA, L')');
            }
            if (pwszAppend)
            {
                StrCpyW(pwszAppend, c_wszUserAgentAppend);
            }
            else
            {
                LocalFree(m_pwszUserAgent);
                m_pwszUserAgent = NULL;
            }
        }
    }

    return m_pwszUserAgent;
}
    
HRESULT CUrlDownload::BeginDownloadWithBrowser(LPCWSTR pwszURL)
{
    HRESULT hr;

    // Get browser and hook up sink
    // (no-op if we're already set up)
    hr = GetBrowser();

    if (SUCCEEDED(hr))
    {
        // browse to the required URL
        LPMONIKER           pURLMoniker = NULL;
        IBindCtx           *pbc = NULL;

        // create a URL moniker from the canonicalized path
        hr=CreateURLMoniker(NULL, pwszURL, &pURLMoniker);
        if (FAILED(hr)) DBG_WARN("CreateURLMoniker failed");

        // create an empty bind context so that Urlmon will call Trident's
        //  QueryService on the proper thread so that Trident can delegate
        //  it to use properly.
        hr=CreateBindCtx(0, &pbc);
        if (FAILED(hr)) DBG_WARN("CreateBindCtx failed");

        if (SUCCEEDED(hr))
        {
            //
            // Looks up the Url in the url history object and if its not CP_ACP
            // inserts an IHTMLLoadOptions object that contains the codepage
            // into the bind context. This is done so that TRIDENT is seeded
            // with the correct codepage.
            //
            InsertHistoricalCodepageIntoBindCtx(pwszURL, pbc);

            hr = m_pPersistMk->Load(FALSE, pURLMoniker, pbc, 0);
            if (SUCCEEDED(hr)) m_fWaitingForReadyState = TRUE;
            if (FAILED(hr)) DBG_WARN("PersistMoniker::Load failed");
        }

        // clean up junk
        if (pURLMoniker)
            pURLMoniker->Release();

        if (pbc)
            pbc->Release();

        if (SUCCEEDED(hr))
        {
            m_fBrowserValid = TRUE;
            StartTimer();       // Start our timeout
        }
        else
        {
            DBG("Error binding with Browser's IPersistMoniker");
            CleanUpBrowser();
        }
    }

    TraceMsg(TF_THISMODULE,
        "CUrlDownload::BeginDownloadWithBrowser (hr=0x%08x)", (long)hr);

    return hr;
}

HRESULT CUrlDownload::OnDownloadComplete(int iError)
{
    PostMessage(m_hwndMe, WM_URLDL_ONDLCOMPLETE, (WPARAM)iError, 0);
    StopTimer();
    return S_OK;
}

BOOL CUrlDownload::HandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_URLDL_CLIENTPULL :
        {
            HRESULT hr = E_FAIL;

            // Ask our parent if we should do this
            if (m_pwszClientPullURL)
            {
                if (m_pParent && (m_iNumClientPull < MAX_CLIENT_PULL_NUM))
                    hr = m_pParent->OnClientPull(m_iID, m_pwszURL, m_pwszClientPullURL);

                TraceMsgA(TF_THISMODULE, "CUrlDownload %s executing client pull to %ws",
                    SUCCEEDED(hr) ? "is" : "**not**", m_pwszClientPullURL);
            }

            if (SUCCEEDED(hr))
            {
                // Download this new url. Don't give "downloadcomplete" for first one
                // Save member vars since they get reset in BDU2
                int iNumClientPull = m_iNumClientPull;
                LPWSTR pszNewURL = m_pwszClientPullURL;

                m_pwszClientPullURL = NULL;
                hr = BeginDownloadURL2(pszNewURL, m_iMethod, m_iOptions, NULL, m_dwMaxSize);
                MemFree(pszNewURL);
                if (SUCCEEDED(hr))
                {
                    m_iNumClientPull = iNumClientPull + 1;
                }
            }
        }
        break;

    case WM_URLDL_ONDLCOMPLETE :
        if (m_pParent)
            m_pParent->OnDownloadComplete(m_iID, (int)wParam);
        return TRUE;

    case WM_TIMER :
#ifdef DEBUG
        DBG_WARN("CUrlDownload ERROR - TIMING OUT");

        if ( m_fBrowserValid )
        {
            TraceMsg( TF_ALWAYS, "CUrlDownload::HandleMessage() - Browser Timeout." );
        }
        else
        {
            TraceMsg( TF_ALWAYS, "CUrlDownload::HandleMessage() - Non-Browser Timeout." );
        }

        if ( m_fbscValid )
        {
            TraceMsg( TF_ALWAYS, "CUrlDownload::HandleMessage() - UrlMon Timeout." );
        }
        else
        {
            TraceMsg( TF_ALWAYS, "CUrlDownload::HandleMessage() - Non-UrlMon Timeout." );
        }
#endif
        StopTimer();
        AbortDownload(BDU2_ERROR_TIMEOUT);
        return TRUE;
    }
    return TRUE;
}

HRESULT CUrlDownload::AbortDownload(int iErrorCode /* =-1 */)
{
    HRESULT hr=S_FALSE;
    BOOL    fAborted=FALSE;

    if (m_fBrowserValid)
    {
        ASSERT(m_pOleCmdTarget);
        if (m_pOleCmdTarget)
        {
            m_pOleCmdTarget->Exec(NULL, OLECMDID_STOP, 0, NULL, NULL);
        }

        SAFELOCALFREE(m_pwszClientPullURL);

        fAborted=TRUE;
        m_fBrowserValid = FALSE;
    }

    if (m_fbscValid)
    {
        ASSERT(m_pCbsc);
        if (m_pCbsc)
        {
            hr = m_pCbsc->Abort();
            fAborted=TRUE;
            SAFE_RELEASE_BSC();
        }
        m_fbscValid=FALSE;
    }

    if (fAborted && m_pParent)
    {
        OnDownloadComplete((iErrorCode==-1) ? BDU2_ERROR_ABORT : iErrorCode);
    }

    return hr;
}

// Loads browser, creates sink and hooks it up to sinks
HRESULT CUrlDownload::GetBrowser()
{
    HRESULT hr = S_OK;

    if (m_fAdviseOn)
        return hr;

    if (NULL == m_pDocument)
    {
        ASSERT(!m_pPersistMk);
        ASSERT(!m_pCP);

        hr = CoCreateInstance(CLSID_HTMLDocument, NULL,
                    CLSCTX_INPROC, IID_IHTMLDocument2, (void **)&m_pDocument);

        DBG("Created new CLSID_HTMLDocument");

        if (SUCCEEDED(hr))
        {
            IOleObject *pOleObj;

            hr = m_pDocument->QueryInterface(IID_IOleObject, (void **)&pOleObj);
            if (SUCCEEDED(hr))
            {
                pOleObj->SetClientSite((IOleClientSite *)this);
                pOleObj->Release();
            }
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pDocument->QueryInterface(IID_IPersistMoniker, (void**)&m_pPersistMk);
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pDocument->QueryInterface(IID_IOleCommandTarget, (void**)&m_pOleCmdTarget);
        }
    }

    // At this point we have m_pDocument and m_pPersistMk

    // Get DownloadNotify sink hooked up
    IDownloadNotify *pNotify=NULL;
    BOOL            fNotifySet=FALSE;

    if (SUCCEEDED(hr) && SUCCEEDED(m_pParent->GetDownloadNotify(&pNotify)) && pNotify)
    {
        IOleCommandTarget *pTarget=NULL;

        if (SUCCEEDED(m_pDocument->QueryInterface(IID_IOleCommandTarget, (void **)&pTarget)) && pTarget)
        {
            VARIANTARG varIn;

            varIn.vt = VT_UNKNOWN;
            varIn.punkVal = (IUnknown *)pNotify;
            if (SUCCEEDED(pTarget->Exec(&CGID_DownloadHost, DWNHCMDID_SETDOWNLOADNOTIFY, 0,
                                        &varIn, NULL)))
            {
                fNotifySet=TRUE;
            }

            pTarget->Release();
        }

        if (!fNotifySet)
        {
            DBG_WARN("IDownloadNotify provided, but couldn't set callback!");
        }

        pNotify->Release();
    }

    if (!fNotifySet && (m_iOptions & BDU2_DOWNLOADNOTIFY_REQUIRED))
    {
        DBG_WARN("Couldn't set notify, parent requires it. CUrlDownload failing MSHTML download.");
        hr = E_FAIL;
    }

    // Get PropertyNotifySink hooked up
    // Find our connection point if necessary
    if (NULL == m_pCP && SUCCEEDED(hr))
    {
        IConnectionPointContainer *pCPCont=NULL;
        hr = m_pDocument->QueryInterface(IID_IConnectionPointContainer,
                (void **)&pCPCont);

        if (SUCCEEDED(hr))
        {
            hr = pCPCont->FindConnectionPoint(IID_IPropertyNotifySink, &m_pCP);
            pCPCont->Release();
            pCPCont = NULL;
        }
    }

    // And hook it up to us
    if (SUCCEEDED(hr))
    {
        // create sink
        IPropertyNotifySink *pSink = (IPropertyNotifySink *)this;

        hr = m_pCP->Advise(pSink, &m_dwConnectionCookie);
        if (SUCCEEDED(hr))
        {
            m_fAdviseOn = TRUE;
        }

    }

    if (FAILED(hr)) DBG_WARN("CUrlDownload::GetBrowser returning failure");
    return hr;
}

void CUrlDownload::UnAdviseMe()
{
    if (m_fAdviseOn)
    {
        m_pCP->Unadvise(m_dwConnectionCookie);
        m_fAdviseOn = FALSE;
    }
}

void CUrlDownload::DestroyBrowser()
{
    CleanUpBrowser();
}

void CUrlDownload::DoneDownloading()
{
    // Don't send any more messages to the parent
    LeaveMeAlone();

    AbortDownload();

    CleanUp();
}

HRESULT CUrlDownload::GetScript(IHTMLWindow2 **ppWin)
{
    HRESULT hr = E_FAIL;
    IDispatch *pDisp=NULL;

    ASSERT(ppWin);
    *ppWin=NULL;

    if (!m_fBrowserValid)
    {
        DBG("m_fBrowserValid FALSE, GetScript returning failure");
        return E_FAIL;
    }

    *ppWin = NULL;

    if (m_pScript)
    {
        m_pScript->AddRef();
        *ppWin = m_pScript;
        return S_OK;
    }

    if (m_pDocument)
    {
        hr = m_pDocument->get_Script(&pDisp);
        if (!pDisp) hr=E_NOINTERFACE;
#ifdef DEBUG
        if (FAILED(hr)) DBG_WARN("CUrlDownload::GetScript:  get_Script failed");
#endif
    }

    if (SUCCEEDED(hr))
    {
        hr = pDisp->QueryInterface(IID_IHTMLWindow2, (void **)ppWin);
        if (*ppWin == NULL) hr = E_NOINTERFACE;
        pDisp->Release();
#ifdef DEBUG
        if (FAILED(hr)) DBG_WARN("CUrlDownload::GetScript:  QI IOmWindow2 failed");
#endif
    }

    // Save this so future GetScript() calls much faster
    ASSERT(!m_pScript);
    if (SUCCEEDED(hr))
    {
        m_pScript = *ppWin;
        m_pScript->AddRef();
    }

    return hr;
}

// static member function
// Strips off anchor from URL (# not after ?)
// S_FALSE : Unchanged
// S_OK    : Removed anchor
HRESULT CUrlDownload::StripAnchor(LPWSTR lpURL)
{
    if (!lpURL) return E_POINTER;

    while (*lpURL)
    {
        if (*lpURL == L'?')
            return S_FALSE;
        if (*lpURL == L'#')
        {
            *lpURL = L'\0';
            return S_OK;
        }
        lpURL ++;
    }
    return S_FALSE;
}

// Returns pointer to '.' or pointer to null-terminator or query '?'
LPWSTR                  // ptr to period or to null-term or '?'
URLFindExtensionW(
    LPCWSTR pszURL,
    int *piLen)         // length including period
{
    LPCWSTR pszDot;

    for (pszDot = NULL; *pszURL && *pszURL!='?'; pszURL++)
    {
        switch (*pszURL) {
        case TEXT('.'):
            pszDot = pszURL;         // remember the last dot
            break;
        case TEXT('/'):
            pszDot = NULL;       // forget last dot, it was in a directory
            break;
        }
    }

    if (piLen)
    {
        if (pszDot)
            *piLen = (int) (pszURL-pszDot);
        else
            *piLen = 0;
    }

    // if we found the extension, return ptr to the dot, else
    // ptr to end of the string (NULL extension) (cast->non const)
    return pszDot ? (LPWSTR)pszDot : (LPWSTR)pszURL;
}

// Returns TRUE if this appears to be an HTML URL
BOOL CUrlDownload::IsHtmlUrl(LPCWSTR lpURL)
{
    LPWSTR pwch;
    int iLen;

    pwch = URLFindExtensionW(lpURL, &iLen);

    if (*pwch && iLen)
    {
        pwch ++; iLen --;

        // We found an extension. Check it out.
        if ((iLen == 4 &&
                (!MyAsciiCmpNIW(pwch, L"html", 4))) ||
            (iLen == 3 &&
                (!MyAsciiCmpNIW(pwch, L"htm", 3) ||
                 !MyAsciiCmpNIW(pwch, L"htt", 3) ||
                 !MyAsciiCmpNIW(pwch, L"asp", 3) ||
                 !MyAsciiCmpNIW(pwch, L"htx", 3)
                                            )))
        {
            // known HTML extension
            return TRUE;
        }
    }

    return FALSE;
}

// Returns TRUE if this appears NOT to be an HTML URL
BOOL CUrlDownload::IsNonHtmlUrl(LPCWSTR lpURL)
{
    LPWSTR pwch;
    int iLen;

    pwch = URLFindExtensionW(lpURL, &iLen);

    if (*pwch && iLen)
    {
        pwch ++; iLen --;

        // We found an extension. Check it out.
        if ((iLen==3) &&
                (!MyAsciiCmpNIW(pwch, L"bmp", 3) ||
                 !MyAsciiCmpNIW(pwch, L"cab", 3) ||
                 !MyAsciiCmpNIW(pwch, L"cdf", 3) ||
                 !MyAsciiCmpNIW(pwch, L"jpg", 3) ||
                 !MyAsciiCmpNIW(pwch, L"exe", 3) ||
                 !MyAsciiCmpNIW(pwch, L"zip", 3) ||
                 !MyAsciiCmpNIW(pwch, L"doc", 3) ||
                 !MyAsciiCmpNIW(pwch, L"gif", 3)
                                            ))
        {
            // known non-HTML extension
            return TRUE;
        }
    }

    return FALSE;
}

// Returns TRUE if this is a URL we should try to download (http:)
BOOL CUrlDownload::IsValidURL(LPCWSTR lpURL)
{
    // Check protocol
//  HKEY hk;

#if 0
    BOOL fValidProtocol = FALSE;

    // Always accept http or https
    if (!StrCmpNIW(lpURL, L"http", 4))
        fValidProtocol = TRUE;

    if (!fValidProtocol &&
        (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("PROTOCOLS\\Handler"), 0, KEY_QUERY_VALUE, &hk)))
    {
        // Crack out protocol
        DWORD dwData=0, cbData = sizeof(DWORD);
        int i;
        char ch[16];

        // we know we are 7-bit
        for (i=0; i<ARRAYSIZE(ch) && lpURL[i] != L':' && lpURL[i]; i++)
            ch[i] = (char) (lpURL[i]);

        if (i<ARRAYSIZE(ch))
        {
            ch[i] = '\0';

            // We have protocol
            if (NO_ERROR == SHGetValue(hk, ch, TEXT("SupportsNoUI"), NULL, &dwData, &cbData))
            {
                if (dwData != 0)
                    fValidProtocol = TRUE;  // Passed test
            }
#ifdef DEBUG
            if (!fValidProtocol)
                TraceMsgA(TF_THISMODULE, "IsValidUrl failing url protocol=%s url=%ws", ch, lpURL);
#endif
        }

        RegCloseKey(hk);
    }
#endif

    // See if this protocol will give us something for the cache
    BOOL fUsesCache=FALSE;
    DWORD dwBufSize=0;
    CoInternetQueryInfo(lpURL, QUERY_USES_CACHE, 0,
        &fUsesCache, sizeof(fUsesCache), &dwBufSize, 0);

    if (!fUsesCache || (S_FALSE == ::IsValidURL(NULL, lpURL, 0)))
        return FALSE;

    return TRUE;
}

HRESULT CUrlDownload::GetRealURL(LPWSTR *ppwszURL)
{
    *ppwszURL = NULL;

    if (!m_fBrowserValid)
    {
        if (m_pwszURL)
            *ppwszURL = StrDupW(m_pwszURL);
    }
    else
    {
        // Get the real URL from the browser in case we were redirected
        // We could optimize to do this only once
        ITargetContainer *pTarget=NULL;
        LPWSTR pwszThisUrl=NULL;

        if (m_pDocument)
        {
            m_pDocument->QueryInterface(IID_ITargetContainer, (void **)&pTarget);

            if (pTarget)
            {
                pTarget->GetFrameUrl(&pwszThisUrl);
                pTarget->Release();
            }
        }

        if (pwszThisUrl)
        {
            if (m_pwszURL) MemFree(m_pwszURL);
            m_pwszURL = StrDupW(pwszThisUrl);
            *ppwszURL = StrDupW(pwszThisUrl);
            CoTaskMemFree(pwszThisUrl);
        }
        else if (m_pwszURL)
        {
            *ppwszURL = StrDupW(m_pwszURL);
        }
    }

    return (*ppwszURL) ? S_OK : E_OUTOFMEMORY;
}


HRESULT CUrlDownload::GetDocument(IHTMLDocument2 **ppDoc)
{
    HRESULT hr;

    if (!m_fBrowserValid)
    {
        DBG("GetDocument failing, m_fBrowserValid FALSE");
        *ppDoc = NULL;
        return E_FAIL;
    }

    *ppDoc = m_pDocument;
    if (m_pDocument)
    {
        m_pDocument->AddRef();
        hr = S_OK;
    }
    else
        hr = E_NOINTERFACE;

    return hr;
}

HRESULT CUrlDownload::GetStream(IStream **ppStm)
{
    if (!m_pStm)
    {
        DBG("Stream not available, CUrlDownload::GetStream failing");
        *ppStm = NULL;
        return E_FAIL;
    }

    *ppStm = m_pStm;
    (*ppStm)->AddRef();

    return S_OK;
}

HRESULT CUrlDownload::GetLastModified(SYSTEMTIME *pstLastModified)
{
    if (NULL == pstLastModified)
        return E_INVALIDARG;

    if (NULL == m_pstLastModified)
        return E_FAIL;

    CopyMemory(pstLastModified, m_pstLastModified, sizeof(SYSTEMTIME));

    return S_OK;
}

HRESULT CUrlDownload::GetResponseCode(DWORD *pdwResponseCode)
{
    if (m_dwResponseCode == 0)
        return E_FAIL;

    *pdwResponseCode = m_dwResponseCode;

    return S_OK;
}
    
// Start or extend timer
void CUrlDownload::StartTimer()
{
    if (m_hwndMe)
    {
        if (!m_iTimerID)
        {
            m_iTimerID = 1;
            DBG("CUrlDownload Creating new timeout timer");
        }

        m_iTimerID = SetTimer(m_hwndMe, 1, 1000 * m_nTimeout, NULL);
    }
}

void CUrlDownload::StopTimer()
{
    if (m_hwndMe && m_iTimerID)
    {
        DBG("CUrlDownload destroying timeout timer");
        KillTimer(m_hwndMe, m_iTimerID);
        m_iTimerID = 0;
    }
}

//
// IUnknown of CUrlDownload
//
STDMETHODIMP CUrlDownload::QueryInterface(REFIID riid, void ** ppv)
{
    *ppv=NULL;

    // Validate requested interface
    if (IID_IOleClientSite == riid)
        *ppv=(IOleClientSite *)this;
    else if (IID_IPropertyNotifySink == riid)
        *ppv=(IPropertyNotifySink *)this;
    else if (IID_IOleCommandTarget == riid)
        *ppv=(IOleCommandTarget *)this;
    else if (IID_IDispatch == riid)
        *ppv=(IDispatch *)this;
    else if (IID_IServiceProvider == riid)
        *ppv = (IServiceProvider *)this;
    else if (IID_IAuthenticate == riid)
        *ppv = (IAuthenticate *)this;
    else if (IID_IInternetSecurityManager == riid)
        *ppv = (IInternetSecurityManager *)this;
    else if (IID_IHttpSecurity == riid)
        *ppv = (IHttpSecurity *)this;
    else if ((IID_IUnknown == riid) ||
             (IID_IHlinkFrame == riid))
        *ppv = (IHlinkFrame *)this;
    else
    {
        // DBGIID("CUrlDownload::QueryInterface() failing", riid);
    }

    // Addref through the interface
    if (NULL != *ppv)
    {
        ((LPUNKNOWN)*ppv)->AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}


STDMETHODIMP_(ULONG) CUrlDownload::AddRef(void)
{
    return ++m_cRef;
}


STDMETHODIMP_(ULONG) CUrlDownload::Release(void)
{
    if (0L != --m_cRef)
        return 1L;

    delete this;
    return 0L;
}

STDMETHODIMP CUrlDownload::GetTypeInfoCount(UINT *pctinfo)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo **pptinfo)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload::GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
            DISPPARAMS *pdispparams, VARIANT *pvarResult,
            EXCEPINFO *pexcepinfo, UINT *puArgErr)
{
    if (!pvarResult)
        return E_INVALIDARG;

    ASSERT(pvarResult->vt == VT_EMPTY);

    if (wFlags == DISPATCH_PROPERTYGET)
    {
        HRESULT hr = DISP_E_MEMBERNOTFOUND;
        
        switch (dispidMember)
        {
        case DISPID_AMBIENT_DLCONTROL :
            TraceMsg(TF_THISMODULE, "Returning DLCONTROL ambient property 0x%08x", m_lBindFlags);
            pvarResult->vt = VT_I4;
            pvarResult->lVal = m_lBindFlags;
            hr = S_OK;
            break;
        case DISPID_AMBIENT_USERAGENT:
            DBG("Returning User Agent ambient property");
            pvarResult->bstrVal = SysAllocString(GetUserAgent());
            if (pvarResult->bstrVal != NULL)
            {
                pvarResult->vt = VT_BSTR;
                hr = S_OK;
            }
            break;
        }
        return hr;
    }

    return DISP_E_MEMBERNOTFOUND;
}

// IPropertyNotifySink

STDMETHODIMP CUrlDownload::OnChanged(DISPID dispID)
{
    // We've received a notification, extend our timer if it's currently running
    if (m_iTimerID)
        StartTimer();

    if ((DISPID_READYSTATE == dispID) ||
        (DISPID_UNKNOWN == dispID))
    {
        // Find out if we're done
        if (m_fWaitingForReadyState)
        {
            VARIANT     varState;
            DISPPARAMS  dp;

            VariantInit(&varState);

            if (SUCCEEDED(m_pDocument->Invoke(DISPID_READYSTATE, 
                                              IID_NULL, 
                                              GetUserDefaultLCID(), 
                                              DISPATCH_PROPERTYGET, 
                                              &dp, 
                                              &varState, NULL, NULL)) &&
                V_VT(&varState)==VT_I4 && 
                V_I4(&varState)== READYSTATE_COMPLETE)
            {
                m_fWaitingForReadyState = FALSE;
                // Successful download. See if a client-pull is waiting.
                if (m_pwszClientPullURL)
                    PostMessage(m_hwndMe, WM_URLDL_CLIENTPULL, 0, 0);
                else
                    OnDownloadComplete(BDU2_ERROR_NONE);
            }
        }
    }

    return S_OK;
}

STDMETHODIMP CUrlDownload::OnRequestEdit(DISPID dispID)
{
    return S_OK;
}

// IOleCommandTarget
STDMETHODIMP CUrlDownload::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds,
                                    OLECMD prgCmds[], OLECMDTEXT *pCmdText)
{
    return OLECMDERR_E_UNKNOWNGROUP;
}

STDMETHODIMP CUrlDownload::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
                                DWORD nCmdexecopt, VARIANTARG *pvarargIn,
                                VARIANTARG *pvarargOut)
{
    HRESULT hres = OLECMDERR_E_NOTSUPPORTED;

    if (pguidCmdGroup == NULL) 
    {
        switch(nCmdID) 
        {
        case OLECMDID_SETPROGRESSPOS:
        {
            hres = S_OK;
            VARIANT     varBytes;
            
            if (m_pOleCmdTarget)
            {
                varBytes.vt=VT_EMPTY;
                m_pOleCmdTarget->Exec(&CGID_MSHTML, IDM_GETBYTESDOWNLOADED, 0, NULL, &varBytes);

                if (varBytes.vt == VT_I4)
                {
                    DWORD dwBytes = (DWORD) varBytes.lVal;

                    TraceMsg(TF_THISMODULE, "%d bytes on page so far (mshtml)", dwBytes);

                    ProgressBytes(dwBytes);
                }
            }

            // 14032: If dialmon is around, tell it that something is going on
            IndicateDialmonActivity();

        }
            break;
        //
        // The containee has found an http-equiv meta tag; handle it
        // appropriately (client pull)
        //
        case OLECMDID_HTTPEQUIV_DONE:
            hres = S_OK;
            break;

        case OLECMDID_HTTPEQUIV:
            {
                LPWSTR  pwszEquivString = pvarargIn? pvarargIn->bstrVal : NULL;
                BOOL    fHasHeader = (pwszEquivString!=NULL);

                if (pvarargIn && pvarargIn->vt != VT_BSTR)
                    return OLECMDERR_E_NOTSUPPORTED;

                if (!fHasHeader || StrCmpNIW(c_wszRefresh, pwszEquivString, lstrlenW(c_wszRefresh)) == 0)
                {
                    // Hit.  Now do the right thing for this header
                    // We pass both the header and a pointer to the first char after
                    // ':', which is usually the delimiter handlers will look for.

                    LPWSTR pwszColon = fHasHeader ? StrChrW(pwszEquivString, ':') : NULL;
      
                    // Enforce the : at the end of the header
                    if (fHasHeader && !pwszColon)
                    {
                        return OLECMDERR_E_NOTSUPPORTED;
                    }
             
                    hres = HandleRefresh(pwszEquivString, pwszColon ? pwszColon+1:NULL,
                                         (nCmdID == OLECMDID_HTTPEQUIV_DONE));
                }
            }

            // if we return OLECMDERR_E_NOTSUPPORTED, we don't handle
            // client pull
            break;
        }
    }

    if ((hres == OLECMDERR_E_NOTSUPPORTED) && m_pParent)
    {
        hres = m_pParent->OnOleCommandTargetExec(pguidCmdGroup, nCmdID, nCmdexecopt,
                                                    pvarargIn, pvarargOut);
    }

    return hres;
}

// The basic operation was lifted from shdocvw\basesb.cpp
HRESULT CUrlDownload::HandleRefresh(LPWSTR pwszEquivString, LPWSTR pwszContent, BOOL fDone)
{
    unsigned int uiTimeout = 0;
    WCHAR        awch[INTERNET_MAX_URL_LENGTH];

    if (fDone)
    {
        return S_OK;    // fDone means we don't process this
    }

    // NSCompat: we only honor the first successfully parsed Refresh
    if (m_pwszClientPullURL)
        return S_OK;

    if (!pwszContent ||
        !ParseRefreshContent(pwszContent, &uiTimeout, awch, INTERNET_MAX_URL_LENGTH))
    {
        return OLECMDERR_E_NOTSUPPORTED;   // cannot handle refresh w/o timeout
    }
    
    if (!awch[0])
    {
        DBG("CUrlDownload ignoring client-pull directive with no url");
        return S_OK;
    }

    if (m_iNumClientPull >= MAX_CLIENT_PULL_NUM)
    {
        DBG("Max # client pulls exceeded; ignoring client pull directive");
        return S_OK;
    }

    TraceMsg(TF_THISMODULE, "CUrlDownload client pull (refresh=%d) url=%ws", uiTimeout, awch);
    if (uiTimeout > MAX_CLIENT_PULL_TIMEOUT)
    {
        DBG("Ignoring client-pull directive with large timeout");
        return S_OK;
    }

    m_pwszClientPullURL = StrDupW(awch);

    // If we can't copy the URL, don't set the timer or else we'll
    // keep reloading the same page.

    if (m_pwszClientPullURL == NULL)
        return OLECMDERR_E_NOTSUPPORTED;

    return S_OK;
}

HRESULT CUrlDownload::SetDLCTL(long lFlags)
{
//  TraceMsg(TF_THISMODULE, "CUrlDownload: SetDLCTL %04x", lFlags);
    m_lBindFlags = lFlags | DLCTL_SILENT;
    if (m_fSetResync)
        m_lBindFlags |= DLCTL_RESYNCHRONIZE;

    return S_OK;
}

#define INET_E_AGENT_BIND_IN_PROGRESS 0x800C0FFF

//==============================================================================
//  UrlMon download code
//==============================================================================
HRESULT CUrlDownload::BeginDownloadWithUrlMon(
    LPCWSTR     pwszURL,
    LPTSTR      pszLocalFile,
    IEnumFORMATETC *pEFE)
{
    IStream*    pstm = NULL;
    IMoniker*   pmk = NULL;
    IBindCtx*   pbc = NULL;
    HRESULT hr;

    hr = CreateURLMoniker(NULL, pwszURL, &pmk);
    if (FAILED(hr))
    {
        DBG_WARN("CreateURLMoniker failed");
        goto LErrExit;
    }

    SAFE_RELEASE_BSC();

    m_pCbsc = new CUrlDownload_BSC(m_iMethod, m_iOptions, pszLocalFile);
    if (m_pCbsc == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto LErrExit;
    }

    hr = CreateBindCtx(0, &pbc);
    if (FAILED(hr))
        goto LErrExit;

    if (pEFE)
    {
        hr = RegisterFormatEnumerator(pbc, pEFE, 0);
        if (FAILED(hr))
            DBG_WARN("RegisterFormatEnumerator failed (continuing download)");
    }

    hr = RegisterBindStatusCallback(pbc,
            (IBindStatusCallback *)m_pCbsc,
            0,
            0L);
    if (FAILED(hr))
        goto LErrExit;

    m_pCbsc->SetParent(this);
    m_fbscValid = TRUE;
    m_hrStatus = INET_E_AGENT_BIND_IN_PROGRESS;
    StartTimer();       // Start our timeout
    hr = pmk->BindToStorage(pbc, 0, IID_IStream, (void**)&pstm);

    if (m_hrStatus != INET_E_AGENT_BIND_IN_PROGRESS)
    {
        // Synchronous success or failure. Call OnDownloadComplete.
        // We can't do it in OnStopBinding because Urlmon returns hrStatus=S_OK...
        //   even if it fails.
        if (FAILED(hr) || FAILED(m_hrStatus))
            OnDownloadComplete(BDU2_ERROR_GENERAL);
        else
            OnDownloadComplete(BDU2_ERROR_NONE);

        DBG("Synchronous bind; OnDownloadComplete called");
    }

    m_hrStatus = S_OK;      // need this so we get OnDownloadComplete (asynch OnStopBinding)
    hr = S_OK;              // need this so we don't get extra OnDownloadComplete (BDU2)

    // Bind has started (and maybe completed), release stuff we don't need
    pmk->Release();
    pbc->Release();

    if (pstm)
        pstm->Release();

    return hr;

LErrExit:
    DBG_WARN("Error in CUrlDownload::BeginDownloadWithUrlMon");
    if (pbc) pbc->Release();
    if (pmk) pmk->Release();
    if (pstm) pstm->Release();
    SAFERELEASE(m_pCbsc);

    return hr;
} // CUrlDownload::BeginDownloadWithUrlMon

void CUrlDownload::BSC_OnStartBinding()
{
    DBG("BSC_OnStartBinding");
}

// We only get this call if we're not downloading with the browser.
void CUrlDownload::BSC_OnStopBinding(HRESULT hrStatus, IStream *pStm)
{
    TraceMsg(TF_THISMODULE, "BSC_OnStopBinding (hrStatus=0x%08x)", (long)hrStatus);
    ASSERT(m_pCbsc);

// It is ok to not have stream when we requested it (robots.txt)
//  ASSERT(( pStm &&  (m_iOptions & BDU2_NEEDSTREAM)) ||
//         (!pStm && !(m_iOptions & BDU2_NEEDSTREAM)));
    ASSERT(!pStm || (m_iOptions & BDU2_NEEDSTREAM));
    ASSERT(!m_pStm);

    // Save stream for caller if they requested it
    // We keep it until the release it (ReleaseStream) or nav to another url
    if (pStm && (m_iOptions & BDU2_NEEDSTREAM))
    {
        if (m_pStm) m_pStm->Release();
        m_pStm = pStm;
        m_pStm->AddRef();
    }

    // Send OnDownloadComplete, stop the timer
    if (m_iMethod == BDU2_HEADONLY && m_pstLastModified)
        hrStatus = S_OK;        // We got what we came for (hrStatus will be E_ABORT)

    if (m_hrStatus != INET_E_AGENT_BIND_IN_PROGRESS)
        OnDownloadComplete(SUCCEEDED(hrStatus) ? BDU2_ERROR_NONE : BDU2_ERROR_GENERAL);
    else
    {
        DBG("Not calling OnDownloadComplete; synchronous bind");
        m_hrStatus = hrStatus;
    }

    m_fbscValid = FALSE;
    SAFE_RELEASE_BSC();
}

void CUrlDownload::BSC_OnProgress(ULONG ulProgress, ULONG ulProgressMax)
{
    // extend our timer
    if (m_iTimerID)
        StartTimer();
}

void CUrlDownload::BSC_FoundLastModified(SYSTEMTIME *pstLastModified)
{
    DBG("Received last modified time");

    SAFELOCALFREE(m_pstLastModified);

    m_pstLastModified = (SYSTEMTIME *)MemAlloc(LMEM_FIXED, sizeof(SYSTEMTIME));

    if (m_pstLastModified)
    {
        CopyMemory(m_pstLastModified, pstLastModified, sizeof(SYSTEMTIME));
    }
}

void CUrlDownload::BSC_FoundMimeType(CLIPFORMAT cf)
{
    TraceMsg(TF_THISMODULE, "FoundMimeType %d", (int)cf);

    BOOL fAbort = FALSE, fBrowser=FALSE;
    HRESULT hr=S_OK;

    // Abort if not html if necessary.
    if ((m_iOptions & BDU2_FAIL_IF_NOT_HTML) && (cf != g_cfHTML))
    {
        DBG("Aborting non-HTML download");
        fAbort = TRUE;
        OnDownloadComplete(BDU2_ERROR_NOT_HTML);
    }

    // Abort the UrlMon download if necessary. Fire off
    //  a browser download if necessary.
    if (((m_iMethod == BDU2_SMART) || (m_iMethod == BDU2_SNIFF)) && (cf == g_cfHTML))
    {
        // Switch into the browser.
        ASSERT(m_pwszURL);
        if (m_pwszURL &&
            (m_dwResponseCode != 401))      // Don't bother if it's auth failure
        {
            DBG("Switching UrlMon download into browser");
            hr = BeginDownloadWithBrowser(m_pwszURL);
            if (SUCCEEDED(hr))
                fBrowser = TRUE;
        }
    }

    if (fAbort || fBrowser)
    {
        // Disconnect the BSC so that we don't get any more notifications.
        // If we're switching into the browser, don't abort the UrlMon
        //  download to help avoid getting multiple GET requests. We do
        //  disconnect the BSC but still maintain a ref to it so we abort
        //  it if necessary.
        ASSERT(m_pCbsc);
        if (m_pCbsc)
        {
            m_pCbsc->SetParent(NULL);  // We don't want OnStopBinding

            if (fAbort)
            {
                m_pCbsc->Abort();
                m_pCbsc->Release();
                m_pCbsc=NULL;
                m_fbscValid = FALSE;
            }
        }
    }
}

// Returns content for Accept-Language header
LPCWSTR CUrlDownload::GetAcceptLanguages()
{
    if (0 == m_iLangStatus)
    {
        DWORD cchLang = ARRAYSIZE(m_achLang);

        if (SUCCEEDED(::GetAcceptLanguagesW(m_achLang, &cchLang)))
        {
            m_iLangStatus = 1;
        }
        else
        {
            m_iLangStatus = 2;
        }
    }

    if (1 == m_iLangStatus)
    {
        return m_achLang;
    }
    
    return NULL;
}

HRESULT CUrlDownload::ProgressBytes(DWORD dwBytes)
{
    if (m_dwMaxSize > 0 && dwBytes > m_dwMaxSize)
    {
        TraceMsg(TF_THISMODULE, "CUrlDownload MaxSize exceeded aborting. %d of %d bytes", dwBytes, m_dwMaxSize);

        AbortDownload(BDU2_ERROR_MAXSIZE);
        return E_ABORT;
    }

    return S_OK;
}

//---------------------------------------------------------------
// IServiceProvider
STDMETHODIMP CUrlDownload::QueryService(REFGUID guidService, REFIID riid, void **ppvObject)
{
    if ((SID_SHlinkFrame == guidService && IID_IHlinkFrame == riid) ||
        (IID_IAuthenticate == guidService && IID_IAuthenticate == riid) ||
        (SID_SInternetSecurityManager == guidService && IID_IInternetSecurityManager == riid) ||
        (IID_IHttpSecurity == guidService && IID_IHttpSecurity == riid))
    {
        return QueryInterface(riid, ppvObject);
    }
    else
    {
        *ppvObject = NULL;
        return E_NOINTERFACE;
    }
}

//---------------------------------------------------------------
//IHttpSecurity
STDMETHODIMP CUrlDownload::OnSecurityProblem(DWORD dwProblem)
{
     
    return S_FALSE;
}

STDMETHODIMP CUrlDownload::GetWindow( REFGUID rguidReason, HWND *phwnd ) {
    
    if(phwnd && m_hwndMe) {
        *phwnd = m_hwndMe;
    } else
        return E_FAIL;

    return S_OK;

}

//---------------------------------------------------------------
// IAuthenticate
STDMETHODIMP CUrlDownload::Authenticate(HWND *phwnd, LPWSTR *ppszUsername, LPWSTR *ppszPassword)
{
    HRESULT hr;
    ASSERT(phwnd && ppszUsername && ppszPassword);
    
    *phwnd = (HWND)-1;
    *ppszUsername = NULL;
    *ppszPassword = NULL;

    if (m_pParent)
        hr = m_pParent->OnAuthenticate(phwnd, ppszUsername, ppszPassword);
    else
        hr = E_NOTIMPL;

    TraceMsg(TF_THISMODULE, "CUrlDownload::Authenticate returning hr=%08x", hr);

    return hr;
}

//---------------------------------------------------------------
// IHlinkFrame
STDMETHODIMP CUrlDownload::SetBrowseContext(IHlinkBrowseContext *pihlbc)
{
    DBG_WARN("CUrlDownload::SetBrowseContext() not implemented");
    return E_NOTIMPL;
}
STDMETHODIMP CUrlDownload::GetBrowseContext(IHlinkBrowseContext **ppihlbc)
{
    DBG_WARN("CUrlDownload::GetBrowseContext() not implemented");
    return E_NOTIMPL;
}
STDMETHODIMP CUrlDownload::Navigate(DWORD grfHLNF, LPBC pbc, IBindStatusCallback *pibsc, IHlink *pihlNavigate)
{
    // We should only get a call through IHlinkFrame->Navigate()
    // when the webcrawler has submitted a form for authentication.
    // Bail out if that's not the case.
    if (!m_fFormSubmitted)
    {
        DBG_WARN("CUrlDownload::Navigate() without a form submission!!!");
        return E_NOTIMPL;
    }

    // Our timer has already been started. If this fails, OnDownloadComplete will get
    //  called when we time out.

    // We don't support a wide variety of parameters.
    ASSERT(grfHLNF == 0);
    ASSERT(pbc);
    ASSERT(pibsc);
    ASSERT(pihlNavigate);

    // Get the moniker from IHlink
    HRESULT hr;
    IMoniker *pmk = NULL;
    hr = pihlNavigate->GetMonikerReference(HLINKGETREF_ABSOLUTE, &pmk, NULL);
    if (SUCCEEDED(hr))
    {
        // Load the URL with the post data.
        // WARNING: What if we get redirected to something other than HTML? (beta 2)
        hr = m_pPersistMk->Load(FALSE, pmk, pbc, 0);
        SAFERELEASE(pmk);
        if (SUCCEEDED(hr))
        {
            m_fBrowserValid = TRUE;
            StartTimer();       // Start our timeout
            // Need to wait again.
            m_fWaitingForReadyState = TRUE;
            DBG("CUrlDownload::Navigate (IHLinkFrame) succeeded");
        }
    }
    return hr;
}
STDMETHODIMP CUrlDownload::OnNavigate(DWORD grfHLNF, IMoniker *pimkTarget, LPCWSTR pwzLocation, LPCWSTR pwzFriendlyName, DWORD dwreserved)
{
    DBG_WARN("CUrlDownload::OnNavigate() not implemented");
    return E_NOTIMPL;
}
STDMETHODIMP CUrlDownload::UpdateHlink(ULONG uHLID, IMoniker *pimkTarget, LPCWSTR pwzLocation, LPCWSTR pwzFriendlyName)
{
    DBG_WARN("CUrlDownload::UpdateHlink() not implemented");
    return E_NOTIMPL;
}

//---------------------------------------------------------------------
// IInternetSecurityManager interface
// Used to override security to allow form submits, for form auth sites
HRESULT CUrlDownload::SetSecuritySite(IInternetSecurityMgrSite *pSite)
{
    return E_NOTIMPL;
}

HRESULT CUrlDownload::GetSecuritySite(IInternetSecurityMgrSite **ppSite)
{
    return E_NOTIMPL;
}

HRESULT CUrlDownload::MapUrlToZone(LPCWSTR pwszUrl, DWORD *pdwZone, DWORD dwFlags)
{
    return INET_E_DEFAULT_ACTION;
}

HRESULT CUrlDownload::GetSecurityId(LPCWSTR pwszUrl, BYTE *pbSecurityId, DWORD *pcbSecurityId, DWORD_PTR dwReserved)
{
    return INET_E_DEFAULT_ACTION;
}

HRESULT CUrlDownload::ProcessUrlAction(LPCWSTR pwszUrl, DWORD dwAction, BYTE __RPC_FAR *pPolicy, DWORD cbPolicy, BYTE *pContext, DWORD cbContext, DWORD dwFlags, DWORD dwReserved)
{
    if ((dwAction == URLACTION_HTML_SUBMIT_FORMS_TO) ||
        (dwAction == URLACTION_HTML_SUBMIT_FORMS_FROM))
    {
        return S_OK;
    }
    
    return INET_E_DEFAULT_ACTION;
}

HRESULT CUrlDownload::QueryCustomPolicy(LPCWSTR pwszUrl, REFGUID guidKey, BYTE **ppPolicy, DWORD *pcbPolicy, BYTE *pContext, DWORD cbContext, DWORD dwReserved)
{
    return INET_E_DEFAULT_ACTION;
}

HRESULT CUrlDownload::SetZoneMapping(DWORD dwZone, LPCWSTR lpszPattern, DWORD dwFlags)
{
    return INET_E_DEFAULT_ACTION;
}

HRESULT CUrlDownload::GetZoneMappings(DWORD dwZone, IEnumString **ppenumString, DWORD dwFlags)
{
    return INET_E_DEFAULT_ACTION;
}


//---------------------------------------------------------------
// CUrlDownload_BSC class
//---------------------------------------------------------------

CUrlDownload_BSC::CUrlDownload_BSC(
    BDUMethod   iMethod,
    BDUOptions  iOptions,
    LPTSTR      pszLocalFile)
{
    // Maintain global count of objects
    DllAddRef();

    m_cRef = 1;

    m_iMethod = iMethod;
    m_iOptions = iOptions;

    if (NULL != pszLocalFile)
    {
        m_pszLocalFileDest = StrDup(pszLocalFile);
        if (m_iMethod != BDU2_URLMON)
        {
            DBG_WARN("CUrlDownload_BSC changing method to URLMON (local file specified)");
            m_iMethod = BDU2_URLMON;
        }
    }
}

CUrlDownload_BSC::~CUrlDownload_BSC()
{
    // Maintain global count of objects
    DllRelease();

    ASSERT(!m_pBinding);
    SAFERELEASE(m_pstm);
    SAFELOCALFREE(m_pszLocalFileDest);
    SAFELOCALFREE(m_pwszLocalFileSrc);
}

void CUrlDownload_BSC::SetParent(CUrlDownload *pUrlDownload)
{
    m_pParent = pUrlDownload;
}

HRESULT CUrlDownload_BSC::Abort()
{
    if (m_pBinding)
    {
        return m_pBinding->Abort();
    }
    return S_FALSE;
}

STDMETHODIMP CUrlDownload_BSC::QueryInterface(REFIID riid, void** ppv)
{
    *ppv = NULL;

    if (riid==IID_IUnknown || riid==IID_IBindStatusCallback)
    {
        *ppv = (IBindStatusCallback *)this;
        AddRef();
        return S_OK;
    }
    if (riid==IID_IHttpNegotiate)
    {
        *ppv = (IHttpNegotiate *)this;
        AddRef();
        return S_OK;
    }
    if (riid==IID_IAuthenticate)
    {
        *ppv = (IAuthenticate *)this;
        AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

//---------------------------------------------------------------
// IAuthenticate
STDMETHODIMP CUrlDownload_BSC::Authenticate(HWND *phwnd, LPWSTR *ppszUsername, LPWSTR *ppszPassword)
{   //copied from CUrlDownload::Authenticate (to whom we pass off anyway)
    HRESULT hr;
    ASSERT(phwnd && ppszUsername && ppszPassword);
    
    *phwnd = (HWND)-1;
    *ppszUsername = NULL;
    *ppszPassword = NULL;

    // Only try this once. If Urlmon asks again, fail it and flag an error.
    if (m_fTriedAuthenticate)
    {
        if (m_pParent)
        {
            m_pParent->m_dwResponseCode = 401;
            DBG("CUrlDownload_BSC::Authenticate called twice. Faking 401 response");
        }

        return E_FAIL;
    }

    m_fTriedAuthenticate = TRUE;

    if (m_pParent)
        hr = m_pParent->Authenticate(phwnd, ppszUsername, ppszPassword);
    else
        hr = E_NOTIMPL;

    if (FAILED(hr) && m_pParent)
    {
        m_pParent->m_dwResponseCode = 401;
        DBG("CUrlDownload_BSC::Authenticate called; no username/pass. Faking 401 response");
    }

    TraceMsg(TF_THISMODULE, "CUrlDownload_BSC::Authenticate returning hr=%08x", hr);

    return hr;
}

STDMETHODIMP CUrlDownload_BSC::OnStartBinding(
    DWORD dwReserved,
    IBinding* pbinding)
{
    m_fSentMimeType = FALSE;
    if (m_pBinding != NULL)
        m_pBinding->Release();
    m_pBinding = pbinding;
    if (m_pBinding != NULL)
    {
        m_pBinding->AddRef();
    }
    if (m_pParent)
        m_pParent->BSC_OnStartBinding();
    return S_OK;
}

// ---------------------------------------------------------------------------
// %%Function: CUrlDownload_BSC::GetPriority
// ---------------------------------------------------------------------------
 STDMETHODIMP
CUrlDownload_BSC::GetPriority(LONG* pnPriority)
{
    return E_NOTIMPL;
}

// ---------------------------------------------------------------------------
// %%Function: CUrlDownload_BSC::OnLowResource
// ---------------------------------------------------------------------------
 STDMETHODIMP
CUrlDownload_BSC::OnLowResource(DWORD dwReserved)
{
    return E_NOTIMPL;
}

// ---------------------------------------------------------------------------
// %%Function: CUrlDownload_BSC::OnProgress
// ---------------------------------------------------------------------------
 STDMETHODIMP
CUrlDownload_BSC::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
//  TraceMsg(TF_THISMODULE, "cbsc::OnProgress %d of %d : msg %ws", ulProgress, ulProgressMax, szStatusText);

    /*
    if (ulStatusCode==BINDSTATUS_USINGCACHEDCOPY)
    */
    if (ulStatusCode == BINDSTATUS_REDIRECTING)
    {
        DBG("CUrlDownload_BSC::OnProgress getting redirected url");
        TraceMsg(TF_THISMODULE, "New url=%ws", szStatusText);
        if (m_pParent)
        {
            if (m_pParent->m_pwszURL) MemFree(m_pParent->m_pwszURL);
            m_pParent->m_pwszURL = StrDupW(szStatusText);
        }
    }

    if ((ulStatusCode == BINDSTATUS_CACHEFILENAMEAVAILABLE) && m_pszLocalFileDest)
    {
        ASSERT(!m_pwszLocalFileSrc);
        DBG("CUrlDownload_BSC::OnProgress Getting local file name");
        if (!m_pwszLocalFileSrc)
            m_pwszLocalFileSrc = StrDupW(szStatusText);
    }

    if (m_pParent)
        m_pParent->BSC_OnProgress(ulProgress, ulProgressMax);

    // 14032: If dialmon is around, tell it that something is going on
    IndicateDialmonActivity();

    return S_OK;
}

STDMETHODIMP CUrlDownload_BSC::OnStopBinding(
    HRESULT     hrStatus,
    LPCWSTR     pszError)
{
#ifdef DEBUG
    if (hrStatus && (hrStatus != E_ABORT))
        TraceMsg(TF_THISMODULE,
            "cbsc: File download Failed hr=%08x.", (int)hrStatus);
#endif

    if (m_pParent)
        m_pParent->BSC_OnStopBinding(hrStatus, (m_iOptions&BDU2_NEEDSTREAM) ? m_pstm : NULL);

    // We should have neither or both of these
    ASSERT(!m_pwszLocalFileSrc == !m_pszLocalFileDest);

    if (m_pwszLocalFileSrc && m_pszLocalFileDest)
    {
        // Copy or move file from cache file to file/directory requested
        // We have a LPWSTR source name and an LPTSTR destination
        TCHAR szSrc[MAX_PATH];
        TCHAR szDest[MAX_PATH];
        LPTSTR pszSrcFileName, pszDest=NULL;

        MyOleStrToStrN(szSrc, MAX_PATH, m_pwszLocalFileSrc);

        // Combine paths to find destination filename if necessary
        if (PathIsDirectory(m_pszLocalFileDest))
        {
            pszSrcFileName = PathFindFileName(szSrc);
            if (pszSrcFileName)
            {
                PathCombine(szDest, m_pszLocalFileDest, pszSrcFileName);
                pszDest = szDest;
            }
        }
        else
        {
            pszDest = m_pszLocalFileDest;
        }

        if (pszDest)
        {
            TraceMsg(TF_THISMODULE, "Copying file\n%s\n to file \n%s", szSrc, pszDest);
            CopyFile(szSrc, pszDest, FALSE);
        }
        else
            DBG_WARN("Unable to get dest path for local file");
    }

    SAFERELEASE(m_pstm);
    SAFERELEASE(m_pBinding);

    return S_OK;
}

STDMETHODIMP CUrlDownload_BSC::GetBindInfo(
    DWORD       *pgrfBINDF,
    BINDINFO    *pbindInfo)
{
    if ( !pgrfBINDF || !pbindInfo || !pbindInfo->cbSize )
        return E_INVALIDARG;

    *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_NO_UI;
    if (m_pszLocalFileDest)
        *pgrfBINDF |= BINDF_NEEDFILE;
    if (m_pParent && m_pParent->m_fSetResync)
        *pgrfBINDF |= BINDF_RESYNCHRONIZE;
    if (m_pParent && (m_pParent->m_lBindFlags & DLCTL_FORCEOFFLINE))
        *pgrfBINDF |= BINDF_OFFLINEOPERATION;

    // clear BINDINFO but keep its size
    DWORD cbSize = pbindInfo->cbSize;
    ZeroMemory( pbindInfo, cbSize );
    pbindInfo->cbSize = cbSize;

    pbindInfo->dwBindVerb = BINDVERB_GET;

    if (m_iMethod == BDU2_HEADONLY)
    {
        LPWSTR pwszVerb = (LPWSTR) CoTaskMemAlloc(sizeof(c_wszHeadVerb));
        if (pwszVerb)
        {
            CopyMemory(pwszVerb, c_wszHeadVerb, sizeof(c_wszHeadVerb));
            pbindInfo->dwBindVerb = BINDVERB_CUSTOM;
            pbindInfo->szCustomVerb = pwszVerb;
            DBG("Using 'HEAD' custom bind verb.");
        }
        else
        {
            DBG_WARN("MemAlloc failure CUrlDownload_BSC::GetBindInfo");
            return E_OUTOFMEMORY;
        }
    }

    return S_OK;
}

STDMETHODIMP CUrlDownload_BSC::OnDataAvailable(
    DWORD grfBSCF,
    DWORD dwSize,
    FORMATETC* pfmtetc,
    STGMEDIUM* pstgmed)
{
    TraceMsg(TF_THISMODULE, "%d bytes on page so far (urlmon)", dwSize);

    if (m_pParent)
        if (FAILED(m_pParent->ProgressBytes(dwSize)))
            return S_OK;

        // Get the Stream passed if we want a local file (to lock the file)
    // We just ignore any data in any case
    if (BSCF_FIRSTDATANOTIFICATION & grfBSCF)
    {
        if (!m_pstm && (pstgmed->tymed==TYMED_ISTREAM) &&
            (m_pszLocalFileDest || (m_iOptions & BDU2_NEEDSTREAM)))
        {
            m_pstm = pstgmed->pstm;
            if (m_pstm)
                m_pstm->AddRef();
        }
    }

    if (!m_fSentMimeType && pfmtetc && m_pParent)
    {
        m_pParent->BSC_FoundMimeType(pfmtetc->cfFormat);
        m_fSentMimeType = TRUE;
    }

    if (BSCF_LASTDATANOTIFICATION & grfBSCF)
    {
        DBG("cbsc: LastDataNotification");
    }

    return S_OK;
}  // CUrlDownload_BSC::OnDataAvailable

STDMETHODIMP CUrlDownload_BSC::OnObjectAvailable(REFIID riid, IUnknown* punk)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload_BSC::BeginningTransaction(
        LPCWSTR szURL,      LPCWSTR szHeaders,
        DWORD dwReserved,   LPWSTR *pszAdditionalHeaders)
{
    // Add User-Agent and Accept-Language headers
    DBG("CUrlDownload_BSC::BeginningTransaction returning headers");

    LPCWSTR pwszAcceptLanguage;
    int iUAlen=0, iALlen=0;     // in chars, with \r\n, without null-term
    LPWSTR pwsz;
    LPCWSTR pwszUA = m_pParent ? m_pParent->GetUserAgent() : NULL;
    
    pwszAcceptLanguage = (m_pParent) ? m_pParent->GetAcceptLanguages() : NULL;

    if (pwszUA)
    {
        iUAlen = ARRAYSIZE(c_szUserAgentPrefix) + lstrlenW(pwszUA) + 1;
    }
    
    if (pwszAcceptLanguage)
    {
        iALlen = ARRAYSIZE(c_szAcceptLanguagePrefix) + lstrlenW(pwszAcceptLanguage)+1;
    }

    if (iUAlen || iALlen)
    {
        pwsz = (WCHAR *)CoTaskMemAlloc((iUAlen + iALlen + 1) * sizeof(WCHAR));

        if (pwsz)
        {
            pwsz[0] = L'\0';
            
            if (iUAlen)
            {
                StrCpyW(pwsz, c_szUserAgentPrefix);
                StrCatW(pwsz, pwszUA);
                StrCatW(pwsz, L"\r\n");
            }

            if (iALlen)
            {
                StrCatW(pwsz, c_szAcceptLanguagePrefix);
                StrCatW(pwsz, pwszAcceptLanguage);
                StrCatW(pwsz, L"\r\n");
            }

            ASSERT(lstrlenW(pwsz) == (iUAlen + iALlen));

            *pszAdditionalHeaders = pwsz;

            return S_OK;
        }
    }

    return E_OUTOFMEMORY;
}
    
STDMETHODIMP CUrlDownload_BSC::OnResponse(
        DWORD   dwResponseCode,     LPCWSTR szResponseHeaders, 
        LPCWSTR szRequestHeaders,   LPWSTR *pszAdditionalRequestHeaders)
{
    TraceMsg(TF_THISMODULE, "CUrlDownload_BSC::OnResponse - %d", dwResponseCode);

    // If we sent a "HEAD" request, Urlmon will hang expecting data.
    // Abort it here.
    if (m_iMethod == BDU2_HEADONLY)
    {
        // First get the Last-Modified date from Urlmon
        IWinInetHttpInfo    *pInfo;

        if (m_pParent
            && SUCCEEDED(m_pBinding->QueryInterface(IID_IWinInetHttpInfo, (void **)&pInfo)
            && pInfo))
        {
            SYSTEMTIME  st;
            DWORD       dwSize = sizeof(st), dwZero=0;

            if (SUCCEEDED(pInfo->QueryInfo(HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED,
                                           (LPVOID) &st, &dwSize, &dwZero, 0)))
            {
                m_pParent->BSC_FoundLastModified(&st);
            }

            pInfo->Release();
        }
        Abort();    // FEATURE: return E_ABORT and handle abort internally
    }

    if (m_pParent)
        m_pParent->m_dwResponseCode = dwResponseCode;
    else
        DBG_WARN("CUrlDownload_BSC::OnResponse - Parent already NULL");

    return S_OK;
}

//
// IOleClientSite
//
STDMETHODIMP CUrlDownload:: SaveObject(void)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload:: GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk)
{
    DBG("CUrlDownload::GetMoniker returning failure");
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload:: GetContainer(IOleContainer **ppContainer)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload:: ShowObject(void)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload:: OnShowWindow(BOOL fShow)
{
    return E_NOTIMPL;
}

STDMETHODIMP CUrlDownload:: RequestNewObjectLayout(void)
{
    return E_NOTIMPL;
}



// ParseRefreshContent was lifted in its entirety from shdocvw\basesb.cpp
BOOL ParseRefreshContent(LPWSTR pwzContent,
    UINT * puiDelay, LPWSTR pwzUrlBuf, UINT cchUrlBuf)
{
    // We are parsing the following string:
    //
    //  [ws]* [0-9]+ [ws]* ; [ws]* url [ws]* = [ws]* { ' | " } [any]* { ' | " }
    //
    // Netscape insists that the string begins with a delay.  If not, it
    // ignores the entire directive.  There can be more than one URL mentioned,
    // and the last one wins.  An empty URL is treated the same as not having
    // a URL at all.  An empty URL which follows a non-empty URL resets
    // the previous URL.

    enum { PRC_START, PRC_DIG, PRC_DIG_WS, PRC_SEMI, PRC_SEMI_URL,
        PRC_SEMI_URL_EQL, PRC_SEMI_URL_EQL_ANY };
    #define ISSPACE(ch) (((ch) == 32) || ((unsigned)((ch) - 9)) <= 13 - 9)

    UINT uiState = PRC_START;
    UINT uiDelay = 0;
    LPWSTR pwz = pwzContent;
    LPWSTR pwzUrl = NULL;
    UINT   cchUrl = 0;
    WCHAR  wch,  wchDel = 0;

    *pwzUrlBuf = 0;

    do
    {
        wch = *pwz;

        switch (uiState)
        {
            case PRC_START:
                if (wch >= TEXT('0') && wch <= TEXT('9'))
                {
                    uiState = PRC_DIG;
                    uiDelay = wch - TEXT('0');
                }
                else if (!ISSPACE(wch))
                    goto done;
                break;

            case PRC_DIG:
                if (wch >= TEXT('0') && wch <= TEXT('9'))
                    uiDelay = uiDelay * 10 + wch - TEXT('0');
                else if (ISSPACE(wch))
                    uiState = PRC_DIG_WS;
                else if (wch == TEXT(';'))
                    uiState = PRC_SEMI;
                else
                    goto done;
                break;

            case PRC_DIG_WS:
                if (wch == TEXT(';'))
                    uiState = PRC_SEMI;
                else if (!ISSPACE(wch))
                    goto done;
                break;

            case PRC_SEMI:
                if (    (wch == TEXT('u') || wch == TEXT('U'))
                    &&  (pwz[1] == TEXT('r') || pwz[1] == TEXT('R'))
                    &&  (pwz[2] == TEXT('l') || pwz[2] == TEXT('L')))
                {
                    uiState = PRC_SEMI_URL;
                    pwz += 2;
                }
                else if (!ISSPACE(wch) && wch != TEXT(';'))
                    goto done;
                break;

            case PRC_SEMI_URL:
                if (wch == TEXT('='))
                {
                    uiState = PRC_SEMI_URL_EQL;
                    *pwzUrlBuf = 0;
                }
                else if (wch == TEXT(';'))
                    uiState = PRC_SEMI;
                else if (!ISSPACE(wch))
                    goto done;
                break;

            case PRC_SEMI_URL_EQL:
                if (wch == TEXT(';'))
                    uiState = PRC_SEMI;
                else if (!ISSPACE(wch))
                {
                    uiState = PRC_SEMI_URL_EQL_ANY;

                    pwzUrl = pwzUrlBuf;
                    cchUrl = cchUrlBuf;

                    if (wch == TEXT('\'')|| wch == TEXT('\"'))
                        wchDel = wch;
                    else
                    {
                        wchDel = 0;
                        *pwzUrl++ = wch;
                        cchUrl--;
                    }
                }
                break;
                        
            case PRC_SEMI_URL_EQL_ANY:
                if (    !wch
                    ||  ( wchDel && wch == wchDel)
                    ||  (!wchDel && wch == L';'))
                {
                    *pwzUrl = 0;
                    uiState = wch == TEXT(';') ? PRC_SEMI : PRC_DIG_WS;
                }
                else if (cchUrl > 1)
                {
                    *pwzUrl++ = wch;
                    cchUrl--;
                }
                break;
        }

        ++pwz;

    } while (wch);

done:

    *puiDelay = uiDelay;

    return(uiState >= PRC_DIG);
} // ParseRefreshContent
