/*===================================================================
Microsoft Denali

Microsoft Confidential.
Copyright 1996 Microsoft Corporation. All Rights Reserved.

Component: Request, Response objects

File: cookies.cpp

Owner: DGottner

This file contains the code for the implementation of the 
Request.Cookies and Response.Cookies collections.
===================================================================*/

#include "denpre.h"
#pragma hdrstop

#include "objbase.h"
#include "cookies.h"
#include "memchk.h"

#pragma warning (disable: 4355)  // ignore: "'this' used in base member init



/*------------------------------------------------------------------
 * C C o o k i e S u p p o r t E r r
 */

/*===================================================================
CCookieSupportErr::CCookieSupportErr

constructor
===================================================================*/

CCookieSupportErr::CCookieSupportErr(CCookie *pCookie)
	{
	m_pCookie = pCookie;
	}



/*===================================================================
CCookieSupportErr::QueryInterface
CCookieSupportErr::AddRef
CCookieSupportErr::Release

Delegating IUnknown members for CCookieSupportErr object.
===================================================================*/

STDMETHODIMP CCookieSupportErr::QueryInterface(const IID &idInterface, void **ppvObj)
	{
	return m_pCookie->QueryInterface(idInterface, ppvObj);
	}

STDMETHODIMP_(ULONG) CCookieSupportErr::AddRef()
	{
	return m_pCookie->AddRef();
	}

STDMETHODIMP_(ULONG) CCookieSupportErr::Release()
	{
	return m_pCookie->Release();
	}



/*===================================================================
CCookieSupportErr::InterfaceSupportsErrorInfo

Report back to OA about which interfaces we support that return
error information
===================================================================*/

STDMETHODIMP CCookieSupportErr::InterfaceSupportsErrorInfo(const GUID &idInterface)
	{
	if (idInterface == IID_IDispatch || idInterface == IID_IWriteCookie || idInterface == IID_IReadCookie)
		return S_OK;

	return S_FALSE;
	}



/*------------------------------------------------------------------
 * C W r i t e C o o k i e
 */

/*===================================================================
CWriteCookie::CWriteCookie

constructor
===================================================================*/

CWriteCookie::CWriteCookie(CCookie *pCookie)
	{
	m_pCookie = pCookie;
	CDispatch::Init(IID_IWriteCookie);
	}



/*===================================================================
CWriteCookie::QueryInterface
CWriteCookie::AddRef
CWriteCookie::Release

Delegating IUnknown members for CWriteCookie object.
===================================================================*/

STDMETHODIMP CWriteCookie::QueryInterface(const IID &idInterface, void **ppvObj)
	{
	// Bug 85953 Trap IDispatch before it gets to the core object
	if (idInterface == IID_IUnknown || 
		idInterface == IID_IWriteCookie ||
		idInterface == IID_IDispatch)
		{
		*ppvObj = this;
		static_cast<IUnknown *>(*ppvObj)->AddRef();
		return S_OK;
		}
	else
		return m_pCookie->QueryInterface(idInterface, ppvObj);
	}

STDMETHODIMP_(ULONG) CWriteCookie::AddRef()
	{
	return m_pCookie->AddRef();
	}

STDMETHODIMP_(ULONG) CWriteCookie::Release()
	{
	return m_pCookie->Release();
	}



/*===================================================================
CWriteCookie::put_Item

Set the primary value for a cookie.
===================================================================*/

STDMETHODIMP CWriteCookie::put_Item(VARIANT varKey, BSTR bstrValue)
	{
	char            *szKey;		// ascii value of the key
    CWCharToMBCS    convValue;
    CWCharToMBCS    convKey;

	// Bug 122589: Don't crash when "bstrValue" is NULL
	if (bstrValue == NULL)
		return E_FAIL;

	// Initialize things
	//
	VARIANT *pvarKey = &varKey;
	HRESULT hrReturn = S_OK;

	// BUG 937: VBScript passes VT_VARIANT|VT_BYREF when passing obect
	//          produced by IEnumVariant
	//
	// Use VariantResolveDispatch which will:
	//
	//     *  Copy BYREF variants for us using VariantCopyInd
	//     *  handle E_OUTOFMEMORY for us
	//     *  get the default value from an IDispatch, which seems
	//        like an appropriate conversion.
	//
	VARIANT varKeyCopy;
	VariantInit(&varKeyCopy);
	if (V_VT(pvarKey) != VT_BSTR)
		{
		if (FAILED(VariantResolveDispatch(&varKeyCopy, &varKey, IID_IRequestDictionary, IDE_REQUEST)))
			goto LExit;

		pvarKey = &varKeyCopy;
		}

	switch (V_VT(pvarKey))
		{
	case VT_BSTR:
		break;

	case VT_ERROR:
		if (V_ERROR(pvarKey) == DISP_E_PARAMNOTFOUND)
			{
			if (m_pCookie->m_szValue == NULL)	// current value is a dictionary
				{
				CCookiePair *pNukePair = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.Head());
				while (pNukePair != NULL)
					{
					CCookiePair *pNext = static_cast<CCookiePair *>(pNukePair->m_pNext);
					delete pNukePair;
					pNukePair = pNext;
					}

				m_pCookie->m_mpszValues.ReInit();
				}
			else								// no dictionary value
				if (m_pCookie->m_fDuplicate)
					free(m_pCookie->m_szValue);
            if (FAILED(hrReturn = convValue.Init(bstrValue,m_pCookie->m_lCodePage))) {
                goto LExit;
            }
			m_pCookie->m_szValue = NULL;
			m_pCookie->AddValue(convValue.GetString(), TRUE);
			m_pCookie->m_fDirty = TRUE;
			goto LExit;
			}

		// Other error, FALL THROUGH to wrong type case

	default:
		ExceptionId(IID_IWriteCookie, IDE_COOKIE, IDE_EXPECTING_STR);
		hrReturn = E_FAIL;
		goto LExit;
		}

	// don't allow empty keys in the cookie
	//
    
    if (V_BSTR(pvarKey)) {

        if (FAILED(hrReturn = convKey.Init(V_BSTR(pvarKey),m_pCookie->m_lCodePage))) {
            goto LExit;
        }
        else {
            szKey = convKey.GetString();
        }
    }
    else {
		szKey = "";
    }

	if (*szKey == '\0')
		{
		ExceptionId(IID_IWriteCookie, IDE_COOKIE, IDE_COOKIE_EMPTY_DICT);
		hrReturn = E_FAIL;
		goto LExit;
		}

	// we're changing a dictionary value, so first trash the primary value
	//
	if (m_pCookie->m_fDuplicate)
		free(m_pCookie->m_szValue);

    if (FAILED(hrReturn = convValue.Init(bstrValue,m_pCookie->m_lCodePage))) {
        goto LExit;
    }

	m_pCookie->m_szValue = NULL;
	m_pCookie->AddKeyAndValue(szKey, convValue.GetString(), TRUE);
	m_pCookie->m_fDirty = TRUE;

