/* Copyright 1996 Microsoft */

#include "priv.h"

#ifdef DEBUG

#define ENTERPROC EnterProc
#define EXITPROC ExitProc

void EnterProc(DWORD dwTraceLevel, LPTSTR szFmt, ...);
void ExitProc(DWORD dwTraceLevel, LPTSTR szFmt, ...);

extern DWORD g_dwIEDDETrace;

#else

#pragma warning(disable:4002)

#ifndef UNIX

#ifndef CCOVER
#define ENTERPROC()
#define EXITPROC()
#else //CCOVER

// these are needed because of a bug in cl.exe which causes 
// compilation problems with #pragma when a program is preprocessed
// and compiled separately

#define ENTERPROC 1 ? (void) 0 : (void)
#define EXITPROC 1 ? (void) 0 : (void)
#endif // CCOVER

#else
#define ENTERPROC EnterProc
#define EXITPROC ExitProc
inline void EnterProc(DWORD dwTraceLevel, LPTSTR szFmt, ...){}
inline void ExitProc(DWORD dwTraceLevel, LPTSTR szFmt, ...){}
#endif

#endif

//
// Forward reference.
//
class CIEDDEThread;

//
// Stored in _hdsaWinitem
//
typedef struct _tagWinItem
{
    DWORD           dwWindowID;     // Synthetic window ID exposed in IEDDE interfaces
    HWND            hwnd;           // Actual hwnd of browser window
    DWORD           dwThreadID;     // ThreadID for this browser window
    CIEDDEThread    *pidt;          // Thread specific data and methods
} WINITEM;

//
// Stored in _hdsaProtocolHandler
//
typedef struct _tagProtocolReg
{
    LPTSTR  pszProtocol;
    LPTSTR  pszServer;
} PROTOCOLREG;

#define TEN_SECONDS         (10 * 1000)
#define DXA_GROWTH_AMOUNT   (10)

#ifndef UNIX
#define IEXPLORE_STR "IEXPLORE"
#else
#define IEXPLORE_STR "iexplorer"
#endif

static const TCHAR c_szIExplore[] = TEXT(IEXPLORE_STR);
static const TCHAR c_szReturn[] = TEXT("Return");
static const TCHAR c_szWWWOpenURL[] = TEXT("WWW_OpenURL");
static const TCHAR c_szWWWUrlEcho[] = TEXT("WWW_URLEcho");

typedef struct _tagDDETHREADINFO
{
    DWORD       dwDDEInst;
    HSZ         hszService;
    HSZ         hszReturn;
    HDDEDATA    hddNameService;
} DDETHREADINFO;

class CIEDDEThread {
public:
    CIEDDEThread() { };
    ~CIEDDEThread() { };

    void GetDdeThreadInfo(DDETHREADINFO *pdti) { *pdti = _dti; }
    void SetDdeThreadInfo(DDETHREADINFO *pdti) { _dti = *pdti; }
    HDDEDATA OnRequestPoke(HSZ hszTopic, HSZ hszParams);
    HDDEDATA OnExecute(HSZ hszTopic, HDDEDATA hddParams);

    HDDEDATA CallTopic(DWORD dwType, LPCTSTR pszTopic, LPTSTR pszParams);

protected:
    DDETHREADINFO   _dti;

    HDDEDATA DoNavigate(LPTSTR pszLocation, HWND hwnd, BOOL bLaunchNewWindow);
    BOOL MakeQuotedString(LPCTSTR pszInput, LPTSTR pszOutput, int cchOutput);
    HDDEDATA CreateReturnObject(LPVOID p, DWORD cb);
    HDDEDATA CreateReturnStringObject(LPTSTR pszReturnString, DWORD cch);


    BOOL ParseString(LPTSTR *ppsz, LPTSTR *ppszString);
    BOOL ParseQString(LPTSTR *ppsz, LPTSTR *ppszString);
    BOOL ParseNumber(LPTSTR *ppsz, DWORD *pdw);
    BOOL ParseWinitem(LPTSTR *ppsz, WINITEM *pwi);

    HDDEDATA WWW_GetWindowInfo(LPTSTR pszParams);
    HDDEDATA WWW_OpenURL(LPTSTR pszParams);
    HDDEDATA WWW_OpenURLNewWindow(LPTSTR pszParams);
    HDDEDATA WWW_ShowFile(LPTSTR pszParams);
    HDDEDATA WWW_Activate(LPTSTR pszParams);
    HDDEDATA WWW_Exit(LPTSTR pszParams);
    HDDEDATA WWW_RegisterURLEcho(LPTSTR pszParams);
    HDDEDATA WWW_UnregisterURLEcho(LPTSTR pszParams);
    HDDEDATA WWW_RegisterProtocol(LPTSTR pszParams);
    HDDEDATA WWW_UnregisterProtocol(LPTSTR pszParams);
    HDDEDATA WWW_ListWindows(LPTSTR pszParams);
};

class CIEDDE {
public:
    CIEDDE() { };
    ~CIEDDE() { };

    BOOL IsAutomationReady(void) { return _fAutomationReady; }
    BOOL GetWinitemFromWindowID(DWORD dwWindowID, WINITEM *pwi);
    BOOL GetWinitemFromHwnd(HWND hwnd, WINITEM *pwi);
    BOOL AddUrlEcho(LPCTSTR pszUrlEcho);
    BOOL RemoveUrlEcho(LPCTSTR pszUrlEcho);
    BOOL AddProtocolHandler(LPCTSTR pszServer, LPCTSTR pszProtocol);
    BOOL RemoveProtocolHandler(LPCTSTR pszServer, LPCTSTR pszProtocol);
    HDSA GetHdsaWinitem(void) { return _hdsaWinitem; }
    static HDDEDATA DdeCallback(UINT dwType, UINT dwFmt, HCONV hconv, HSZ hsz1, HSZ hsz2, HDDEDATA hdd, DWORD dwData1, DWORD dwData2);
    void EnterCrit(void) { ASSERT(_fCSInitialized); EnterCriticalSection(&_csIEDDE); }
    void LeaveCrit(void) { ASSERT(_fCSInitialized); LeaveCriticalSection(&_csIEDDE); }
    void SetDelayedExecute(LPCTSTR pszTopic, LPCTSTR pszParams);
    void RunDelayedExecute();
    
protected:
    BOOL _fAutomationReady;
    HDSA _hdsaWinitem;
    HDSA _hdsaProtocolHandler;
    HDPA _hdpaUrlEcho;
    BOOL _fCSInitialized;
    CRITICAL_SECTION _csIEDDE;
    DWORD _dwThreadID;
    LPTSTR _pszTopic;
    LPTSTR _pszParams;

    HDDEDATA _SendDDEMessageHsz(DWORD dwDDEInst, HSZ hszApp, HSZ hszTopic, HSZ hszMessage, UINT wType);
    HDDEDATA _SendDDEMessageSz(DWORD dwDDEInst, LPCTSTR pszApp, LPCTSTR pszTopic, LPCTSTR pszMessage, UINT wType);

    static int _DestroyProtocol(LPVOID p1, LPVOID p2);
    static int _DestroyUrlEcho(LPVOID p1, LPVOID p2);
    static int _DestroyWinitem(LPVOID p1, LPVOID p2);

    BOOL _GetWinitemFromThread(DWORD dwThreadID, WINITEM *pwi);
    BOOL _GetDtiFromThread(DWORD dwThreadID, DDETHREADINFO *pdti);

    BOOL _CreateDdeThreadInfo(DDETHREADINFO *pdti);
    void _DestroyDdeThreadInfo(DDETHREADINFO *pdti);
    BOOL _AddWinitem(WINITEM *pwi);
    BOOL _UpdateWinitem(WINITEM *pwi);
    BOOL _DeleteWinitemByHwnd(HWND hwnd, WINITEM *pwi);

    BOOL _Initialize(void);
    void _Uninitialize(void);
    void _AutomationStarted(void);
    HRESULT _BeforeNavigate(LPCTSTR pszURL, BOOL *pfProcessed);
    HRESULT _AfterNavigate(LPCTSTR pszURL, HWND hwnd);
    BOOL _NewWindow(HWND hwnd);
    BOOL _WindowDestroyed(HWND hwnd);

    friend BOOL IEDDE_Initialize(void);
    friend void IEDDE_Uninitialize(void);
    friend void IEDDE_AutomationStarted(void);
    friend HRESULT IEDDE_BeforeNavigate(LPCWSTR pwszURL, BOOL *pfProcessed);
    friend HRESULT IEDDE_AfterNavigate(LPCWSTR pwszURL, HWND hwnd);
    friend BOOL IEDDE_NewWindow(HWND hwnd);
    friend BOOL IEDDE_WindowDestroyed(HWND hwnd);
};
CIEDDE *g_pIEDDE = NULL;

#define ENTER_IEDDE_CRIT g_pIEDDE->EnterCrit()
#define LEAVE_IEDDE_CRIT g_pIEDDE->LeaveCrit()



//
// There is one CIEDDEThread object per browser window.
// Its private data consists of DDE handles, which are
// necessarily valid only in the thread that created them.
//
// Its methods consist of three broad categories:
//      the parser
//      the dispatcher
//      one handler for each DDE topic
//







//
// CreateReturnObject - creates a dde data item.
//
#define CREATE_HDD(x) CreateReturnObject(&x, SIZEOF(x))
HDDEDATA CIEDDEThread::CreateReturnObject(LPVOID p, DWORD cb)
{
    HDDEDATA hddRet;

    ENTERPROC(2, TEXT("CreateReturnObject(p=%08X,cb=%d)"), p, cb);

    hddRet = DdeCreateDataHandle(_dti.dwDDEInst, (BYTE *)p, cb, 0, _dti.hszReturn, CF_TEXT, 0);

    if (hddRet == 0)
    {
        TraceMsg(TF_WARNING, "IEDDE: Could not create return object");
    }

    EXITPROC(2, TEXT("CreateReturnObject=%08X"), hddRet);
    return hddRet;
}