LExit:
	VariantClear(&varKeyCopy);
	return hrReturn;
	}



/*===================================================================
CWriteCookie::put_Expires

Set the expires attribute for a cookie.
===================================================================*/

STDMETHODIMP CWriteCookie::put_Expires(DATE dtExpires)
	{
	if (FAILED(VariantDateToCTime(dtExpires, &m_pCookie->m_tExpires)))
		{
		ExceptionId(IID_IWriteCookie, IDE_COOKIE, IDE_COOKIE_BAD_EXPIRATION);
		return E_FAIL;
		}

	m_pCookie->m_fDirty = TRUE;
	return S_OK;
	}



/*===================================================================
CWriteCookie::put_Domain

Set the domain attribute for a cookie.
===================================================================*/

STDMETHODIMP CWriteCookie::put_Domain(BSTR bstrDomain)
	{
    CWCharToMBCS    convDomain;
    HRESULT         hr = S_OK;

    if (FAILED(hr = convDomain.Init(bstrDomain,m_pCookie->m_lCodePage)));

    else {
        if (m_pCookie->m_szDomain)
            free(m_pCookie->m_szDomain);
	    m_pCookie->m_szDomain = convDomain.GetString(TRUE);
	    m_pCookie->m_fDirty = TRUE;
    }

	return hr;
	}



/*===================================================================
CWriteCookie::put_Path

Set the path attribute for a cookie.
===================================================================*/

STDMETHODIMP CWriteCookie::put_Path(BSTR bstrPath) 
{
    HRESULT         hr = S_OK;

    CWCharToMBCS    convPath;

    if (FAILED(hr = convPath.Init(bstrPath,m_pCookie->m_lCodePage)));

    else {
		
        if (m_pCookie->m_szPath)
            free(m_pCookie->m_szPath);
	    m_pCookie->m_szPath = convPath.GetString(TRUE);
        if (m_pCookie->m_szPath == NULL)
            hr = E_OUTOFMEMORY;
    }

    if (SUCCEEDED(hr))
        m_pCookie->m_fDirty = TRUE;

	return hr;
}

/*===================================================================
CWriteCookie::put_Secure

Set the secure attribute for a cookie.
===================================================================*/

STDMETHODIMP CWriteCookie::put_Secure(VARIANT_BOOL fSecure)
	{
	m_pCookie->m_fSecure = fSecure;
	m_pCookie->m_fDirty = TRUE;

	return S_OK;
	}



/*===================================================================
CWriteCookie::get_HasKeys

Return True if the cookie contains keys, False if it is a simple
value
===================================================================*/

STDMETHODIMP CWriteCookie::get_HasKeys(VARIANT_BOOL *pfHasKeys)
	{
	*pfHasKeys = ( m_pCookie->m_mpszValues.Count() > 0 ? VARIANT_TRUE : VARIANT_FALSE);
	return S_OK;
	}



/*===================================================================
CWriteCookie::get__NewEnum

Return an enumerator object.

ReadCookie and WriteCookie use the same iterator object.
To reduce useless redundancy, deletage to IReadCookie.
The IReadCookie enumerator will likely be used much more
frequently than the IWriteCookie iterator, so we pay the
overhead of delegation in this function.
===================================================================*/

STDMETHODIMP CWriteCookie::get__NewEnum(IUnknown **ppEnumReturn)
	{
	IReadCookie *pReadCookie;
	if (FAILED(QueryInterface(IID_IReadCookie, reinterpret_cast<void **>(&pReadCookie))))
		{
		Assert (FALSE);		// expect success!
		return E_FAIL;
		}

	HRESULT hrNewEnum = pReadCookie->get__NewEnum(ppEnumReturn);

	pReadCookie->Release();
	return hrNewEnum;
	}



/*------------------------------------------------------------------
 * C R e a d C o o k i e
 */

/*===================================================================
CReadCookie::CReadCookie

constructor
===================================================================*/

CReadCookie::CReadCookie(CCookie *pCookie)
	{
	m_pCookie = pCookie;
	CDispatch::Init(IID_IReadCookie);
	}



/*===================================================================
CReadCookie::QueryInterface
CReadCookie::AddRef
CReadCookie::Release

Delegating IUnknown members for CReadCookie object.
===================================================================*/

STDMETHODIMP CReadCookie::QueryInterface(const IID &idInterface, void **ppvObj)
	{
	// Bug 85953 Trap IDispatch before it gets to the core object
	if (idInterface == IID_IUnknown || 
		idInterface == IID_IReadCookie ||
		idInterface == IID_IDispatch)
		{
		*ppvObj = this;
		static_cast<IUnknown *>(*ppvObj)->AddRef();
		return S_OK;
		}
	else
		return m_pCookie->QueryInterface(idInterface, ppvObj);
	}

STDMETHODIMP_(ULONG) CReadCookie::AddRef()
	{
	return m_pCookie->AddRef();
	}

STDMETHODIMP_(ULONG) CReadCookie::Release()
	{
	return m_pCookie->Release();
	}



/*===================================================================
CReadCookie::get_Item

Retrieve a value in the cookie dictionary.
===================================================================*/

STDMETHODIMP CReadCookie::get_Item(VARIANT varKey, VARIANT *pvarReturn)
	{
	char            *szKey;			// ascii version of the key
	CCookiePair     *pPair = NULL;	// name and value of cookie in the dictionary
    CWCharToMBCS    convKey;

    STACK_BUFFER( tempCookie, 128 );

	// Initialize things
	//
	VariantInit(pvarReturn);
	VARIANT *pvarKey = &varKey;
	HRESULT hrReturn = S_OK;

	// BUG 937: VBScript passes VT_VARIANT|VT_BYREF when passing obect
	//          produced by IEnumVariant
	//
	// Use VariantResolveDispatch which will:
	//
	//     *  Copy BYREF variants for us using VariantCopyInd
	//     *  handle E_OUTOFMEMORY for us
	//     *  get the default value from an IDispatch, which seems
	//        like an appropriate conversion.
	//
	VARIANT varKeyCopy;
	VariantInit(&varKeyCopy);
	DWORD vt = V_VT(pvarKey);

	if ((V_VT(pvarKey) != VT_BSTR) && (vt != VT_I2) && (vt != VT_I4))
		{
		if (FAILED(VariantResolveDispatch(&varKeyCopy, &varKey, IID_IRequestDictionary, IDE_REQUEST)))
			goto LExit;

		pvarKey = &varKeyCopy;
		}
	vt = V_VT(pvarKey);

	switch (vt)
		{
	// Bug 95201 support all numberic sub-types
	case VT_I1:  case VT_I2:               case VT_I8:
	case VT_UI1: case VT_UI2: case VT_UI4: case VT_UI8:
	case VT_R4:  case VT_R8:
		// Coerce all integral types to VT_I4
		if (FAILED(hrReturn = VariantChangeType(pvarKey, pvarKey, 0, VT_I4)))
			goto LExit;

		// fallthru to VT_I4

	case VT_I4:
	case VT_BSTR:
		break;
	
	case VT_ERROR:
		if (V_ERROR(pvarKey) == DISP_E_PARAMNOTFOUND)
			{
			V_VT(pvarReturn) = VT_BSTR;
	
			// simple value, URLEncoding NOT a good idea in this case
			if (m_pCookie->m_szValue)
				{
               	BSTR bstrT;
               	if (FAILED(SysAllocStringFromSz(m_pCookie->m_szValue, 0, &bstrT,m_pCookie->m_lCodePage)))
            		{
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
					hrReturn = E_FAIL;
					goto LExit;
            		}
		        V_BSTR(pvarReturn) = bstrT;
				}			
			// dictionary value, must URLEncode to prevent '&', '=' from being misinterpreted
			else
				{
				int cbHTTPCookie = m_pCookie->GetHTTPCookieSize();
				if (cbHTTPCookie > REQUEST_ALLOC_MAX)
					{
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_STACK_OVERFLOW);
					hrReturn = E_FAIL;
					goto LExit;
					}

                if (tempCookie.Resize(cbHTTPCookie) == FALSE) {
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
                    hrReturn = E_OUTOFMEMORY;
                    goto LExit;
                }
				char *szHTTPCookie = static_cast<char *>(tempCookie.QueryPtr());
				m_pCookie->GetHTTPCookie(szHTTPCookie);
	
               	BSTR bstrT;
               	if (FAILED(SysAllocStringFromSz(szHTTPCookie, 0, &bstrT,m_pCookie->m_lCodePage)))
            		{
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
					hrReturn = E_FAIL;
					goto LExit;
            		}
		        V_BSTR(pvarReturn) = bstrT;
				}

			goto LExit;
			}

	default:
		ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_EXPECTING_STR);
		hrReturn = E_FAIL;
		goto LExit;
		}

	if (vt == VT_BSTR)
		{
		// convert the key to ANSI
        if (V_BSTR(pvarKey)) {
            if (FAILED(hrReturn = convKey.Init(V_BSTR(pvarKey),m_pCookie->m_lCodePage))) {
                goto LExit;
            }
            else {
                szKey = convKey.GetString();
            }
        }
        else {
			szKey = "";
        }

		// Look up the key in the Cookie.
		pPair = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.FindElem(szKey, strlen(szKey)));
		}
	else 
		{
		// Look up item by index
		int iCount;

		iCount = V_I4(pvarKey);

		if ((iCount < 1) || 
			(m_pCookie->m_mpszValues.Count() == 0) ||
			(iCount > (int) m_pCookie->m_mpszValues.Count() ))
			{
			hrReturn = E_FAIL;
			ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_BAD_ARRAY_INDEX);
			goto LExit;
			}

		pPair = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.Head());
		while((iCount > 1) && (pPair != NULL)) 
			{
			pPair = static_cast<CCookiePair *>(pPair->m_pNext);
			iCount--;
			}
		}

	if (pPair)
		{
       	BSTR bstrT;
       	if (FAILED(SysAllocStringFromSz(pPair->m_szValue, 0, &bstrT,m_pCookie->m_lCodePage)))
    		{
			ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
			hrReturn = E_FAIL;
			goto LExit;
    		}
		V_VT(pvarReturn) = VT_BSTR;
		V_BSTR(pvarReturn) = bstrT;
		}