HDDEDATA CIEDDEThread::CreateReturnStringObject(LPTSTR pszReturnString, DWORD cch)
{
    HDDEDATA hddRet = 0;

    ENTERPROC(2, TEXT("CreateReturnStringObject(p=%s,cb=%d)"), pszReturnString, cch);

    //
    // REVIEW I thought specifying CF_UNICODETEXT should have worked, but... 
    // it didn't, so always return ANSI string as out string params
    // - julianj
    //
    LPSTR pszAnsiBuf = (LPSTR)LocalAlloc(LPTR, cch+1);
    if (pszAnsiBuf)
    {
        SHUnicodeToAnsi(pszReturnString, pszAnsiBuf, cch+1);
        hddRet = DdeCreateDataHandle(_dti.dwDDEInst, (BYTE *)pszAnsiBuf, (cch+1), 0, _dti.hszReturn, CF_TEXT, 0);
        LocalFree(pszAnsiBuf);
        pszAnsiBuf = NULL;
    }
    
    if (hddRet == 0)
    {
        TraceMsg(TF_WARNING, "IEDDE: Could not create return object");
    }

    EXITPROC(2, TEXT("CreateReturnObject=%08X"), hddRet);
    return hddRet;
}


//
// OnRequestPoke - handle XTYP_REQUEST and XTYP_POKE
//
HDDEDATA CIEDDEThread::OnRequestPoke(HSZ hszTopic, HSZ hszParams)
{
    HDDEDATA hddRet = 0;
    ENTERPROC(2, TEXT("OnRequestPoke(hszTopic=%08X,hszParams=%08X)"), hszTopic, hszParams);

    TCHAR szTopic[100];
    TCHAR szParams[1000];

    if (DdeQueryString(_dti.dwDDEInst, hszTopic, szTopic, ARRAYSIZE(szTopic), CP_WINNEUTRAL) != 0)
    {
        if (DdeQueryString(_dti.dwDDEInst, hszParams, szParams, ARRAYSIZE(szParams), CP_WINNEUTRAL))
        {
            hddRet = CallTopic(XTYP_REQUEST, szTopic, szParams);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: OnRequestPoke could not query the parameters");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: OnRequestPoke could not query the topic");
    }

    EXITPROC(2, TEXT("OnRequestPoke=%08X"), hddRet);
    return hddRet;
}

//
// OnExecute - handle XTYP_EXECUTE
//
HDDEDATA CIEDDEThread::OnExecute(HSZ hszTopic, HDDEDATA hddParams)
{
    HDDEDATA hddRet = 0;
    ENTERPROC(2, TEXT("OnExecute(hszTopic=%08X,hddParams=%08X)"), hszTopic, hddParams);

    TCHAR szTopic[100];

    if (DdeQueryString(_dti.dwDDEInst, hszTopic, szTopic, ARRAYSIZE(szTopic), CP_WINNEUTRAL) != 0)
    {
        //
        // Why "cbParams + 3"?
        // UNICODE - if we cut the last unicode character in half, we need
        //           one 0 to finish the character, and two more 0 for the
        //           terminating NULL
        // ANSI - if we cut the last DBCS character in half, we need one 0
        //        to finish the character, and one 0 for the terminating NULL
        //
        //
        DWORD cbParams = DdeGetData(hddParams, NULL, 0, 0) + 3;
        LPTSTR pszParams = (LPTSTR) LocalAlloc(LPTR, cbParams);

        if(pszParams)
        {
            DdeGetData(hddParams, (BYTE *)pszParams, cbParams, 0);
            //
            // DdeGetData can't be wrapped in shlwapi since it can return non
            // string data.  Here we only expect strings so the result can be
            // safely converted.
            //
            if (g_fRunningOnNT)
            {
                hddRet = CallTopic(XTYP_EXECUTE, szTopic, pszParams);
            }
            else
            {
                WCHAR szParams[MAX_URL_STRING];
                SHAnsiToUnicode((LPCSTR)pszParams, szParams, ARRAYSIZE(szParams));
                hddRet = CallTopic(XTYP_EXECUTE, szTopic, szParams);
            }
            LocalFree(pszParams);
            pszParams = NULL;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: OnExecute could not query the topic");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: OnExecute could not query the topic");
    }

    EXITPROC(2, TEXT("OnExecute=%08X"), hddRet);
    return hddRet;
}