LExit:
	VariantClear(&varKeyCopy);
	return hrReturn;
	}



/*===================================================================
CReadCookie::get_HasKeys

Return True if the cookie contains keys, False if it is a simple
value
===================================================================*/

STDMETHODIMP CReadCookie::get_HasKeys(VARIANT_BOOL *pfHasKeys)
	{
	*pfHasKeys = (m_pCookie->m_mpszValues.Count() > 0 ? VARIANT_TRUE : VARIANT_FALSE);
	return S_OK;
	}

/*===================================================================
CReadCookie::get__NewEnum

Return an enumerator object.
===================================================================*/

STDMETHODIMP CReadCookie::get__NewEnum(IUnknown **ppEnumReturn)
	{
	*ppEnumReturn = NULL;

	CCookieIterator *pIterator = new CCookieIterator(m_pCookie);
	if (pIterator == NULL)
		{
		ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
		return E_OUTOFMEMORY;
		}

	*ppEnumReturn = pIterator;
	return S_OK;
	}

/*===================================================================
CReadCookie::get_Count

Parameters:
	pcValues - count is stored in *pcValues.  Set to 0 if this
		cookie is not multi-valued.
===================================================================*/
STDMETHODIMP CReadCookie::get_Count(int *pcValues)
	{
	*pcValues = m_pCookie->m_mpszValues.Count();
	return S_OK;
	}

/*===================================================================
CReadCookie::get_Key

Function called from DispInvoke to get keys from a multi-valued
Cookie collection.

Parameters:
	vKey		VARIANT [in], which parameter to get the key of
	pvarReturn	VARIANT *, [out] value of the requested parameter

Returns:
	S_OK on success, E_FAIL on failure.
===================================================================*/
STDMETHODIMP CReadCookie::get_Key(VARIANT varKey, VARIANT *pvarReturn)
	{
	char            *szKey;			// ascii version of the key
	CCookiePair     *pPair = NULL;	// name and value of cookie in the dictionary
    CWCharToMBCS    convKey;

    STACK_BUFFER( tempCookie, 128);

	// Initialize things
	//
	VariantInit(pvarReturn);
	VARIANT *pvarKey = &varKey;
	HRESULT hrReturn = S_OK;

	// BUG 937: VBScript passes VT_VARIANT|VT_BYREF when passing obect
	//          produced by IEnumVariant
	//
	// Use VariantResolveDispatch which will:
	//
	//     *  Copy BYREF variants for us using VariantCopyInd
	//     *  handle E_OUTOFMEMORY for us
	//     *  get the default value from an IDispatch, which seems
	//        like an appropriate conversion.
	//
	VARIANT varKeyCopy;
	VariantInit(&varKeyCopy);
	DWORD vt = V_VT(pvarKey);

	if ((V_VT(pvarKey) != VT_BSTR) && (vt != VT_I2) && (vt != VT_I4))
		{
		if (FAILED(VariantResolveDispatch(&varKeyCopy, &varKey, IID_IRequestDictionary, IDE_REQUEST)))
			goto LExit;

		pvarKey = &varKeyCopy;
		}
	vt = V_VT(pvarKey);

	switch (vt)
		{
	// Bug 95201 support all numberic sub-types
	case VT_I1:  case VT_I2:               case VT_I8:
	case VT_UI1: case VT_UI2: case VT_UI4: case VT_UI8:
	case VT_R4:  case VT_R8:
		// Coerce all integral types to VT_I4
		if (FAILED(hrReturn = VariantChangeType(pvarKey, pvarKey, 0, VT_I4)))
			goto LExit;

		// fallthru to VT_I4

	case VT_I4:
	case VT_BSTR:
		break;
	
	case VT_ERROR:
		if (V_ERROR(pvarKey) == DISP_E_PARAMNOTFOUND)
			{
			V_VT(pvarReturn) = VT_BSTR;
	
			// simple value, URLEncoding NOT a good idea in this case
			if (m_pCookie->m_szValue)
				{
               	BSTR bstrT;
               	if (FAILED(SysAllocStringFromSz(m_pCookie->m_szValue, 0, &bstrT,m_pCookie->m_lCodePage)))
            		{
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
					hrReturn = E_FAIL;
					goto LExit;
            		}
		        V_BSTR(pvarReturn) = bstrT;
				}			
			// dictionary value, must URLEncode to prevent '&', '=' from being misinterpreted
			else
				{
				int cbHTTPCookie = m_pCookie->GetHTTPCookieSize();
				if (cbHTTPCookie > REQUEST_ALLOC_MAX)
					{
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_STACK_OVERFLOW);
					hrReturn = E_FAIL;
					goto LExit;
					}

                if (tempCookie.Resize(cbHTTPCookie) == FALSE) {
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
                    hrReturn = E_OUTOFMEMORY;
                    goto LExit;
                }
				char *szHTTPCookie = static_cast<char *>(tempCookie.QueryPtr());
				m_pCookie->GetHTTPCookie(szHTTPCookie);
	
               	BSTR bstrT;
               	if (FAILED(SysAllocStringFromSz(szHTTPCookie, 0, &bstrT, m_pCookie->m_lCodePage)))
            		{
					ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_OOM);
					hrReturn = E_FAIL;
					goto LExit;
            		}
		        V_BSTR(pvarReturn) = bstrT;
				}

			goto LExit;
			}

	default:
		ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_EXPECTING_STR);
		hrReturn = E_FAIL;
		goto LExit;
		}

	if (vt == VT_BSTR)
		{
		// convert the key to ANSI
        if (V_BSTR(pvarKey)) {
            if (FAILED(hrReturn = convKey.Init(V_BSTR(pvarKey),m_pCookie->m_lCodePage))) {
                goto LExit;
            }
            else {
                szKey = convKey.GetString();
            }
        }
        else {
			szKey = "";
        }

		// Look up the key in the Cookie.
		pPair = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.FindElem(szKey, strlen(szKey)));
		}
	else 
		{
		// Look up item by index
		int iCount;
		
		iCount = V_I4(pvarKey);

		if ((iCount < 1) || 
			(m_pCookie->m_mpszValues.Count() == 0) ||
			(iCount > (int) m_pCookie->m_mpszValues.Count() ))
			{
			hrReturn = E_FAIL;
			ExceptionId(IID_IReadCookie, IDE_COOKIE, IDE_BAD_ARRAY_INDEX);
			goto LExit;
			}

		pPair = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.Head());
		while((iCount > 1) && (pPair != NULL)) 
			{
			pPair = static_cast<CCookiePair *>(pPair->m_pNext);
			iCount--;
			}
		}

	if (pPair)
		{
		// Create a BSTR containing the key for this variant
    	BSTR bstrT;
        SysAllocStringFromSz((CHAR *)pPair->m_pKey, 0, &bstrT, m_pCookie->m_lCodePage);
		if (!bstrT)
			return E_OUTOFMEMORY;
		V_VT(pvarReturn) = VT_BSTR;
		V_BSTR(pvarReturn) = bstrT;
    	}

LExit:
	VariantClear(&varKeyCopy);
	return hrReturn;
	}


/*------------------------------------------------------------------
 * C C o o k i e
 */

/*===================================================================
CCookie::CCookie

constructor
===================================================================*/

CCookie::CCookie(CIsapiReqInfo   *pIReq, UINT lCodePage, IUnknown *pUnkOuter, PFNDESTROYED pfnDestroy)
	: m_WriteCookieInterface(this),
	  m_ReadCookieInterface(this),
	  m_CookieSupportErrorInfo(this)
	{
	m_szValue    = NULL;
	m_tExpires  = -1;
	m_szDomain   = NULL;
	m_szPath     = NULL;
	m_fSecure    = FALSE;
	m_fDirty     = FALSE;
	m_fDuplicate = FALSE;
	m_pfnDestroy = pfnDestroy;
	m_pIReq      = pIReq;
    m_lCodePage  = lCodePage;
	m_cRefs      = 1;
	}



/*===================================================================
CCookie::~CCookie

Destructor
===================================================================*/

CCookie::~CCookie()
	{
	CCookiePair *pNukePair = static_cast<CCookiePair *>(m_mpszValues.Head());
	while (pNukePair != NULL)
		{
		CCookiePair *pNext = static_cast<CCookiePair *>(pNukePair->m_pNext);
		delete pNukePair;
		pNukePair = pNext;
		}

	m_mpszValues.UnInit();
	
	if (m_fDuplicate)
		free(m_szValue);
	
	if (m_szDomain) free(m_szDomain);
	if (m_szPath) free(m_szPath);
	}



/*===================================================================
CCookie::Init

initialize the cookie. This initializes the cookie's value hashing
table
===================================================================*/