//
// CallTopic - Looks up the command in the DDETOPICHANDLER table and calls the
// corresponding function.
//
HDDEDATA CIEDDEThread::CallTopic(DWORD dwType, LPCTSTR pszTopic, LPTSTR pszParams)
{
    HDDEDATA hddRet = DDE_FNOTPROCESSED;
    ENTERPROC(2, TEXT("CallTopic(wType=%d,pszTopic=>%s<,pszParams=>%s<)"), dwType, pszTopic, pszParams);

#define DISPATCH_BEGIN
#define DISPATCH(topic)                                 \
    if (StrCmpI(TEXT("WWW_") TEXT(#topic), pszTopic) == 0)   \
    {                                                   \
        if (fCanRun)                                    \
        {                                               \
            hddRet = WWW_ ## topic(pszParams);          \
        }                                               \
        else                                            \
        {                                               \
            fAbortedRun = TRUE;                         \
        }                                               \
    }                                                   \
    else
#define DISPATCH_END { TraceMsg(TF_WARNING, "IEDDE: CallTopic given unknown topic"); }

    BOOL fAbortedRun = FALSE;
    BOOL fCanRun = ((dwType != XTYP_EXECUTE) || g_pIEDDE->IsAutomationReady());

    DISPATCH_BEGIN
        DISPATCH(GetWindowInfo)
        DISPATCH(OpenURL)
        DISPATCH(ShowFile)
        DISPATCH(Activate)
        DISPATCH(Exit)
        DISPATCH(RegisterURLEcho)
        DISPATCH(UnregisterURLEcho)
        DISPATCH(RegisterProtocol)
        DISPATCH(UnregisterProtocol)
        DISPATCH(ListWindows)
        DISPATCH(OpenURLNewWindow)
    DISPATCH_END

    if (fAbortedRun)
    {
        if (dwType == XTYP_EXECUTE)
        {
            g_pIEDDE->SetDelayedExecute(pszTopic, pszParams);
        }
        hddRet = (HDDEDATA)DDE_FACK;
        TraceMsg(TF_WARNING, "IEDDE: CallTopic received XTYP_EXECUTE before Automation was ready - not processing");
    }

    EXITPROC(2, TEXT("CallTopic=%08X"), hddRet);
    return hddRet;
}

//
// ParseString - parse one string
//
BOOL CIEDDEThread::ParseString(LPTSTR *ppsz, LPTSTR *ppszString)
{
    BOOL fRet = FALSE;

    ENTERPROC(3, TEXT("ParseString(ppsz=%08X,ppszString=%08X)"), ppsz, ppszString);

    LPTSTR pchCurrent, pchNext;
    BOOL fInQuote = FALSE;

    pchCurrent = pchNext = *ppsz;
    while (*pchNext)
    {
        switch (*pchNext)
        {
        case TEXT(' '):
        case TEXT('\t'):
            if (fInQuote)
            {
                //
                // Skip over whitespace when not inside quotes.
                //
                *pchCurrent++ = *pchNext;
            }
            pchNext++;
            break;

        case TEXT('"'):
            //
            // Always copy quote marks.
            //
            fInQuote = !fInQuote;
            *pchCurrent++ = *pchNext++;
            break;

        case TEXT(','):
            if (!fInQuote)
            {
                goto done_parsing;
            }
            *pchCurrent++ = *pchNext++;
            break;

        case TEXT('\\'):
            if (fInQuote &&
                (*(pchNext+1) == TEXT('"')))
            {
                //
                // When in quotes, a \" becomes a ".
                //
                pchNext++;
            }
            *pchCurrent++ = *pchNext++;
            break;

        default:
            *pchCurrent++ = *pchNext++;
            break;
        }
    }
done_parsing:

    //
    // Advance past the comma separator.
    //
    if (*pchNext == TEXT(','))
    {
        pchNext++;
    }

    //
    // NULL terminate the return string.
    //
    *pchCurrent = TEXT('\0');

    //
    // Set the return values.
    //
    *ppszString = *ppsz;
    *ppsz = pchNext;
    fRet = TRUE;

    EXITPROC(3, TEXT("ParseString=%d"), fRet);
    return fRet;
}

//
// ParseQString - parse one quoted string
//
BOOL CIEDDEThread::ParseQString(LPTSTR *ppsz, LPTSTR *ppszString)
{
    BOOL fRet = FALSE;

    ENTERPROC(3, TEXT("ParseQString(ppsz=%08X,ppszString=%08X)"), ppsz, ppszString);

    if (ParseString(ppsz, ppszString))
    {
        LPTSTR pszString = *ppszString;
        int cch = lstrlen(pszString);

        //
        // Strip off optional outer quotes.
        //
        if ((cch >= 2) &&
            (pszString[0] == TEXT('"')) &&
            (pszString[cch-1] == TEXT('"')))
        {
            pszString[0] = pszString[cch-1] = TEXT('\0');
            *ppszString = pszString + 1;
        }

        fRet = TRUE;
    }

    EXITPROC(3, TEXT("ParseQString=%d"), fRet);
    return fRet;
}

//
// ParseNumber - parse one numeric value
//
BOOL CIEDDEThread::ParseNumber(LPTSTR *ppsz, DWORD *pdw)
{
    BOOL fRet = FALSE;
    LPTSTR pszNumber;

    ENTERPROC(3, TEXT("GetNumber(ppsz=%08X,pdw=%08X)"), ppsz, pdw);

    if (ParseString(ppsz, &pszNumber) && pszNumber[0])
    {
        StrToIntEx(pszNumber, STIF_SUPPORT_HEX, (int *)pdw);
        fRet = TRUE;
    }

    EXITPROC(3, TEXT("GetNumber=%d"), fRet);
    return fRet;
}

//
// ParseWinitem - parse one window ID, and return the winitem
//
BOOL CIEDDEThread::ParseWinitem(LPTSTR *ppsz, WINITEM *pwi)
{
    BOOL fRet = FALSE;
    DWORD dwWindowID;

    ENTERPROC(3, TEXT("ParseWinitem(ppsz=%08X,pwi=%08X)"), ppsz, pwi);

    if (ParseNumber(ppsz, &dwWindowID))
    {
        switch (dwWindowID)
        {
        case 0:
        case -1:
            ZeroMemory(pwi, SIZEOF(*pwi));
            pwi->dwWindowID = dwWindowID;
            pwi->hwnd = (HWND)LongToHandle(dwWindowID);
            fRet = TRUE;
            break;

        default:
            fRet = g_pIEDDE->GetWinitemFromWindowID(dwWindowID, pwi);
            break;
        }
    }

    EXITPROC(3, TEXT("ParseWinitem=%d"), fRet);
    return fRet;
}

//
//  WWW_GetWindowInfo - get information about a browser window
//
//  Parameters:
//      dwWindowID - Window ID to examine (-1 = last active window)
//
//  Returns:
//      qcsURL,qcsTitle
//
HDDEDATA CIEDDEThread::WWW_GetWindowInfo(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    WINITEM wi;

    ENTERPROC(1, TEXT("WWW_GetWindowInfo(pszParams=>%s<)"), pszParams);

    if (ParseWinitem(&pszParams, &wi) &&
        (wi.hwnd != 0))
    {
        BSTR bstrURL;

        if (SUCCEEDED(CDDEAuto_get_LocationURL(&bstrURL, wi.hwnd)) && (bstrURL != (BSTR)-1))
        {
            BSTR bstrTitle;

            if (SUCCEEDED(CDDEAuto_get_LocationTitle(&bstrTitle, wi.hwnd)) && (bstrTitle != (BSTR)-1))
            {
                LPTSTR pszURL, pszTitle;


                pszURL = bstrURL;
                pszTitle = bstrTitle;

                if (pszURL && pszTitle)
                {
                    TCHAR szURLQ[MAX_URL_STRING];
                    TCHAR szTitleQ[MAX_URL_STRING];

                    if (MakeQuotedString(pszURL, szURLQ, ARRAYSIZE(szURLQ)) &&
                        MakeQuotedString(pszTitle, szTitleQ, ARRAYSIZE(szTitleQ)))
                    {
                        DWORD cchBuffer = lstrlen(szURLQ) + 1 + lstrlen(szTitleQ) + 1;
                        LPTSTR pszBuffer = (LPTSTR)LocalAlloc(LPTR, cchBuffer * SIZEOF(TCHAR));

                        if (pszBuffer)
                        {
                            wnsprintf(pszBuffer, cchBuffer, TEXT("%s,%s"), szURLQ, szTitleQ);
                            hddRet = CreateReturnStringObject(pszBuffer, lstrlen(pszBuffer));
                            LocalFree(pszBuffer);
                            pszBuffer = NULL;
                        }
                        else
                        {
                            TraceMsg(TF_WARNING, "IEDDE: GetWindowInfo could not alloc buffer");
                        }
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: GetWindowInfo could not quote return strings");
                    }
                }

                SysFreeString(bstrTitle);
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: GetWindowInfo could not get title");
            }

            SysFreeString(bstrURL);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: GetWindowInfo could not get URL");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: GetWindowInfo could not parse parameters");
    }

    EXITPROC(1, TEXT("WWW_GetWindowInfo=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_OpenURLNewWindow - navigate to a URL (but make sure to spawn a new window)
//
//  NOTE: this code was stolen from IEDDEThread::WWW_OpenURL below
//
HDDEDATA CIEDDEThread::WWW_OpenURLNewWindow(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    LPTSTR pszUrl, pszFile;

    ENTERPROC(1, TEXT("WWW_OpenURLNewWindow(pszParams=>%s<)"), pszParams);

    if (*pszParams == TEXT('\0') || *pszParams == TEXT('*'))
    {
        // An empty string is a NOOP.
    }
    else if (ParseQString(&pszParams, &pszUrl) &&
        ParseQString(&pszParams, &pszFile))
    {
        // null hwnd & bLaunchNewWindow = TRUE means "launch a new window",
        // which is exactly what we want to do in the WWW_OpenURLNewWindow case
        hddRet = DoNavigate(pszUrl, NULL, TRUE);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: WWW_OpenURLNewWindow could not parse parameters");
    }

    EXITPROC(1, TEXT("WWW_OpenURL=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_OpenURL - navigate to a URL
//
//  Parameters:
//      qcsURL - url to navigate to
//      qcsSaveFile - [optional] file to save contents in
//      dwWindowID - Window ID to perform navigation
//      dwFlags - flags for navigation
//      qcsPostFormData - [optional] form data to post to URL
//      qcsPostMIMEType - [optional] mime type for form data
//      csProgressServer - [optional] DDE server to get progress updates
//
//  Returns:
//      dwWindowID - window which is doing the work
//
HDDEDATA CIEDDEThread::WWW_OpenURL(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    LPTSTR pszUrl, pszFile;
    WINITEM wi;

    ENTERPROC(1, TEXT("WWW_OpenURL(pszParams=>%s<)"), pszParams);

    if (*pszParams == TEXT('\0') || *pszParams == TEXT('*'))
    {
        // An empty string is a NOOP.  Needed for NT #291766
    }
    else if (ParseQString(&pszParams, &pszUrl) &&
             ParseQString(&pszParams, &pszFile))
    {
        //
        // APPCOMPAT - a missing hwnd parameter implies -1.
        //
        if (!ParseWinitem(&pszParams, &wi))
        {
            TraceMsg(TF_WARNING, "IEDDE: Some bozo isn't giving the required hwnd parameter to WWW_OpenURL, assuming -1");
            wi.hwnd = (HWND)-1;
        }

#ifdef DEBUG
        DWORD dwFlags;
        if (!ParseNumber(&pszParams, &dwFlags))
        {
            TraceMsg(TF_WARNING, "IEDDE: Some bozo isn't giving the required dwFlags parameter to WWW_OpenURL");
        }
#endif

        hddRet = DoNavigate(pszUrl, wi.hwnd, FALSE);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: OpenURL could not parse parameters");
    }

    EXITPROC(1, TEXT("WWW_OpenURL=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_ShowFile - navigate to a file
//
//  Parameters:
//      qcsFilename - file to load
//      qcsPostMIMEType - [optional] mime type for form data
//      dwWindowID - Window ID to perform navigation
//      qcsURL - URL of the same document
//
//  Returns:
//      dwWindowID - window which is doing the work
//
HDDEDATA CIEDDEThread::WWW_ShowFile(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    LPTSTR pszFilename, pszMIMEType;
    WINITEM wi;

    ENTERPROC(1, TEXT("WWW_ShowFile(pszParams=>%s<)"), pszParams);

    if (ParseQString(&pszParams, &pszFilename) && pszFilename[0])
    {
        if (!ParseQString(&pszParams, &pszMIMEType) || !pszMIMEType[0])
        {
            TraceMsg(TF_WARNING, "IEDDE: Some bozo isn't giving the required MIMEType parameter to WWW_ShowFile");
        }
        if (!ParseWinitem(&pszParams, &wi))
        {
            TraceMsg(TF_WARNING, "IEDDE: Some bozo isn't giving the required dwWindowID parameter to WWW_ShowFile, assuming -1");
            wi.hwnd = (HWND)-1;
        }

#ifdef DEBUG
        LPTSTR pszURL;

        if (!ParseQString(&pszParams, &pszURL) || !pszURL[0])
        {
            TraceMsg(TF_WARNING, "IEDDE: Some bozo isn't giving the required szURL parameter to WWW_ShowFile");
        }
#endif
        hddRet = DoNavigate(pszFilename, wi.hwnd, FALSE);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: ShowFile could not parse parameters");
    }

    EXITPROC(1, TEXT("WWW_ShowFile=%08X"), hddRet);
    return hddRet;
}

//
// DoNavigate - navigate to a location
//
HDDEDATA CIEDDEThread::DoNavigate(LPTSTR pszLocation, HWND hwnd, BOOL bLaunchNewWindow)
{
    HDDEDATA hddRet = 0;
    HRESULT hr = S_OK;
    TCHAR szParsedPath[MAX_URL_STRING+1];
    DWORD cchParsedPath = ARRAYSIZE(szParsedPath);

    ENTERPROC(2, TEXT("DoNavigate(pszLocation=>%s<,hwnd=%08X)"), pszLocation, hwnd);

    //
    // Convert URL from outside format to internal format.
    //
    if (ParseURLFromOutsideSource(pszLocation, szParsedPath, &cchParsedPath, NULL))
    {
        pszLocation = szParsedPath;
    }

    //
    // In the case of a file:// URL, convert the location to a path.
    //
    cchParsedPath = ARRAYSIZE(szParsedPath);
    if (IsFileUrlW(pszLocation) && SUCCEEDED(PathCreateFromUrl(pszLocation, szParsedPath, &cchParsedPath, 0)))
    {
        pszLocation = szParsedPath;
    }

    LPWSTR pwszPath;

    pwszPath = pszLocation;

    if (SUCCEEDED(hr))
    {
        hr = CDDEAuto_Navigate(pwszPath, &hwnd, bLaunchNewWindow ? 1 : 0);
    }

    DWORD dwServicingWindow = SUCCEEDED(hr) ? -2 : -3;

    hddRet = CREATE_HDD(dwServicingWindow);

    EXITPROC(2, TEXT("DoNavigate=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_Activate - activate a browser window
//
//  Parameters:
//      dwWindowID - Window ID to activate
//      dwFlags - should always zero
//
//  Returns:
//      dwWindowID - window ID that got activated
//
HDDEDATA CIEDDEThread::WWW_Activate(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    WINITEM wi;

    ENTERPROC(1, TEXT("WWW_Activate(pszParams=>%s<)"), pszParams);

    if (ParseWinitem(&pszParams, &wi) &&
        wi.dwWindowID != 0)
    {
#ifdef DEBUG
        DWORD dwFlags;
        if (ParseNumber(&pszParams, &dwFlags))
        {
            //
            // Netscape spec says this should always be zero.
            //
            ASSERT(dwFlags == 0);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: Some bozo isn't giving the required dwFlags parameter to WWW_Activate");
        }
#endif

        //
        // dwWindowID of -1 means use the active window.
        //
        if (wi.dwWindowID == -1)
        {
            HWND hwnd;

            CDDEAuto_get_HWND((long *)&hwnd);

            if (hwnd)
            {
                if (g_pIEDDE->GetWinitemFromHwnd(hwnd, &wi) == FALSE)
                {
                    wi.dwWindowID = (DWORD)-1;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: Activate could not find an active window");
            }
        }

        //
        // Activate the window.
        //
        if (wi.dwWindowID != -1)
        {
            if ((GetForegroundWindow() == wi.hwnd) || (SetForegroundWindow(wi.hwnd)))
            {
                if (IsIconic(wi.hwnd))
                {
                    ShowWindow(wi.hwnd, SW_RESTORE);
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: Activate could not set foreground window");
            }
            
            hddRet = CREATE_HDD(wi.dwWindowID);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: Activate could not find a browser window to activate");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: Activate could not parse parameters");
    }

    EXITPROC(1, TEXT("WWW_Activate=%08X"), hddRet);
    return hddRet;
}


//
//  WWW_Exit - close all browser windows
//
//  Parameters:
//      none
//
//  Returns:
//      none
//
HDDEDATA CIEDDEThread::WWW_Exit(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;

    ENTERPROC(1, TEXT("WWW_Exit(pszParams=>%s<)"), pszParams);

    CDDEAuto_Exit();

    EXITPROC(1, TEXT("WWW_Exit=%08X"), hddRet);
    return hddRet;
}


//
//  WWW_RegisterURLEcho - register a server for URL change notifications
//
//  Parameters:
//      qcsServer - the DDE server to get notifications
//
//  Returns:
//      fSuccess
//
HDDEDATA CIEDDEThread::WWW_RegisterURLEcho(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    BOOL fSuccess = FALSE;
    LPTSTR pszServer;

    ENTERPROC(1, TEXT("WWW_RegisterURLEcho(pszParams=>%s<)"), pszParams);

    if (ParseQString(&pszParams, &pszServer) && pszServer[0])
    {
        LPTSTR pszServerCopy = StrDup(pszServer);

        if (pszServerCopy)
        {
            if (g_pIEDDE->AddUrlEcho(pszServerCopy))
            {
                fSuccess = TRUE;
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: RegisterURLEcho could not add an URLEcho");
            }

            if (!fSuccess)
            {
                LocalFree(pszServerCopy);
                pszServerCopy = NULL;
            }
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: RegisterURLEcho could not dup a string");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: RegisterURLEcho could not parse parameters");
    }

    hddRet = CREATE_HDD(fSuccess);

    EXITPROC(1, TEXT("WWW_RegisterURLEcho=%08X"), hddRet);
    return hddRet;
}


//
//  WWW_UnregisterURLEcho - unregister a DDE server
//
//  Parameters:
//      qcsServer - the DDE server to stop getting notifications
//
//  Returns:
//      fSuccess
//
HDDEDATA CIEDDEThread::WWW_UnregisterURLEcho(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    BOOL fSuccess = FALSE;
    LPTSTR pszServer;

    ENTERPROC(1, TEXT("WWW_UnregisterURLEcho(pszParams=>%s<)"), pszParams);

    if (ParseQString(&pszParams, &pszServer) && pszServer[0])
    {
        if (g_pIEDDE->RemoveUrlEcho(pszServer))
        {
            fSuccess = TRUE;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: UnregisterURLEcho could not find the server");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: UnregisterURLEcho could not parse parameters");
    }

    hddRet = CREATE_HDD(fSuccess);

    EXITPROC(1, TEXT("WWW_UnregisterURLEcho=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_RegisterProtocol - register a server for handling a protocol
//
//  Parameters:
//      qcsServer - the DDE server to handle URLs
//      qcsProtocol - the protocol to handle
//
//  Returns:
//      fSuccess - this is the first server to register the protocol
//
HDDEDATA CIEDDEThread::WWW_RegisterProtocol(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    BOOL fSuccess = FALSE;
    LPTSTR pszServer, pszProtocol;

    ENTERPROC(1, TEXT("WWW_RegisterProtocol(pszParams=>%s<)"), pszParams);

    if (ParseQString(&pszParams, &pszServer) && pszServer[0] &&
        ParseQString(&pszParams, &pszProtocol) && pszProtocol[0])
    {
        if (g_pIEDDE->AddProtocolHandler(pszServer, pszProtocol))
        {
            fSuccess = TRUE;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: RegisterProtocol unable to register");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: RegisterProtocol could not parse parameters");
    }

    hddRet = CREATE_HDD(fSuccess);

    EXITPROC(1, TEXT("WWW_RegisterProtocol=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_UnregisterProtocol - unregister a server handling a protocol
//
//  Parameters:
//      qcsServer - the DDE server which is handling URLs
//      qcsProtocol - the protocol getting handled
//
//  Returns:
//      fSuccess - this server was registered, but now isn't
//
HDDEDATA CIEDDEThread::WWW_UnregisterProtocol(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    BOOL fSuccess = FALSE;
    LPTSTR pszServer, pszProtocol;

    ENTERPROC(1, TEXT("WWW_UnregisterProtocol(pszParams=>%s<)"), pszParams);

    if (ParseQString(&pszParams, &pszServer) && pszServer[0] &&
        ParseQString(&pszParams, &pszProtocol) && pszProtocol[0])
    {
        if (g_pIEDDE->RemoveProtocolHandler(pszServer, pszProtocol))
        {
            fSuccess = TRUE;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: UnregisterProtocol unable to unregister");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: UnregisterProtocol could not parse parameters");
    }

    hddRet = CREATE_HDD(fSuccess);

    EXITPROC(1, TEXT("WWW_UnregisterProtocol=%08X"), hddRet);
    return hddRet;
}

//
//  WWW_ListWindows - Get a list of DDE supported browser window IDs
//
//  Parameters:
//      none
//
//  Returns:
//      pdwWindowID (terminated with 0)
//
HDDEDATA CIEDDEThread::WWW_ListWindows(LPTSTR pszParams)
{
    HDDEDATA hddRet = 0;
    ENTERPROC(1, TEXT("WWW_ListWindows(pszParams=>%s<)"), pszParams);

    ENTER_IEDDE_CRIT;

    DWORD cbAlloc, *pdwWindowID;
    int cWindows = 0;
    HDSA hdsaWinitem = g_pIEDDE->GetHdsaWinitem();

    if (hdsaWinitem)
    {
        cWindows = DSA_GetItemCount(hdsaWinitem);
    }

    //
    // Note: we are following the Netscape spec (null terminated pdw) here,
    // whereas IE3 followed the Spyglass spec (pdw[0] = count of windows).
    //

    cbAlloc = (cWindows + 1) * SIZEOF(DWORD);

    pdwWindowID = (DWORD *)LocalAlloc(LPTR, cbAlloc);
    if (pdwWindowID)
    {
        DWORD *pdw;

        pdw = pdwWindowID;

        for (int i=0; i<cWindows; i++)
        {
            WINITEM wi;

            int iResult = DSA_GetItem(hdsaWinitem, i, &wi);

            if (iResult != -1)
            {
                *pdw++ = wi.dwWindowID;
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: ListWindows could not get a DSA item");
            }
        }

        hddRet = CreateReturnObject(pdwWindowID, cbAlloc);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: ListWindows could not allocate a window list");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(1, TEXT("WWW_ListWindows=%08X"), hddRet);
    return hddRet;
}

//
// MakeQuotedString - wrap a string in " marks, escaping internal "s as \"
//
BOOL CIEDDEThread::MakeQuotedString(LPCTSTR pszInput, LPTSTR pszOutput, int cchOutput)
{
    BOOL fRet = FALSE;

    ENTERPROC(2, TEXT("MakeQuotedString(pszInput=>%s<,pszOutput=%08X,cchOutput=%08X)"), pszInput, pszOutput, cchOutput);

    if (cchOutput < 3)
    {
        TraceMsg(TF_WARNING, "IEDDE: MakeQuotedString has no room for minimal quoted string");
    }
    else if ((pszInput == NULL) || (*pszInput == TEXT('\0')))
    {
        StrCpyN(pszOutput, TEXT("\"\""), cchOutput);
        fRet = TRUE;
    }
    else
    {
        //
        // Copy first quote mark.
        //
        *pszOutput++ = TEXT('"');
        cchOutput--;

        //
        // Copy pszInput, escaping quote marks and making
        // sure to leave room for final quote and NULL.
        //
        while ((cchOutput > 2) && (*pszInput))
        {
            if (*pszInput == TEXT('"'))
            {
                *pszOutput++ = TEXT('\\');
                cchOutput--;
            }
            *pszOutput++ = *pszInput++;
            cchOutput--;
        }

        //
        // Copy final quote and NULL if we're done and there is room.
        //
        if ((*pszInput == TEXT('\0')) && (cchOutput >= 2))
        {
            StrCpyN(pszOutput, TEXT("\""), cchOutput);
            fRet = TRUE;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: MakeQuotedString ran out of room in output buffer");
        }
    }

    EXITPROC(2, TEXT("MakeQuotedString=%d"), fRet);
    return fRet;
}
















#undef CIEDDEThread

//
// There is one global CIEDDE object per process.
// It maintains the global information, such as
// the list of all browsers & what threads they are on,
// and the list of all apps who have registered an URL Echo.
//
// Its methods consist of these categories:
//      the DDE callback function
//      an internal handler for each exposed IEDDE_ function
//      database (hdsa, hdpa) access and manipulation functions
//
// This object creates and destroys CIEDDEThread objects
// (at NewWindow and WindowDestroyed time) and also initializes /
// uninitializes DDE services on a per thread (not per hwnd!) basis.
//



//
// DdeCallback - DDE callback function for IEDDE.
//
#define DDETYPESTR(x) (x == XTYP_REQUEST ? TEXT("Request") : \
                       (x == XTYP_POKE ? TEXT("Poke") : \
                       (x == XTYP_EXECUTE ? TEXT("Execute") : \
                       (x == XTYP_CONNECT ? TEXT("Connect") : TEXT("Unknown")))))
HDDEDATA CIEDDE::DdeCallback(UINT dwType, UINT dwFmt, HCONV hconv, HSZ hsz1, HSZ hsz2, HDDEDATA hdd, DWORD dwData1, DWORD dwData2)
{
    HDDEDATA    hddRet = 0;
    ENTERPROC(2, TEXT("DdeCallback(dwType=%08X(%s),dwFmt=%d,hconv=%d,hsz1=%08X,hsz2=%08X,hdd=%08X,dwData1=%08X,dwData2=%08X)"),
                dwType, DDETYPESTR(dwType), dwFmt, hconv, hsz1, hsz2, hdd, dwData1, dwData2);

    WINITEM wi;

    switch (dwType)
    {
    case XTYP_REQUEST:
    case XTYP_POKE:
        if (g_pIEDDE->_GetWinitemFromThread(GetCurrentThreadId(), &wi))
        {
            hddRet = wi.pidt->OnRequestPoke(hsz1, hsz2);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: DdeCallback unable to get thread info on request / poke");
        }
        break;

    case XTYP_EXECUTE:
        if (g_pIEDDE->_GetWinitemFromThread(GetCurrentThreadId(), &wi))
        {
            hddRet = wi.pidt->OnExecute(hsz1, hdd);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: DdeCallback unable to get thread info on execute");
        }
        break;

    case XTYP_CONNECT:
        if (g_pIEDDE->_GetWinitemFromThread(GetCurrentThreadId(), &wi))
        {
            DDETHREADINFO dti;
            wi.pidt->GetDdeThreadInfo(&dti);
            hddRet = (HDDEDATA)(hsz2 == dti.hszService);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: DdeCallback unable to get thread info on connect");
        }
        break;

    case XTYP_ADVREQ:
    case XTYP_ADVSTOP:
        hddRet = DDE_FNOTPROCESSED;
        break;
    }

    EXITPROC(2, TEXT("DdeCallback=%08X"), hddRet);
    return hddRet;
}

//
// SendDDEMessageHsz - handle based wrapper for doing one DDE client transaction
//
HDDEDATA CIEDDE::_SendDDEMessageHsz(DWORD dwDDEInst, HSZ hszApp, HSZ hszTopic, HSZ hszMessage, UINT wType)
{
    HDDEDATA hddRet = 0;

    ENTERPROC(2, TEXT("_SendDDEMessageHsz(dwDDEInst=%08X,hszApp=%08X,hszTopic=%08X,hszMessage=%08X,wType=%d)"), dwDDEInst, hszApp, hszTopic, hszMessage, wType);

    if (hszApp && hszTopic)
    {
        HCONV hconv;
        
        hconv = DdeConnect(dwDDEInst, hszApp, hszTopic, NULL);
        if (hconv)
        {
            hddRet = DdeClientTransaction(NULL, 0, hconv, hszMessage, CF_TEXT, wType, TEN_SECONDS, NULL);
            DdeDisconnect(hconv);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _SendDDEMessageHsz could not connect to app");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _SendDDEMessageHsz is missing either App or Topic");
    }

    EXITPROC(2, TEXT("_SendDDEMessageHsz=%08X"), hddRet);
    return hddRet;
}

//
// SendDDEMessageSz - string based wrapper for doing one DDE client transaction
//
HDDEDATA CIEDDE::_SendDDEMessageSz(DWORD dwDDEInst, LPCTSTR pszApp, LPCTSTR pszTopic, LPCTSTR pszMessage, UINT wType)
{
    HDDEDATA hddRet = 0;

    ENTERPROC(2, TEXT("_SendDDEMessageSz(dwDDEInst=%08X,pszApp=>%s<,pszTopic=>%s<,pszMessage=>%s<,wType=%d)"), dwDDEInst, pszApp, pszTopic, pszMessage, wType);

    HSZ hszApp = DdeCreateStringHandle(dwDDEInst, pszApp, CP_WINNEUTRAL);
    if (hszApp)
    {
        HSZ hszTopic = DdeCreateStringHandle(dwDDEInst, pszTopic, CP_WINNEUTRAL);
        if (hszTopic)
        {
            HSZ hszMessage = DdeCreateStringHandle(dwDDEInst, pszMessage, CP_WINNEUTRAL);
            if (hszMessage)
            {
                hddRet = _SendDDEMessageHsz(dwDDEInst, hszApp, hszTopic, hszMessage, wType);
                DdeFreeStringHandle(dwDDEInst, hszMessage);
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _SendDDEMessageSz could not convert message");
            }
            DdeFreeStringHandle(dwDDEInst, hszTopic);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _SendDDEMessageSz could not convert topic");
        }
        DdeFreeStringHandle(dwDDEInst, hszApp);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _SendDDEMessageSz could not convert app");
    }

    EXITPROC(2, TEXT("_SendDDEMessageSz=%08X"), hddRet);
    return hddRet;
}

//
// Initialize - called when ready to start IEDDE server
//
BOOL CIEDDE::_Initialize(void)
{
    BOOL fSuccess = TRUE;
    ENTERPROC(2, TEXT("_Initialize()"));

    ASSERT(_fCSInitialized == FALSE);
    InitializeCriticalSection(&_csIEDDE);
    _fCSInitialized = TRUE;

    EXITPROC(2, TEXT("_Initialize=%d"), fSuccess);
    return fSuccess;
}

//
// _DestroyWinitem - DSA callback to partially free the contents of a WINITEM*
//  In practice this should never get called, the hdsaWinItem list should be
//  empty at uninit time.
//
int CIEDDE::_DestroyWinitem(LPVOID p1, LPVOID p2)
{
    WINITEM *pwi = (WINITEM *)p1;
    ASSERT(IS_VALID_READ_PTR(pwi, WINITEM));
    ASSERT(IS_VALID_READ_PTR(pwi->pidt, CIEDDEThread));

    //
    // It would be good to unregister the DDE server at this point,
    // but we'd need to be on its thread to do it.
    //

    delete pwi->pidt;

    return 1;
}

//
// _DestroyProtocol - DSA callback to free the contents of a PROTOCOLREG*
//
int CIEDDE::_DestroyProtocol(LPVOID p1, LPVOID p2)
{
    PROTOCOLREG *pr = (PROTOCOLREG *)p1;
    ASSERT(IS_VALID_READ_PTR(pr, PROTOCOLREG));

    LocalFree(pr->pszProtocol);
    pr->pszProtocol = NULL;
    LocalFree(pr->pszServer);
    pr->pszServer = NULL;

    return 1;
}

//
// _DestroyUrlEcho - DPA callback to free allocated memory
//
int CIEDDE::_DestroyUrlEcho(LPVOID p1, LPVOID p2)
{
    ASSERT(IS_VALID_STRING_PTR((LPTSTR)p1, -1));
    LocalFree(p1);
    p1 = NULL;

    return 1;
}

//
// Uninitialize - called when ready to stop IEDDE server
//
void CIEDDE::_Uninitialize(void)
{
    ENTERPROC(2, TEXT("_Uninitialize()"));

    _fAutomationReady = FALSE;

    if (_hdsaWinitem)
    {
        if (DSA_GetItemCount(_hdsaWinitem))
        {
            //ASSERT(DSA_GetItemCount(_hdsaWinitem)==0);
            TraceMsg(TF_ERROR, "IEDDE: Browser windows still open on uninitialize");
        }

        DSA_DestroyCallback(_hdsaWinitem, _DestroyWinitem, 0);
        _hdsaWinitem = NULL;
    }

    if (_hdsaProtocolHandler)
    {
        DSA_DestroyCallback(_hdsaProtocolHandler, _DestroyProtocol, 0);
        _hdsaProtocolHandler = NULL;
    }

    if (_hdpaUrlEcho)
    {
        DPA_DestroyCallback(_hdpaUrlEcho, _DestroyUrlEcho, 0);
        _hdpaUrlEcho = NULL;
    }

    if (_fCSInitialized)
    {
        DeleteCriticalSection(&_csIEDDE);
    }

    EXITPROC(2, TEXT("_Uninitialize!"));
}

void CIEDDE::SetDelayedExecute(LPCTSTR pszTopic, LPCTSTR pszParams)
{
    _dwThreadID = GetCurrentThreadId();
    Str_SetPtr(&_pszTopic, pszTopic);
    Str_SetPtr(&_pszParams, pszParams);
}

void CIEDDE::RunDelayedExecute()
{
    if (_pszTopic && _pszParams)
    {
        WINITEM wi;
        if (_GetWinitemFromThread(_dwThreadID, &wi) && wi.pidt)
        {
            HDDEDATA h = wi.pidt->CallTopic(XTYP_EXECUTE, _pszTopic, _pszParams);
            DdeFreeDataHandle(h);
        }
    }

    Str_SetPtr(&_pszTopic, NULL);
    Str_SetPtr(&_pszParams, NULL);
}
//
// _AutomationStarted - called when automation support can be called
//
void CIEDDE::_AutomationStarted(void)
{
    ENTERPROC(1, TEXT("_AutomationStarted()"));
    if (!_fAutomationReady && _pszTopic && _pszParams)
    {
        WINITEM wi;
        if (_GetWinitemFromThread(_dwThreadID, &wi) && wi.pidt)
        {
            PostMessage(wi.hwnd, WMC_DELAYEDDDEEXEC, 0, 0);
        }
    }
    _fAutomationReady = TRUE;

    EXITPROC(1, TEXT("_AutomationStarted!"));
}

//
// _BeforeNavigate - called before a navigation occurs.
//
HRESULT CIEDDE::_BeforeNavigate(LPCTSTR pszURL, BOOL *pfProcessed)
{
    ENTERPROC(1, TEXT("_BeforeNavigate(pszURL=>%s<,pfProcessed=%08X)"), pszURL, pfProcessed);

    SHSTR shstrMsg;
    HRESULT hr = S_OK;
    int cProtocols = 0;

    ENTER_IEDDE_CRIT;
    if (_hdsaProtocolHandler)
    {
        cProtocols = DSA_GetItemCount(_hdsaProtocolHandler);
    }
    LEAVE_IEDDE_CRIT;

    if (cProtocols)
    {
        DDETHREADINFO dti;

        if (_GetDtiFromThread(GetCurrentThreadId(), &dti))
        {
            PARSEDURL pu;

            pu.cbSize = SIZEOF(pu);

            if (SUCCEEDED(ParseURL(pszURL, &pu)))
            {
                int i;

                for (i=0; i<cProtocols; i++)
                {
                    PROTOCOLREG pr;

                    ENTER_IEDDE_CRIT;
                    int iResult = DSA_GetItem(_hdsaProtocolHandler, i, &pr);
                    LEAVE_IEDDE_CRIT;

                    if (iResult != -1)
                    {
                        //
                        // Check to see if the protocol to navigate
                        // matches one of our registered protocols.
                        // We do a case insensitive compare.  Note
                        // that:
                        //
                        //   (1) ParseURL does not null terminate the
                        //       pu.pszProtocol (its length is stored
                        //       in pu.cchProtocol).
                        //
                        //   (2) pu.pszProtocol is a LPCTSTR so we
                        //       can't modify the pszProtocol ourselves.
                        //
                        //   (3) There is no win32 lstrncmpi() API.
                        //
                        // Therefore in order to do a case insensitive
                        // compare we must copy the pu.pszProtocol into
                        // a writable buffer at some point.
                        //
                        if (lstrlen(pr.pszProtocol) == (int)pu.cchProtocol)
                        {
                            shstrMsg.SetStr(pu.pszProtocol, pu.cchProtocol);
                            if (StrCmpI(pr.pszProtocol, shstrMsg) == 0)
                            {
                                shstrMsg.SetStr(TEXT("\""));
                                shstrMsg.Append(pszURL);
                                shstrMsg.Append(TEXT("\",,-1,0,,,,"));

                                if (_SendDDEMessageSz(dti.dwDDEInst, pr.pszServer, c_szWWWOpenURL, shstrMsg, XTYP_REQUEST))
                                {
                                    if (pfProcessed)
                                    {
                                        *pfProcessed = TRUE;
                                    }
                                }
                                else
                                {
                                    TraceMsg(TF_WARNING, "IEDDE: _BeforeNavigate could not DDE to protocol handler");
                                }

                                break;
                            }
                        }
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: _BeforeNavigate could not get item from DSA");
                    }
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _BeforeNavigate could not parse URL");
            }
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _BeforeNavigate unable to get thread info, can't use DDE");
        }
    }

    EXITPROC(1, TEXT("_BeforeNavigate=%08X"), hr);
    return hr;
}

//
// _AfterNavigate - called after a navigation occurs
//
HRESULT CIEDDE::_AfterNavigate(LPCTSTR pszURL, HWND hwnd)
{
    ENTERPROC(1, TEXT("_AfterNavigate(pszURL=>%s<,hwnd=%08X)"), pszURL, hwnd);

    int cURLHooks = 0;
    SHSTR shstrMsg;
    HRESULT hr = S_OK;

    ENTER_IEDDE_CRIT;
    if (_hdpaUrlEcho)
    {
        cURLHooks = DPA_GetPtrCount(_hdpaUrlEcho);
    }
    LEAVE_IEDDE_CRIT;

    if (cURLHooks)
    {
        SHSTR shstrMime;

        // (mattsq 1-97)
        // this is a temporary lie - it should be fixed to use the real mimetype
        // with something like:
        //      GetMimeTypeFromUrl(pszURL, shstrMime);
        // talk to URLMON people
        shstrMime.SetStr(TEXT("text/html"));

        DDETHREADINFO dti={0};
        WINITEM wi;
        DWORD dwWindowID;
        if (GetWinitemFromHwnd(hwnd, &wi))
        {
            dwWindowID = wi.dwWindowID;
            wi.pidt->GetDdeThreadInfo(&dti);
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate unable to find browser window ID, using -1");
            dwWindowID = (DWORD)-1;

            WINITEM wiThread;

            if (_GetWinitemFromThread(GetCurrentThreadId(), &wiThread))
            {
                ASSERT(wiThread.pidt);
                wiThread.pidt->GetDdeThreadInfo(&dti);
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate unable to find DDE thread info");
            }
        }

        if (dti.dwDDEInst)
        {
            HSZ hszTopic = DdeCreateStringHandle(dti.dwDDEInst, c_szWWWUrlEcho, CP_WINNEUTRAL);
            if (hszTopic)
            {
                TCHAR szFinish[16];

                shstrMsg.SetStr(TEXT("\""));                // Quote
                shstrMsg.Append(pszURL);                    // URL
                shstrMsg.Append(TEXT("\",\""));             // Quote Comma Quote
                shstrMsg.Append(shstrMime);                 // Mime
                wnsprintf(szFinish, ARRAYSIZE(szFinish), TEXT("\",%d"), dwWindowID);    //
                shstrMsg.Append(szFinish);                  // Quote Comma dwWindowID NULL

                HSZ hszMsg = DdeCreateStringHandle(dti.dwDDEInst, shstrMsg, CP_WINNEUTRAL);

                if (hszMsg)
                {
                    //
                    // Enumerate in reverse order because calling a hook may destroy it.
                    //
                    for (int i=cURLHooks-1; i>=0; --i)
                    {
                        ENTER_IEDDE_CRIT;
                        LPTSTR pszService = (LPTSTR)DPA_GetPtr(_hdpaUrlEcho, i);
                        LEAVE_IEDDE_CRIT;

                        if (pszService != NULL)
                        {
                            HSZ hszService = DdeCreateStringHandle(dti.dwDDEInst, pszService, CP_WINNEUTRAL);

                            if (hszService)
                            {
                                if (_SendDDEMessageHsz(dti.dwDDEInst, hszService, hszTopic, hszMsg, XTYP_POKE) == 0)
                                {
                                    TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate could not DDE to URLHook handler");
                                }

                                DdeFreeStringHandle(dti.dwDDEInst, hszService);
                            }
                            else
                            {
                                TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate unable to create hszService");
                            }
                        }
                        else
                        {
                            TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate unable to enumerate an URL hook");
                        }
                    }

                    DdeFreeStringHandle(dti.dwDDEInst, hszMsg);
                }
                else
                {
                    TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate unable to create hszMsg");
                }

                DdeFreeStringHandle(dti.dwDDEInst, hszTopic);
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _AfterNavigate unable to create hszTopic");
            }
        }
    }

    EXITPROC(1, TEXT("_AfterNavigate=%08X"), hr);
    return hr;
}

//
// GetWinitemFromHwnd - return the winitem associated with an hwnd
//
BOOL CIEDDE::GetWinitemFromHwnd(HWND hwnd, WINITEM *pwi)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("GetWinitemFromHwnd(hwnd=%08X,pwi=%08X)"), hwnd, pwi);

    ENTER_IEDDE_CRIT;

    if (_hdsaWinitem)
    {
        for (int i=0; i<DSA_GetItemCount(_hdsaWinitem); i++)
        {
            WINITEM wi;

            if (DSA_GetItem(_hdsaWinitem, i, &wi) != -1)
            {
                if (wi.hwnd == hwnd)
                {
                    *pwi = wi;
                    fSuccess = TRUE;
                    break;
                }
            }
        }
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("GetWinitemFromHwnd=%d"), fSuccess); 
    return fSuccess;
}

//
// GetWinitemFromWindowID - return the winitem associated with a window ID
//
BOOL CIEDDE::GetWinitemFromWindowID(DWORD dwWindowID, WINITEM *pwi)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(3, TEXT("GetWinitemFromWindowID(dwWindowID=%08X,pwi=%08X)"), dwWindowID, pwi);

    ENTER_IEDDE_CRIT;

    if (_hdsaWinitem)
    {
        for (int i=0; i<DSA_GetItemCount(_hdsaWinitem); i++)
        {
            WINITEM wi;

            if (DSA_GetItem(_hdsaWinitem, i, &wi) != -1)
            {
                if (wi.dwWindowID == dwWindowID)
                {
                    *pwi = wi;
                    fSuccess = TRUE;
                    break;
                }
            }
        }
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("GetWinitemFromWindowID=%d"), fSuccess); 
    return fSuccess;
}

//
// _GetWinitemFromThread - return the first winitem associated with a thread
//
BOOL CIEDDE::_GetWinitemFromThread(DWORD dwThreadID, WINITEM *pwi)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("_GetWinitemFromThread(dwThreadID=%08X,pwi=%08X)"), dwThreadID, pwi);

    ENTER_IEDDE_CRIT;

    if (_hdsaWinitem)
    {
        for (int i=0; i<DSA_GetItemCount(_hdsaWinitem); i++)
        {
            WINITEM wi;

            if (DSA_GetItem(_hdsaWinitem, i, &wi) != -1)
            {
                if (wi.dwThreadID == dwThreadID)
                {
                    *pwi = wi;
                    fSuccess = TRUE;
                    break;
                }
            }
        }
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("_GetWinitemFromThread=%d"), fSuccess); 
    return fSuccess;
}

//
// _GetDtiFromThread - return the threadinfo associated with a thread
//
BOOL CIEDDE::_GetDtiFromThread(DWORD dwThreadID, DDETHREADINFO *pdti)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("_GetDtiFromThread(dwThreadID=%08X,pdti=%08X)"), dwThreadID, pdti);

    ENTER_IEDDE_CRIT;

    WINITEM wi;
    if (_GetWinitemFromThread(dwThreadID, &wi))
    {
        wi.pidt->GetDdeThreadInfo(pdti);
        fSuccess = TRUE;
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _GetDtiFromThread unable to find winitem");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("_GetDtiFromThread=%d"), fSuccess); 
    return fSuccess;
}

//
// _CreateDdeThreadInfo - Initialize DDE services and names for this thread
//
BOOL CIEDDE::_CreateDdeThreadInfo(DDETHREADINFO *pdti)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("_CreateDdeThreadInfo(pdti=%08X)"), pdti);

    UINT uiDDE;
    DDETHREADINFO dti={0};

    //
    // Initialize DDEML, register our service.
    //

    uiDDE = DdeInitialize(&dti.dwDDEInst, (PFNCALLBACK)DdeCallback,
                           APPCLASS_STANDARD | CBF_FAIL_ADVISES |
                           CBF_SKIP_REGISTRATIONS | CBF_SKIP_UNREGISTRATIONS, 0);

    if (uiDDE == DMLERR_NO_ERROR)
    {
        dti.hszReturn = DdeCreateStringHandle(dti.dwDDEInst, c_szReturn, CP_WINNEUTRAL);
        if (dti.hszReturn)
        {
            dti.hszService = DdeCreateStringHandle(dti.dwDDEInst, c_szIExplore, CP_WINNEUTRAL);
            if (dti.hszService)
            {
                *pdti = dti;
                fSuccess = TRUE;
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _CreateDdeThreadInfo unable to convert service");
            }

            if (!fSuccess)
            {
                DdeFreeStringHandle(dti.dwDDEInst, dti.hszReturn);
            }
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _CreateDdeThreadInfo unable to convert return");
        }

        if (!fSuccess)
        {
            DdeUninitialize(dti.dwDDEInst);
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _CreateDdeThreadInfo unable to init DDE");
    }

    EXITPROC(2, TEXT("_CreateDdeThreadInfo=%d"), fSuccess);
    return fSuccess;
}

//
// _DestroyDdeThreadInfo - Free up any resources in a dti structure.
//
void CIEDDE::_DestroyDdeThreadInfo(DDETHREADINFO *pdti)
{
    ENTERPROC(2, TEXT("_DestroyDdeThreadInfo(pdti=%08X)"), pdti);

    if (pdti->hddNameService)
    {
        ASSERT(pdti->hszService);
        DdeNameService(pdti->dwDDEInst, pdti->hszService, 0, DNS_UNREGISTER);
        pdti->hddNameService = 0;
    }

    if (pdti->hszService)
    {
        DdeFreeStringHandle(pdti->dwDDEInst, pdti->hszService);
        pdti->hszService = 0;
    }

    if (pdti->hszReturn)
    {
        DdeFreeStringHandle(pdti->dwDDEInst, pdti->hszReturn);
        pdti->hszReturn = 0;
    }

    if (pdti->dwDDEInst)
    {
        DdeUninitialize(pdti->dwDDEInst);
        pdti->dwDDEInst = 0;
    }

    EXITPROC(2, TEXT("_DestroyDdeThreadInfo!"));
    return;
}

//
// _AddWinitem - adds a winitem to _hdsaWinitem
//
BOOL CIEDDE::_AddWinitem(WINITEM *pwi)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("_AddWinitem(pwi=%08X)"), pwi);

    ENTER_IEDDE_CRIT;

    if (!_hdsaWinitem)
    {
        _hdsaWinitem = DSA_Create(SIZEOF(WINITEM), DXA_GROWTH_AMOUNT);
    }

    if (_hdsaWinitem)
    {
        if (DSA_AppendItem(_hdsaWinitem, pwi) != -1)
        {
            fSuccess = TRUE;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _AddWinitem could not append an item");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _AddWinitem could not create hdsa");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("_AddWinitem=%d"), fSuccess);
    return fSuccess;
}

//
// _UpdateWinitem - updates a winitem based on the dwWindowID.
//
BOOL CIEDDE::_UpdateWinitem(WINITEM *pwi)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("_UpdateWinitem(pwi=%08X)"), pwi);

    ENTER_IEDDE_CRIT;

    if (_hdsaWinitem)
    {
        int cItems = DSA_GetItemCount(_hdsaWinitem);

        for (int i=0; i<cItems; i++)
        {
            WINITEM wi;

            if (DSA_GetItem(_hdsaWinitem, i, &wi) != -1)
            {
                if (wi.dwWindowID == pwi->dwWindowID)
                {
                    if (DSA_SetItem(_hdsaWinitem, i, pwi))
                    {
                        fSuccess = TRUE;
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: _UpdateWinitem could not update an item");
                    }
                    break;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _UpdateWinitem could not get an item");
            }
        }
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("_UpdateWinitem=%d"), fSuccess);
    return fSuccess;
}

//
// AddUrlEcho - adds an UrlEcho entry to the dpa
//
BOOL CIEDDE::AddUrlEcho(LPCTSTR pszUrlEcho)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("AddUrlEcho(pszUrlEcho=>%s<)"), pszUrlEcho);

    ENTER_IEDDE_CRIT;

    if (!_hdpaUrlEcho)
    {
        _hdpaUrlEcho = DPA_Create(DXA_GROWTH_AMOUNT);
    }

    if (_hdpaUrlEcho)
    {
        if (DPA_AppendPtr(_hdpaUrlEcho, (LPVOID)pszUrlEcho) != -1)
        {
            fSuccess = TRUE;
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: AddUrlEcho unable to append a ptr");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: AddUrlEcho unable to create a dpa");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("AddUrlEcho=%d"), fSuccess);
    return fSuccess;
}

//
// RemoveUrlEcho - remove an UrlEcho entry from the dpa
//
BOOL CIEDDE::RemoveUrlEcho(LPCTSTR pszUrlEcho)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("RemoveUrlEcho(pszUrlEcho=>%s<)"), pszUrlEcho);

    ENTER_IEDDE_CRIT;

    if (_hdpaUrlEcho)
    {
        for (int i=0; i<DPA_GetPtrCount(_hdpaUrlEcho); i++)
        {
            LPTSTR pszList = (LPTSTR)DPA_GetPtr(_hdpaUrlEcho, i);
            if (pszList)
            {
                if (StrCmpI(pszList, pszUrlEcho) == 0)
                {
                    DPA_DeletePtr(_hdpaUrlEcho, i);
                    LocalFree((HANDLE)pszList);
                    pszList = NULL;
                    fSuccess = TRUE;
                    break;
                }
            }
            else
            {
                TraceMsg(TF_ALWAYS, "IEDDE: RemoveUrlEcho unable to get dpa ptr");
            }
        }

        if (!fSuccess)
        {
            TraceMsg(TF_WARNING, "IEDDE: RemoveUrlEcho unable to find server");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: RemoveUrlEcho unable to find dpa");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("RemoveUrlEcho=%d"), fSuccess);
    return fSuccess;
}

//
// AddProtocolHandler - add a PROTOCOLREG entry to the dsa
//
BOOL CIEDDE::AddProtocolHandler(LPCTSTR pszServer, LPCTSTR pszProtocol)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("AddProtocolHandler(pszServer=>%s<,pszProtocol=>%s<)"), pszServer, pszProtocol);

    ENTER_IEDDE_CRIT;

    PROTOCOLREG pr;
    //
    // First, see if anybody else grabbed the protocol first.
    //
    BOOL fFoundHandler = FALSE;
    if (_hdsaProtocolHandler)
    {
        for (int i=0; i<DSA_GetItemCount(_hdsaProtocolHandler); i++)
        {
            if (DSA_GetItem(_hdsaProtocolHandler, i, &pr) != -1)
            {
                if (StrCmpI(pr.pszProtocol, pszProtocol) == 0)
                {
                    TraceMsg(TF_WARNING, "IEDDE: AddProtocolHandler already has a handler");
                    fFoundHandler = TRUE;
                    break;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: AddProtocolHandler unable to get an item");
            }
        }
    }

    if (!fFoundHandler)
    {
        if (!_hdsaProtocolHandler)
        {
            _hdsaProtocolHandler = DSA_Create(SIZEOF(PROTOCOLREG), DXA_GROWTH_AMOUNT);
        }

        if (_hdsaProtocolHandler)
        {
            pr.pszServer = StrDup(pszServer);
            if (pr.pszServer)
            {
                pr.pszProtocol = StrDup(pszProtocol);
                if (pr.pszProtocol)
                {
                    if (DSA_AppendItem(_hdsaProtocolHandler, &pr) != -1)
                    {
                        fSuccess = TRUE;
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: AddProtocolHandler unable to append to dsa");
                    }

                    if (!fSuccess)
                    {
                        LocalFree((HANDLE)pr.pszProtocol);
                        pr.pszProtocol = NULL;
                    }
                }
                else
                {
                    TraceMsg(TF_WARNING, "IEDDE: AddProtocolHandler unable to dup protocol");
                }

                if (!fSuccess)
                {
                    LocalFree((HANDLE)pr.pszServer);
                    pr.pszServer = NULL;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: AddProtocolHandler unable to dup server");
            }

        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: AddProtocolHandler unable to create dsa");
        }
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("AddProtocolHandler=%d"), fSuccess);
    return fSuccess;
}

//
// RemoveProtocolHandler - removes a PROTOCOLREG item from the dsa
//
BOOL CIEDDE::RemoveProtocolHandler(LPCTSTR pszServer, LPCTSTR pszProtocol)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("RemoveProtocolHandler(pszServer=>%s<,pszProtocol=>%s<)"), pszServer, pszProtocol);

    ENTER_IEDDE_CRIT;

    if (_hdsaProtocolHandler)
    {
        for (int i=0; i<DSA_GetItemCount(_hdsaProtocolHandler); i++)
        {
            PROTOCOLREG pr;

            if (DSA_GetItem(_hdsaProtocolHandler, i, &pr) != -1)
            {
                if (StrCmpI(pr.pszProtocol, pszProtocol) == 0)
                {
                    if (StrCmpI(pr.pszServer, pszServer) == 0)
                    {
                        if (DSA_DeleteItem(_hdsaProtocolHandler, i) != -1)
                        {
                            LocalFree((HANDLE)pr.pszServer);
                            pr.pszServer = NULL;
                            LocalFree((HANDLE)pr.pszProtocol);
                            pr.pszProtocol = NULL;
                            fSuccess = TRUE;
                        }
                        else
                        {
                            TraceMsg(TF_WARNING, "IEDDE: RemoveProtocolHandler unable to remove item");
                        }
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: RemoveProtocolHandler says server didn't match");
                    }

                    break;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: RemoveProtocolHandler unable to get item");
            }
        }

        if (!fSuccess)
        {
            TraceMsg(TF_WARNING, "IEDDE: RemoveProtocolHandler unable to complete mission");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: RemoveProtocolHandler can't find the dsa");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("RemoveProtocolHandler=%d"), fSuccess);
    return fSuccess;
}

//
// _DeleteWinitemByHwnd - removes a winitem from _hdsaWinitem
//
BOOL CIEDDE::_DeleteWinitemByHwnd(HWND hwnd, WINITEM *pwi)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(2, TEXT("_DeleteWinitemByHwnd(hwnd=%08X,pwi=%08X)"), hwnd, pwi);

    ENTER_IEDDE_CRIT;

    if (_hdsaWinitem)
    {
        for (int i=0; i<DSA_GetItemCount(_hdsaWinitem); i++)
        {
            WINITEM wi;

            if (DSA_GetItem(_hdsaWinitem, i, &wi) != -1)
            {
                if (wi.hwnd == hwnd)
                {
                    if (DSA_DeleteItem(_hdsaWinitem, i) != -1)
                    {
                        *pwi = wi;
                        fSuccess = TRUE;
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: _DeleteWinitemByHwnd could note delete an item");
                    }

                    break;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _DeleteWinitemByHwnd could note get an item");
            }
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _DeleteWinitemByHwnd has no _hdsaWinitem");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(2, TEXT("_DeleteWinitemByHwnd=%d"), fSuccess);
    return fSuccess;
}

//
// _NewWindow - Add a browser window to the internal list
//
BOOL CIEDDE::_NewWindow(HWND hwnd)
{
    BOOL fSuccess = FALSE;

    ENTERPROC(1, TEXT("NewWindow(hwnd=%08X)"), hwnd);

    ASSERT(IS_VALID_HANDLE(hwnd, WND));

    ENTER_IEDDE_CRIT;

    WINITEM wi;
    if (GetWinitemFromHwnd(hwnd, &wi) == FALSE)
    {
        CIEDDEThread *pidt = new CIEDDEThread();

        if (pidt)
        {
            DDETHREADINFO dti = {0};
            DWORD dwThreadID = GetCurrentThreadId();
            WINITEM wi;
            BOOL fCreatedDTI = FALSE;

            if (_GetWinitemFromThread(dwThreadID, &wi))
            {
                wi.pidt->GetDdeThreadInfo(&dti);
            }
            else
            {
                LEAVE_IEDDE_CRIT;
                _CreateDdeThreadInfo(&dti);
                ENTER_IEDDE_CRIT;
                fCreatedDTI = TRUE;
            }

            if (dti.dwDDEInst)
            {
                static DWORD dwNextWindowID = 1;

                pidt->SetDdeThreadInfo(&dti);

                wi.dwThreadID = dwThreadID;
                wi.pidt = pidt;
                wi.hwnd = hwnd;
                wi.dwWindowID = dwNextWindowID++;

                if (_AddWinitem(&wi))
                {
                    //
                    // Now that we have a (partial) winitem in the winitem
                    // database, we can register our name service.  If we
                    // registered any sooner, there is a risk that an app
                    // will try to connect to us while we are registering,
                    // and we will fail the connect because the winitem
                    // is not in the registry yet.
                    //
                    LEAVE_IEDDE_CRIT;
                    dti.hddNameService = DdeNameService(dti.dwDDEInst, dti.hszService, 0, DNS_REGISTER);
                    ENTER_IEDDE_CRIT;

                    //
                    // Now that we have hddNameService, we can update the
                    // winitem in the database.
                    //
                    if (dti.hddNameService)
                    {
                        pidt->SetDdeThreadInfo(&dti);
                        if (_UpdateWinitem(&wi))
                        {
                            fSuccess = TRUE;
                        }
                        else
                        {
                            TraceMsg(TF_WARNING, "IEDDE: _NewWindow unable to update a win item");
                        }
                    }
                    else
                    {
                        TraceMsg(TF_WARNING, "IEDDE: _NewWindow unable to register service");
                    }
                }
                else
                {
                    TraceMsg(TF_WARNING, "IEDDE: _NewWindow could not append win item");
                }

                if (!fSuccess && fCreatedDTI)
                {
                    LEAVE_IEDDE_CRIT;
                    _DestroyDdeThreadInfo(&dti);
                    ENTER_IEDDE_CRIT;
                }
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _NewWindow could not get/create dde thread info");
            }

            if (!fSuccess)
            {
                delete pidt;
            }
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: _NewWindow could not create iedde thread object");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _NewWindow says window already registered?!?");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(1, TEXT("_NewWindow=%d"), fSuccess);
    return fSuccess;
}

//
// _WindowDestroyed - Remove a browser window from the internal list
//
BOOL CIEDDE::_WindowDestroyed(HWND hwnd)
{
    BOOL fSuccess = FALSE;
    ENTERPROC(1, TEXT("_WindowDestroyed(hwnd=%08X)"), hwnd);

    ENTER_IEDDE_CRIT;

    WINITEM wi;
    if (_DeleteWinitemByHwnd(hwnd, &wi))
    {
        fSuccess = TRUE;

        ASSERT(wi.pidt);
        WINITEM wiThread;

        if (_GetWinitemFromThread(wi.dwThreadID, &wiThread) == FALSE)
        {
            if (wi.dwThreadID == GetCurrentThreadId())
            {
                DDETHREADINFO dti;

                wi.pidt->GetDdeThreadInfo(&dti);
                // Don't hold onto critical section while doing this...
                LEAVE_IEDDE_CRIT;
                _DestroyDdeThreadInfo(&dti);
                ENTER_IEDDE_CRIT;
            }
            else
            {
                TraceMsg(TF_WARNING, "IEDDE: _WindowDestroyed called on wrong thread");
            }
        }

        delete wi.pidt;
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: _WindowDestroyed could not find hwnd");
    }

    LEAVE_IEDDE_CRIT;

    EXITPROC(1, TEXT("_WindowDestroyed=%d"), fSuccess);
    return fSuccess;
}





//
// IEDDE_ functions are those exported for other parts of shdocvw to call.
// They pretty much just call the equivalent function in g_pIEDDE.
//

BOOL IEDDE_Initialize(void)
{
    BOOL fRet = FALSE;

    ENTERPROC(2, TEXT("IEDDE_Initialize()"));

    if (g_pIEDDE == NULL)
    {
        g_pIEDDE = new CIEDDE;

        if (g_pIEDDE)
        {
            fRet = g_pIEDDE->_Initialize();
        }
        else
        {
            TraceMsg(TF_WARNING, "IEDDE: IEDDE_Initialize could not allocate an IEDDE object");
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_Initialize says already initialized");
    }

    EXITPROC(2, TEXT("IEDDE_Initialize=%d"), fRet);
    return fRet;
}

void IEDDE_Uninitialize(void)
{
    ENTERPROC(2, TEXT("IEDDE_Uninitialize()"));

    if (g_pIEDDE)
    {
        g_pIEDDE->_Uninitialize();
        delete g_pIEDDE;
        g_pIEDDE = NULL;
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_Uninitialize has no IEDDE object");
    }

    EXITPROC(2, TEXT("IEDDE_Uninitialize!"));
}

BOOL IEDDE_RunDelayedExecute()
{
    if (g_pIEDDE)
    {
        g_pIEDDE->RunDelayedExecute();
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_RunDelayedExecute has no IEDDE object");
    }
    return TRUE;
}

void IEDDE_AutomationStarted(void)
{
    ENTERPROC(2, TEXT("IEDDE_AutomationStarted()"));

    if (g_pIEDDE)
    {
        g_pIEDDE->_AutomationStarted();
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_AutomationStarted has no IEDDE object");
    }

    EXITPROC(2, TEXT("IEDDE_AutomationStarted!"));
}

HRESULT IEDDE_BeforeNavigate(LPCWSTR pwszURL, BOOL *pfCanceled)
{
    HRESULT hr = E_FAIL;

    ENTERPROC(2, TEXT("IEDDE_BeforeNavigate(pwszURL=%08X,pfCanceled=%08X)"), pwszURL, pfCanceled);

    if (g_pIEDDE)
    {
        LPCTSTR pszURL;

        pszURL = pwszURL;

        if (pszURL)
        {
            hr = g_pIEDDE->_BeforeNavigate(pszURL, pfCanceled);
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_BeforeNavigate has no IEDDE object");
    }

    EXITPROC(2, TEXT("IEDDE_BeforeNavigate=%08X"), hr);
    return hr;
}

HRESULT IEDDE_AfterNavigate(LPCWSTR pwszURL, HWND hwnd)
{
    HRESULT hr = E_FAIL;

    ENTERPROC(2, TEXT("IEDDE_AfterNavigate(pwszURL=%08X,hwnd=%08X)"), pwszURL, hwnd);

    if (g_pIEDDE)
    {
        LPCTSTR pszURL;

        pszURL = pwszURL;

        if (pszURL)
        {
            hr = g_pIEDDE->_AfterNavigate(pszURL, hwnd);
        }
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_AfterNavigate has no IEDDE object");
    }

    EXITPROC(2, TEXT("IEDDE_AfterNavigate=%08X"), hr);
    return hr;
}

BOOL IEDDE_NewWindow(HWND hwnd)
{
    BOOL fRet = FALSE;

    ENTERPROC(2, TEXT("IEDDE_NewWindow(hwnd=%08X)"), hwnd);

    if (g_pIEDDE)
    {
        fRet = g_pIEDDE->_NewWindow(hwnd);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_NewWindow has no IEDDE object");
    }

    EXITPROC(2, TEXT("IEDDE_NewWindow=%d"), fRet);
    return fRet;
}

BOOL IEDDE_WindowDestroyed(HWND hwnd)
{
    BOOL fRet = FALSE;

    ENTERPROC(2, TEXT("IEDDE_WindowDestroyed(hwnd=%08X)"), hwnd);

    if (g_pIEDDE)
    {
        fRet = g_pIEDDE->_WindowDestroyed(hwnd);
    }
    else
    {
        TraceMsg(TF_WARNING, "IEDDE: IEDDE_WindowDestroyed has no IEDDE object");
    }

    EXITPROC(2, TEXT("IEDDE_WindowDestroyed=%d"), fRet);
    return fRet;
}





#ifdef DEBUG

//
// Move g_dwIEDDETrace into ccshell.ini to prevent recompiles.
//

DWORD g_dwIEDDETrace = 0;
static DWORD g_dwIndent = 0;
static const TCHAR c_szDotDot[] = TEXT("..");

#define MAX_INDENTATION_VALUE   0x10

void EnterProc(DWORD dwTraceLevel, LPTSTR szFmt, ...)
{
    TCHAR szOutput[1000];
    va_list arglist;

    if (dwTraceLevel <= g_dwIEDDETrace)
    {
        szOutput[0] = TEXT('\0');

        for (DWORD i=0; i<g_dwIndent; i++)
        {
            StrCatBuff(szOutput, c_szDotDot, ARRAYSIZE(szOutput));
        }

        va_start(arglist, szFmt);
        wvnsprintf(szOutput + lstrlen(szOutput), ARRAYSIZE(szOutput) - lstrlen(szOutput), szFmt, arglist);
        va_end(arglist);

        TraceMsg(TF_ALWAYS, "%s", szOutput);

        // This value can get out of hand if EnterProc and ExitProc
        // calls do not match. This can trash the stack.
        if(g_dwIndent < MAX_INDENTATION_VALUE)
            g_dwIndent++;
    }
}

void ExitProc(DWORD dwTraceLevel, LPTSTR szFmt, ...)
{
    TCHAR szOutput[1000];
    va_list arglist;

    if (dwTraceLevel <= g_dwIEDDETrace)
    {
        // This can happen if the EnterProc and 
        // ExitProc calls do not match.
        if(g_dwIndent > 0)
            g_dwIndent--;

        szOutput[0] = TEXT('\0');

        for (DWORD i=0; i<g_dwIndent; i++)
        {
            StrCatBuff(szOutput, c_szDotDot, ARRAYSIZE(szOutput));
        }

        va_start(arglist, szFmt);
        wvnsprintf(szOutput + lstrlen(szOutput), ARRAYSIZE(szOutput) - lstrlen(szOutput), szFmt, arglist);
        va_end(arglist);

        TraceMsg(TF_ALWAYS, "%s", szOutput);
    }
}

#endif