HRESULT CCookie::Init()
{
    HRESULT     hr = S_OK;
    TCHAR       pathInfo[MAX_PATH];
#if UNICODE
    CWCharToMBCS  convStr;
#endif

    if (FAILED(hr = m_mpszValues.Init(7)));

    // it would be nice if we could use the application path from the metabase,
    // but because of case sensitivity issues, we can't.  The safest bet is
    // to use the request's path info up to the length of the application's
    // pathinfo.

    else if (FAILED(hr=FindApplicationPath(m_pIReq, pathInfo, sizeof(pathInfo))));

#if UNICODE
    else if (FAILED(hr = convStr.Init(m_pIReq->QueryPszPathInfo(), m_lCodePage, _tcslen(pathInfo))));

    else {

        m_szPath = convStr.GetString(TRUE);
    }
#else
    else {

        int cchPathInfo = _tcslen(pathInfo);
        
        if (!(m_szPath = (char *)malloc(cchPathInfo+1))) {
            hr = E_OUTOFMEMORY;
        }
        else {
            memcpy(m_szPath, pathInfo, cchPathInfo+1);
        }
    }
#endif

    return hr;
}



/*===================================================================
CCookie::QueryInterface
CCookie::AddRef
CCookie::Release

IUnknown members for CCookie object.

Note on CCookie::QueryInterface: The Query for IDispatch is
ambiguous because it can either refer to IReadCookie or
IWriteCookie.  To resolve this, we resolve requests for IDispatch
to IReadCookie.  The rationale for this is that the code in
request.cpp calls QueryInterface for a generic IDispatch pointer
(because the collection is heterogenous)  The Response.Cookies
collection is homogeneous and so only calls QueryInterface for
IWriteCookie.
===================================================================*/

STDMETHODIMP CCookie::QueryInterface(const IID &idInterface, void **ppvObj)
	{
	if (idInterface == IID_IUnknown)
		*ppvObj = this;

	else if (idInterface == IID_IReadCookie || idInterface == IID_IDispatch)
		*ppvObj = &m_ReadCookieInterface;

	else if (idInterface == IID_IWriteCookie)
		*ppvObj = &m_WriteCookieInterface;

	else if (idInterface == IID_ISupportErrorInfo)
		*ppvObj = &m_CookieSupportErrorInfo;

	else
		*ppvObj = NULL;

	if (*ppvObj != NULL)
		{
		static_cast<IUnknown *>(*ppvObj)->AddRef();
		return S_OK;
		}

	return ResultFromScode(E_NOINTERFACE);
	}


STDMETHODIMP_(ULONG) CCookie::AddRef()
	{
	return ++m_cRefs;
	}


STDMETHODIMP_(ULONG) CCookie::Release(void)
	{
	if (--m_cRefs != 0)
		return m_cRefs;

	if (m_pfnDestroy != NULL)
		(*m_pfnDestroy)();

	delete this;
	return 0;
	}



/*===================================================================
CCookie::AddValue

Set the cookie's primary value. One you set the primary value,
you can't reset it.
===================================================================*/

HRESULT CCookie::AddValue(char *szValue, BOOL fDuplicate)
	{
	if (m_szValue != NULL)		// cookie already is marked as single-valued
		return E_FAIL;

	if (m_mpszValues.Count() != 0)	// cookie already has a value
		return E_FAIL;

	if (fDuplicate)
		{
		char *szNew = (char *)malloc(strlen(szValue) + 1);
		if (szNew == NULL)
			return E_OUTOFMEMORY;

		m_szValue = strcpy(szNew, szValue);
		}
	else
		m_szValue = szValue;

	m_fDuplicate = fDuplicate;
	return S_OK;
	}



/*===================================================================
CCookie::AddKeyAndValue

Add a key and value pair to the Cookie's dictionary. It fails
if the cookie has a primary value already set. It will overwrite
the value if the key already exists.
===================================================================*/

HRESULT CCookie::AddKeyAndValue(char *szKey, char *szValue, BOOL fDuplicate)
	{
	if (m_szValue != NULL)
		return E_FAIL;

	delete static_cast<CCookiePair *>(m_mpszValues.DeleteElem(szKey, strlen(szKey)));

	CCookiePair *pCookiePair = new CCookiePair;
	if (pCookiePair == NULL)
		return E_OUTOFMEMORY;

	if (FAILED(pCookiePair->Init(szKey, szValue, fDuplicate)))
		return E_FAIL;

	m_mpszValues.AddElem(pCookiePair);

	return S_OK;
	}




/*===================================================================
CCookie::GetHTTPCookieSize

Return the number of bytes required for the expansion of the HTTP_COOKIE variable
===================================================================*/

size_t CCookie::GetHTTPCookieSize()
	{
	if (m_szValue)
		return URLEncodeLen(m_szValue);

	else
		{
		int cbValue = 1;
		CCookiePair *pPair = static_cast<CCookiePair *>(m_mpszValues.Head());
		while (pPair)
			{
			// Add size of the URL Encoded key, a character for the '=', and a
			// character for the '&' or the NUL terminator.  URLEncodeLen
			// returns the size + 1, so the two calls to URLEncodeLen() add the
			// two characters we need.
			//
			cbValue += URLEncodeLen(reinterpret_cast<char *>(pPair->m_pKey)) + URLEncodeLen(pPair->m_szValue);
			pPair = static_cast<CCookiePair *>(pPair->m_pNext);
			}

		return cbValue;
		}
	}


/*===================================================================
CCookie::GetHTTPCookie

Return the URL Encoded value a single cookie

Parameters:
	szBuffer -  pointer to the destination buffer to store the
				URL encoded value

Returns:
	Returns a pointer to the terminating NUL character.
===================================================================*/

char *CCookie::GetHTTPCookie(char *szBuffer)
	{
	if (m_szValue)
		return URLEncode(szBuffer, m_szValue);

	else
		{
		char *szDest = szBuffer;
		*szDest = '\0';

		CCookiePair *pPair = static_cast<CCookiePair *>(m_mpszValues.Head());
		while (pPair)
			{
			// Write <name>=<value> string
			szDest = URLEncode(szDest, reinterpret_cast<char *>(pPair->m_pKey));
			*szDest++ = '=';
			szDest = URLEncode(szDest, pPair->m_szValue);

			// Advance
			pPair = static_cast<CCookiePair *>(pPair->m_pNext);

			// Append '&' if there's another one following
			if (pPair)
				*szDest++ = '&';
			}

		Assert (*szDest == '\0');	// make sure we are nul-terminated
		return szDest;
		}
	}



/*===================================================================
CCookie::GetCookieHeaderSize

Return the number of bytes required to allocate for the "Set-Cookie" header.

Parameters:
	szName - the name of the cookie (the size of the name is added to the value)

Returns:
	Returns 0 if *this does not contain a cookie value.
===================================================================*/

size_t CCookie::GetCookieHeaderSize(const char *szName)
	{
	int cbCookie = sizeof "Set-Cookie: ";		// initialize and add NUL terminator now

	// Add size of the URL Encoded name, a character for the '=', and the size
	// of the URL Encoded cookie value.  URLEncodeLen, and GetHttpCookieSize
	// compensate for the NUL terminator, so we actually SUBTRACT 1. (-2 for
	// these two function calls, +1 for the '=' sign
	//
	cbCookie += URLEncodeLen(szName) + GetHTTPCookieSize() - 1;
	
	if (m_tExpires != -1)
		cbCookie += (sizeof "; expires=") + DATE_STRING_SIZE - 1;

	// BUG 250 - DBCS External
	// ASP does not URLEncode the domain and path attributes, which was noticed
	// during localizaiton.
	//
	// NOTE: URLEncodeLen and sizeof both add a space for the nul terminator,
	//       so we subtract 2 to compensate.
	//
	if (m_szDomain)
		cbCookie += (sizeof "; domain=") + DBCSEncodeLen(m_szDomain) - 2;

	cbCookie += (sizeof "; path=") + DBCSEncodeLen(m_szPath) - 2;

	if (m_fSecure)
		cbCookie += (sizeof "; secure") - 1;
	
	return cbCookie;
	}



/*===================================================================
CCookie::GetCookieHeader

Construct the appropriate "Set-Cookie" header for a cookie.

Parameters:
	szName - the name of the cookie (the size of the name is added to the value)

Returns:
	Returns 0 if *this does not contain a cookie value.
===================================================================*/

char *CCookie::GetCookieHeader(const char *szName, char *szBuffer)
{
	// write out the cookie name and value
	//
	char *szDest = strcpyExA(szBuffer, "Set-Cookie: ");
	szDest = URLEncode(szDest, szName);
	szDest = strcpyExA(szDest, "=");
	szDest = GetHTTPCookie(szDest);
	
	if (m_tExpires != -1) {
		char szExpires[DATE_STRING_SIZE];
		CTimeToStringGMT(&m_tExpires, szExpires, TRUE);

		szDest = strcpyExA(szDest, "; expires=");
		szDest = strcpyExA(szDest, szExpires);
    }

	if (m_szDomain) {
		szDest = strcpyExA(szDest, "; domain=");
		szDest = DBCSEncode(szDest, m_szDomain);
    }

	szDest = strcpyExA(szDest, "; path=");
	szDest = DBCSEncode(szDest, m_szPath);

	if (m_fSecure)
		szDest = strcpyExA(szDest, "; secure");

	return szDest;
	}



/*------------------------------------------------------------------
 * C C o o k i e P a i r
 */

/*===================================================================
CCookiePair::CCookiePair

constructor
===================================================================*/

CCookiePair::CCookiePair()
	{
	m_fDuplicate = FALSE;
	m_szValue = NULL;
	}



/*===================================================================
CCookiePair::Init

Initialize the cookie pair with a key and a value.  Optionally,
it will copy the strings as well.
===================================================================*/

HRESULT CCookiePair::Init(const char *szKey, const char *szValue, BOOL fDuplicate)
	{
	m_fDuplicate = fDuplicate;
	if (fDuplicate)
		{
		char *szNewKey = (char *)malloc(strlen(szKey) + 1);
		if (szNewKey == NULL)
			return E_OUTOFMEMORY;
		
		char *szNewValue = (char *)malloc(strlen(szValue) + 1);
		if (szNewValue == NULL)
			{
			free(szNewKey);
			return E_OUTOFMEMORY;
			}
		
		if (FAILED(CLinkElem::Init(strcpy(szNewKey, szKey), strlen(szKey))))
			{
			free(szNewKey);
			free(szNewValue);
			return E_FAIL;
			}
	
		m_szValue = strcpy(szNewValue, szValue);
		}
	else
		{
		if (FAILED(CLinkElem::Init(const_cast<char *>(szKey), strlen(szKey))))
			return E_FAIL;
	
		m_szValue = const_cast<char *>(szValue);
		}
	
	return S_OK;
	}



/*===================================================================
CCookiePair::~CCookiePair

destructor
===================================================================*/

CCookiePair::~CCookiePair()
	{
	if (m_fDuplicate)
		{
		if (m_pKey) free(m_pKey);
		if (m_szValue) free(m_szValue);
		}
	}



/*------------------------------------------------------------------
 * C C o o k i e I t e r a t o r
 */

/*===================================================================
CCookieIterator::CCookieIterator

Constructor
===================================================================*/

CCookieIterator::CCookieIterator(CCookie *pCookie)
	{
	m_pCookie	= pCookie;
	m_pCurrent  = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.Head());
	m_cRefs		= 1;

	m_pCookie->AddRef();
	}



/*===================================================================
CCookieIterator::CCookieIterator

Destructor
===================================================================*/

CCookieIterator::~CCookieIterator()
	{
	m_pCookie->Release();
	}



/*===================================================================
CCookieIterator::QueryInterface
CCookieIterator::AddRef
CCookieIterator::Release

IUnknown members for CServVarsIterator object.
===================================================================*/

STDMETHODIMP CCookieIterator::QueryInterface(REFIID iid, void **ppvObj)
	{
	if (iid == IID_IUnknown || iid == IID_IEnumVARIANT)
		{
		AddRef();
		*ppvObj = this;
		return S_OK;
		}

	*ppvObj = NULL;
	return E_NOINTERFACE;
	}


STDMETHODIMP_(ULONG) CCookieIterator::AddRef()
	{
	return ++m_cRefs;
	}


STDMETHODIMP_(ULONG) CCookieIterator::Release()
	{
	if (--m_cRefs > 0)
		return m_cRefs;

	delete this;
	return 0;
	}



/*===================================================================
CCookieIterator::Clone

Clone this iterator (standard method)
===================================================================*/

STDMETHODIMP CCookieIterator::Clone(IEnumVARIANT **ppEnumReturn)
	{
	CCookieIterator *pNewIterator = new CCookieIterator(m_pCookie);
	if (pNewIterator == NULL)
		return E_OUTOFMEMORY;

	// new iterator should point to same location as this.
	pNewIterator->m_pCurrent = m_pCurrent;

	*ppEnumReturn = pNewIterator;
	return S_OK;
	}



/*===================================================================
CCookieIterator::Next

Get next value (standard method)

To rehash standard OLE semantics:

	We get the next "cElements" from the collection and store them
	in "rgVariant" which holds at least "cElements" items.  On
	return "*pcElementsFetched" contains the actual number of elements
	stored.  Returns S_FALSE if less than "cElements" were stored, S_OK
	otherwise.
===================================================================*/

STDMETHODIMP CCookieIterator::Next(unsigned long cElementsRequested, VARIANT *rgVariant, unsigned long *pcElementsFetched)
	{
	// give a valid pointer value to 'pcElementsFetched'
	//
	unsigned long cElementsFetched;
	if (pcElementsFetched == NULL)
		pcElementsFetched = &cElementsFetched;

	// Loop through the collection until either we reach the end or
	// cElements becomes zero
	//
	unsigned long cElements = cElementsRequested;
	*pcElementsFetched = 0;

	while (cElements > 0 && m_pCurrent != NULL)
		{
       	BSTR bstrT;
       	if (FAILED(SysAllocStringFromSz(reinterpret_cast<char *>(m_pCurrent->m_pKey), 0, &bstrT, m_pCookie->m_lCodePage)))
			return E_OUTOFMEMORY;
		V_VT(rgVariant) = VT_BSTR;
        V_BSTR(rgVariant) = bstrT;

		++rgVariant;
		--cElements;
		++*pcElementsFetched;
		m_pCurrent = static_cast<CCookiePair *>(m_pCurrent->m_pNext);
		}

	// initialize the remaining variants
	//
	while (cElements-- > 0)
		VariantInit(rgVariant++);

	return (*pcElementsFetched == cElementsRequested)? S_OK : S_FALSE;
	}



/*===================================================================
CCookieIterator::Skip

Skip items (standard method)

To rehash standard OLE semantics:

	We skip over the next "cElements" from the collection.
	Returns S_FALSE if less than "cElements" were skipped, S_OK
	otherwise.
===================================================================*/

STDMETHODIMP CCookieIterator::Skip(unsigned long cElements)
	{
	/* Loop through the collection until either we reach the end or
	 * cElements becomes zero
	 */
	while (cElements > 0 && m_pCurrent != NULL)
		{
		--cElements;
		m_pCurrent = static_cast<CCookiePair *>(m_pCurrent->m_pNext);
		}

	return (cElements == 0)? S_OK : S_FALSE;
	}



/*===================================================================
CCookieIterator::Reset

Reset the iterator (standard method)
===================================================================*/

STDMETHODIMP CCookieIterator::Reset()
	{
	m_pCurrent = static_cast<CCookiePair *>(m_pCookie->m_mpszValues.Head());
	return S_OK;
	}